# http://mathling.com/shape/paths library module

http://mathling.com/shape/paths

Module with functions providing some interesting paths

No global parameters or randomizers

Copyright© Mary Holstege 2020-2023

CC-BY (https://creativecommons.org/licenses/by/4.0/)

Status: Active

### Imports

http://mathling.com/type/modulated-lissajousimport module namespace lissa_t="http://mathling.com/type/modulated-lissajous" at "../types/modulated-lissajous.xqy"http://mathling.com/type/pendulum

import module namespace pend_t="http://mathling.com/type/pendulum" at "../types/pendulum.xqy"http://mathling.com/type/fern

import module namespace fern_t="http://mathling.com/type/fern" at "../types/fern.xqy"http://mathling.com/geometric/curve

import module namespace curve="http://mathling.com/geometric/curve" at "../geo/curves.xqy"http://mathling.com/type/modulated-knot

import module namespace knot_t="http://mathling.com/type/modulated-knot" at "../types/modulated-knot.xqy"http://mathling.com/geometric/rectangle

import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.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/geometric/path

import module namespace path="http://mathling.com/geometric/path" at "../geo/path.xqy"http://mathling.com/geometric/edge

import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"http://mathling.com/core/utilities

import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/type/polynomial

import module namespace polynomial="http://mathling.com/type/polynomial" at "../types/polynomial.xqy"http://mathling.com/core/complex

import module namespace z="http://mathling.com/core/complex" at "../core/complex.xqy"http://mathling.com/geometric/ellipse

import module namespace ellipse="http://mathling.com/geometric/ellipse" at "../geo/ellipse.xqy"http://mathling.com/type/wiggle

import module namespace wiggle_t="http://mathling.com/type/wiggle" at "../types/wiggle.xqy"http://mathling.com/core/config

import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"http://mathling.com/core/errors

import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"http://mathling.com/core/sequences

import module namespace seq="http://mathling.com/core/sequences" at "../core/sequences.xqy"

### Functions

####
__Function__: arc

declare function arc($center as map(xs:string,item()*),
$radius as xs:double,
$arc as xs:double,
$start-angle as xs:double,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: arc

declare function arc($center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, $start-angle as xs:double, $num-points as xs:integer) as map(xs:string,item()*)*

arc()

Make a circular path of $arc degrees from $start-angle

##### Params

- center as map(xs:string,item()*): center of the circle
- radius as xs:double: radius of the circle
- arc as xs:double: degrees of arc
- start-angle as xs:double: starting angle (degrees)
- num-points as xs:integer: number of points to create along arc

##### Returns

- map(xs:string,item()*)*

declare function this:arc( $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, $start-angle as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { (: for (α=β; α<θ+β α+=θ/n) :) for $point in 0 to $num-points - 1 (: α = β + point*θ/n :) let $α := $start-angle + $point * ($arc div $num-points) return ( geom:destination($center, $α, $radius) ) }

####
__Function__: reverse-arc

declare function reverse-arc($center as map(xs:string,item()*),
$radius as xs:double,
$arc as xs:double,
$start-angle as xs:double,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: reverse-arc

declare function reverse-arc($center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, $start-angle as xs:double, $num-points as xs:integer) as map(xs:string,item()*)*

reverse-arc()

Make a circular path from $start-angle down to $start-angle - $arc

##### Params

- center as map(xs:string,item()*): center of the circle
- radius as xs:double: radius of the circle
- arc as xs:double: degrees of arc
- start-angle as xs:double: starting angle (degrees)
- num-points as xs:integer: number of points to create along arc

##### Returns

- map(xs:string,item()*)*

declare function this:reverse-arc( $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, $start-angle as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { (: for (α=θ+β; α>=β α-=θ/n) :) for $point in 0 to $num-points (: α = β - point*θ/n :) let $α := $start-angle - $point * ($arc div $num-points) return ( geom:destination($center, $α, $radius) ) }

####
__Function__: helix

declare function helix($center as map(xs:string,item()*),
$radius as xs:double,
$slope as xs:double,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: helix

declare function helix($center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer) as map(xs:string,item()*)*

helix()

Create a right-handed 3-D helix path

##### Params

- center as map(xs:string,item()*): starting point, center of turn
- radius as xs:double: radius of helix
- slope as xs:double: slope of helix
- num-points as xs:integer: how many points to draw helix(x) = {a cos(t), a sin(t), b t} a = radius; b/a = slope; so b = slope*a

##### Returns

- map(xs:string,item()*)*

declare function this:helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { for $t in 0 to $num-points - 1 let $x := $radius * math:cos($t) + point:px($center) let $y := $radius * math:sin($t) + point:py($center) let $z := $slope * $radius * $t + point:pz($center) return point:point($x, $y, $z) }

####
__Function__: helix

declare function helix($center as map(xs:string,item()*),
$radius as xs:double,
$slope as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: helix

declare function helix($center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

helix()

Create a right-handed 3-D helix path

##### Params

- center as map(xs:string,item()*): starting point, center of turn
- radius as xs:double: radius of helix
- slope as xs:double: slope of helix
- num-points as xs:integer: how many points to draw
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs 0 to 2π; gives double spirals helix(x) = {a cos(t), a sin(t), b t} a = radius; b/a = slope; so b = slope*a

##### Returns

- map(xs:string,item()*)*

declare function this:helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*math:cos($t) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($t) + $δy }, function ($t as xs:double) as xs:double { $slope*$radius*$t + $δz }, $min, $max ) ) }

####
__Function__: left-helix

declare function left-helix($center as map(xs:string,item()*),
$radius as xs:double,
$slope as xs:double,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: left-helix

declare function left-helix($center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer) as map(xs:string,item()*)*

left-helix()

Create a right-handed 3-D helix path

##### Params

- center as map(xs:string,item()*): starting point, center of turn
- radius as xs:double: radius of helix
- slope as xs:double: slope of helix
- num-points as xs:integer: how many points to draw helix(x) = {-a cos(t), a sin(t), b t} a = radius; b/a = slope; so b = slope*a

##### Returns

- map(xs:string,item()*)*

declare function this:left-helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { for $t in 0 to $num-points - 1 let $x := -$radius * math:cos($t) + point:px($center) let $y := $radius * math:sin($t) + point:py($center) let $z := $slope * $radius * $t + point:pz($center) return point:point($x, $y, $z) }

####
__Function__: left-helix

declare function left-helix($center as map(xs:string,item()*),
$radius as xs:double,
$slope as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: left-helix

declare function left-helix($center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

left-helix()

Create a left-handed 3-D helix path

##### Params

- center as map(xs:string,item()*): starting point, center of turn
- radius as xs:double: radius of helix
- slope as xs:double: slope of helix
- num-points as xs:integer: how many points to draw
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs 0 to 2π; gives double spirals helix(x) = {-a cos(t), a sin(t), b t} a = radius; b/a = slope; so b = slope*a

##### Returns

- map(xs:string,item()*)*

declare function this:left-helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { -$radius*math:cos($t) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($t) + $δy }, function ($t as xs:double) as xs:double { $slope*$radius*$t + $δz }, $min, $max ) ) }

####
__Function__: torus-knot

declare function torus-knot($center as map(xs:string,item()*),
$radius as xs:double,
$p as xs:integer,
$q as xs:integer,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: torus-knot

declare function torus-knot($center as map(xs:string,item()*), $radius as xs:double, $p as xs:integer, $q as xs:integer, $num-points as xs:integer) as map(xs:string,item()*)*

torus-knot()

Create a torus knot.

##### Params

- center as map(xs:string,item()*): center point of knot
- radius as xs:double: scaling of knot
- p as xs:integer: knot parameter
- q as xs:integer: know parameter For clean knots p and q should be mutually prime, if they aren't you miss some of the lobes bridges = q; crossings = p(q-1); generally p-lobed shape Normally p > q and you get clean lobes When p < q, you get involuted circles
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:torus-knot( $center as map(xs:string,item()*), $radius as xs:double, $p as xs:integer, $q as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*math:cos($q*$t)*(3 + math:cos($p*$t)) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($q*$t)*(3 + math:cos($p*$t)) + $δy }, function ($t as xs:double) as xs:double { $radius*math:sin($p*$t) + $δz }, 0, 2*math:pi() ) ) }

####
__Function__: skewed-torus-knot

declare function skewed-torus-knot($center as map(xs:string,item()*),
$radius as xs:double,
$p as xs:integer,
$q as xs:integer,
$s as xs:integer,
$r as xs:integer,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: skewed-torus-knot

declare function skewed-torus-knot($center as map(xs:string,item()*), $radius as xs:double, $p as xs:integer, $q as xs:integer, $s as xs:integer, $r as xs:integer, $num-points as xs:integer) as map(xs:string,item()*)*

skewed-torus-knot()

Create a torus knot.

##### Params

- center as map(xs:string,item()*): center point of knot
- radius as xs:double: scaling of knot
- p as xs:integer: knot parameter
- q as xs:integer: knot parameter For clean knots p and q should be mutually prime, if they aren't you miss some of the lobes bridges = q; crossings = p(q-1); generally p-lobed shape Normally p > q and you get clean lobes When p < q, you get involuted circles
- s as xs:integer: skew parameter < 4
- r as xs:integeradius: scaling of knot: skew parameter < 4 A normal torus knot has s=r=3; s!=r gives smooshed knots; s is very different from r gives overlaps perceived as complicated twists s=r>3 gives pudgier links s=r<3 gives linked circles
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:skewed-torus-knot( $center as map(xs:string,item()*), $radius as xs:double, $p as xs:integer, $q as xs:integer, $s as xs:integer, $r as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric($num-points, function ($t as xs:double) as xs:double { $radius*math:cos($q*$t)*($s + math:cos($p*$t)) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($q*$t)*($r + math:cos($p*$t)) + $δy }, function ($t as xs:double) as xs:double { $radius*math:sin($p*$t) + $δz }, 0, 2*math:pi() ) ) }

####
__Function__: modulated-torus-knot

declare function modulated-torus-knot($center as map(xs:string,item()*),
$radius as xs:double,
$knot as map(xs:string,item()*),
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: modulated-torus-knot

declare function modulated-torus-knot($center as map(xs:string,item()*), $radius as xs:double, $knot as map(xs:string,item()*), $num-points as xs:integer) as map(xs:string,item()*)*

modulated-torus-knot()

Create a modulated torus knot.

##### Params

- center as map(xs:string,item()*): center point of knot
- radius as xs:double: scaling of knot
- knot as map(xs:string,item()*): knot parameters (see modulated torus knot type information)
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:modulated-torus-knot( $center as map(xs:string,item()*), $radius as xs:double, $knot as map(xs:string,item()*), $num-points as xs:integer ) as map(xs:string,item()*)* { trace((), "@"||$radius||" "||geom:quote($center)||" "||knot_t:describe($knot)), let $p := $knot=>knot_t:p() let $openness := $knot=>knot_t:openness() let $x-factors := $knot=>knot_t:x-factors() let $y-factors := $knot=>knot_t:y-factors() let $stretch := $knot=>knot_t:stretch() let $openness := $knot=>knot_t:openness() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric($num-points, function ($t as xs:double) as xs:double { $radius*$stretch*( math:cos($p*$t)* ($openness + sum( let $n := count($x-factors) for $j in 1 to $n return ( let $factor := $x-factors[$j] let $r := $factor=>knot_t:r() let $q := $factor=>knot_t:q() return ( if ($factor=>knot_t:skew()) then ( $r*math:sin($q*$t) ) else ( $r*math:cos($q*$t) ) ) ) ) ) ) + $δx }, function ($t as xs:double) as xs:double { $radius*( math:sin($p*$t)* ($openness + sum( let $n := count($y-factors) for $j in 1 to $n return ( let $factor := $y-factors[$j] let $r := $factor=>knot_t:r() let $q := $factor=>knot_t:q() return ( if ($factor=>knot_t:skew()) then ( $r*math:sin($q*$t) ) else ( $r*math:cos($q*$t) ) ) ) ) ) ) + $δy }, function ($t as xs:double) as xs:double { $radius*0.2*( $openness + math:sin(max(($p,$x-factors!knot_t:q(.)))*$t) ) + $δz }, 0, 2*math:pi() ) ) }

####
__Function__: modulated-lissajous

declare function modulated-lissajous($center as map(xs:string,item()*),
$radius as xs:double,
$curve as map(xs:string,item()*),
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: modulated-lissajous

declare function modulated-lissajous($center as map(xs:string,item()*), $radius as xs:double, $curve as map(xs:string,item()*), $num-points as xs:integer) as map(xs:string,item()*)*

modulated-lissajous()

Create a modulated Lissajous curve.

##### Params

- center as map(xs:string,item()*): center point of curve
- radius as xs:double: scaling of curve
- curve as map(xs:string,item()*): curve parameters (see Lissajous type information)
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:modulated-lissajous( $center as map(xs:string,item()*), $radius as xs:double, $curve as map(xs:string,item()*), $num-points as xs:integer ) as map(xs:string,item()*)* { let $x-factors := $curve=>lissa_t:x-factors() let $y-factors := $curve=>lissa_t:y-factors() let $stretch := $curve=>lissa_t:stretch() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*$stretch*sum( for $factor in $x-factors let $n := $factor=>lissa_t:n() let $φ := util:radians($factor=>lissa_t:phase()) let $skew := $factor=>lissa_t:skew() return if ($skew) then math:sin($n*$t + $φ) else math:cos($n*$t + $φ) ) + $δx }, function ($t as xs:double) as xs:double { $radius*sum( for $factor in $y-factors let $n := $factor=>lissa_t:n() let $φ := util:radians($factor=>lissa_t:phase()) let $skew := $factor=>lissa_t:skew() return if ($skew) then math:sin($n*$t + $φ) else math:cos($n*$t + $φ) ) + $δy }, function ($t as xs:double) as xs:double { $radius*math:sin(max($x-factors=>lissa_t:n())*$t) + $δz }, 0, 2 * math:pi() ) ) }

####
__Function__: rose-curve

declare function rose-curve($center as map(xs:string,item()*),
$radius as xs:double,
$n as xs:integer, (: k=n/d petals: 2k if even, k :)
$d as xs:integer,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: rose-curve

declare function rose-curve($center as map(xs:string,item()*), $radius as xs:double, $n as xs:integer, (: k=n/d petals: 2k if even, k :) $d as xs:integer, $num-points as xs:integer) as map(xs:string,item()*)*

rose-curve()

Create a closed looping curve

(see https://en.wikipedia.org/wiki/Rose_(mathematics))

##### Params

- center as map(xs:string,item()*): center point of rose
- radius as xs:double: scaling of rose
- n as xs:integer: rose parameterum-points: number of points to draw
- d as xs:integer: rose parameter k = n/d $n > $d gives petals around a center $n < $d gives involuted petals k = 1 is a circle Number of petals depends on k k is integer => k if odd; 2k if even k is n/3 w/ n mod 3 != 0; n petals if n is odd; 2k if even n and d mutually prime gives cleaner roses; not mutually prime drops some of the petals
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:rose-curve( $center as map(xs:string,item()*), $radius as xs:double, $n as xs:integer, (: k=n/d petals: 2k if even, k :) $d as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $k := $n div $d let $angle := if ($n mod $d = 0) then ( if ($k mod 2 = 0) then 2 * math:pi() else math:pi() ) else ( 2 * math:pi() * $d ) let $δx := point:px($center) let $δy := point:py($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*math:cos($k*$t)*math:cos($t) + $δx }, function ($t as xs:double) as xs:double { $radius*math:cos($k*$t)*math:sin($t) + $δy }, 0, $angle ) ) }

####
__Function__: trefoil

declare function trefoil($center as map(xs:string,item()*),
$radius as xs:double,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: trefoil

declare function trefoil($center as map(xs:string,item()*), $radius as xs:double, $num-points as xs:integer) as map(xs:string,item()*)*

trefoil()

A trefoil; as a 3D curve (torus surface)

Compared to a torus(3,2) the lobes are more drop-like and smaller

Compared to a rose(3,1) the lobes are separated and larger

##### Params

- center as map(xs:string,item()*): center point of curve
- radius as xs:double: scaling of curve
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:trefoil( $center as map(xs:string,item()*), $radius as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*(math:sin($t) + 2*math:sin(2*$t)) + $δx }, function ($t as xs:double) as xs:double { $radius*(math:cos($t) - 2*math:cos(2*$t)) + $δy }, function ($t as xs:double) as xs:double { $radius*(-math:sin(3*$t)) + $δz }, 0, 2*math:pi() ) ) }

####
__Function__: maurer-rose

declare function maurer-rose($center as map(xs:string,item()*),
$radius as xs:double,
$n as xs:integer,
$d as xs:integer,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: maurer-rose

declare function maurer-rose($center as map(xs:string,item()*), $radius as xs:double, $n as xs:integer, $d as xs:integer, $num-points as xs:integer) as map(xs:string,item()*)*

maurer-rose()

Construct a Maurer rose.

(see https://en.wikipedia.org/wiki/Maurer_rose)

##### Params

- center as map(xs:string,item()*): center point of rose
- radius as xs:double: scaling of rose
- n as xs:integer: rose parameterum-points: number of points to draw for each line set Since the rose if formed by cross-cutting lines; a small n is more noticeable in a way the other curves are not; splining doesn't help num-points=360 (i.e. one per degree) gives a filled out appearance
- d as xs:integer: rose parameter Generally n=small; d=order of magnitude larger; relatively prime
- num-points as xs:integer: number of points to draw for each line set Since the rose if formed by cross-cutting lines; a small n is more noticeable in a way the other curves are not; splining doesn't help num-points=360 (i.e. one per degree) gives a filled out appearance

##### Returns

- map(xs:string,item()*)*

declare function this:maurer-rose( $center as map(xs:string,item()*), $radius as xs:double, $n as xs:integer, $d as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { let $k := $t * $d return $radius * math:sin($n * $k) * math:cos($k) + $δx }, function ($t as xs:double) as xs:double { let $k := $t * $d return $radius * math:sin($n * $k) * math:sin($k) + $δy }, 0, 2*math:pi() ) ) }

####
__Function__: granny-knot

declare function granny-knot($center as map(xs:string,item()*),
$radius as xs:double,
$num-points as xs:integer) as map(xs:string,item()*)*

__Function__: granny-knot

declare function granny-knot($center as map(xs:string,item()*), $radius as xs:double, $num-points as xs:integer) as map(xs:string,item()*)*

granny-knot()

Construct a granny knot.

##### Params

- center as map(xs:string,item()*): center point of knot
- radius as xs:double: scaling of knot
- num-points as xs:integer: number of points to draw

##### Returns

- map(xs:string,item()*)*

declare function this:granny-knot( $center as map(xs:string,item()*), $radius as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { let $scaled-radius := $radius div 100 let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $scaled-radius*(-22*math:cos($t) - 128*math:sin($t) - 44*math:cos(3*$t) - 78*math:sin(3*$t)) + $δx }, function ($t as xs:double) as xs:double { $scaled-radius*(-10*math:cos(2*$t) - 27*math:sin(2*$t) + 38*math:cos(4*$t) + 46*math:sin(4*$t)) + $δy }, function ($t as xs:double) as xs:double { $scaled-radius*(70*math:cos(3*$t) - 40*math:sin(3*$t)) + $δz }, 0, 2*math:pi() ) ) }

####
__Function__: harmonograph

declare function harmonograph($center as map(xs:string,item()*),
$radius as xs:double,
$harmonograph as map(xs:string,item()*),
$density as xs:integer,
$extent as xs:double) as map(xs:string,item()*)*

__Function__: harmonograph

declare function harmonograph($center as map(xs:string,item()*), $radius as xs:double, $harmonograph as map(xs:string,item()*), $density as xs:integer, $extent as xs:double) as map(xs:string,item()*)*

harmonograph()

Draw the path of a harmonograph (multi-axis multi-pendulum system)

##### Params

- center as map(xs:string,item()*): Center of graph
- radius as xs:double: Basic scaling of graph
- harmonograph as map(xs:string,item()*): Harmonograph descriptor (see pend_t:harmonograph())
- density as xs:integer: Parameter to adjust fineness of the time ticks (t=i/density)
- extent as xs:double: Basic extent of drawing Final number of ticks (and therefore points) is num-points*density

##### Returns

- map(xs:string,item()*)*

declare function this:harmonograph( $center as map(xs:string,item()*), $radius as xs:double, $harmonograph as map(xs:string,item()*), $density as xs:integer, $extent as xs:double ) as map(xs:string,item()*)* { let $num-x-pendulums := pend_t:num-pendulums(pend_t:x($harmonograph)) let $x-pendulums := pend_t:x($harmonograph)=>pend_t:pendulums() let $num-y-pendulums := pend_t:num-pendulums(pend_t:y($harmonograph)) let $y-pendulums := pend_t:y($harmonograph)=>pend_t:pendulums() let $num-z-pendulums := pend_t:num-pendulums(pend_t:z($harmonograph)) let $z-pendulums := pend_t:z($harmonograph)=>pend_t:pendulums() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( util:round($extent * $density), function ($t as xs:double) as xs:double { $radius*sum( $x-pendulums!pend_t:damped-pendulum($t, .) ) + $δx }, function ($t as xs:double) as xs:double { $radius*sum( $y-pendulums!pend_t:damped-pendulum($t, .) ) + $δy }, function ($t as xs:double) as xs:double { $radius*sum( $z-pendulums!pend_t:damped-pendulum($t, .) ) + $δz }, 1 div $density, $extent ) ) (: for $i in 1 to $extent*$density let $t := $i div $density let $x := $radius*sum( $x-pendulums!pend_t:damped-pendulum($t, .) ) + point:px($center) let $y := $radius*sum( $y-pendulums!pend_t:damped-pendulum($t, .) ) + point:py($center) let $z := $radius*sum( $z-pendulums!pend_t:damped-pendulum($t, .) ) + point:py($center) return point:point($x, $y, $z) :) }

####
__Function__: wiggle

declare function wiggle($start as map(xs:string,item()*),
$length as xs:double,
$wiggle as map(xs:string,item()*)) as map(xs:string,item()*)*

__Function__: wiggle

declare function wiggle($start as map(xs:string,item()*), $length as xs:double, $wiggle as map(xs:string,item()*)) as map(xs:string,item()*)*

wiggle()

Draw the path of a knot wiggle. Number of points will depend on wiggle

smoothness

##### Params

- start as map(xs:string,item()*): Starting point
- length as xs:double: Length
- wiggle as map(xs:string,item()*): Wiggle descriptor

##### Returns

- map(xs:string,item()*)*

declare function this:wiggle( $start as map(xs:string,item()*), $length as xs:double, $wiggle as map(xs:string,item()*) ) as map(xs:string,item()*)* { let $smoothness := $length * ($wiggle=>wiggle_t:smoothness()) return ( curve:parametric( util:round($length), function ($t as xs:double) as xs:double { $t }, function ($t as xs:double) as xs:double { let $θ := ($t * $length) div (2 * math:pi() * $smoothness) return point:y($start) + $wiggle=>wiggle_t:value($θ, $length) }, point:x($start), point:x($start) + util:round($length) ) ) }

####
__Function__: golden-spiral

declare function golden-spiral($num-points as xs:integer,
$n as xs:integer,
$scale as xs:double) as map(xs:string,item()*)*

__Function__: golden-spiral

declare function golden-spiral($num-points as xs:integer, $n as xs:integer, $scale as xs:double) as map(xs:string,item()*)*

golden-spiral()

Create a sequence of points following a (Fibonacci approximation of) a golden spiral

##### Params

- num-points as xs:integer: Number of points in each (90 degree) arc of spiral
- n as xs:integerum-points: Number of points in each (90 degree) arc of spiral: Number of Fibonacci generations
- scale as xs:double: Scale of the spiral (need to do at this level to avoid jaggies)

##### Returns

- map(xs:string,item()*)*

declare function this:golden-spiral( $num-points as xs:integer, $n as xs:integer, $scale as xs:double ) as map(xs:string,item()*)* { let $fibs := seq:fibonacci-sequence($n) let $radii := for $i in 1 to $n return if ($i=1) then $scale else $scale*$fibs[$i - 1] let $angles := for $i in 1 to $n return switch ($i mod 4) case 0 return 90 case 1 return 360 case 2 return 270 case 3 return 180 default return () let $centers := fold-left(1 to $n, point:point(0,0), function ($points as map(xs:string,item()*)*, $i as xs:integer) as map(xs:string,item()*)* { $points, let $center := $points[last()] let $last := if ($i = 1) then point:point(point:x($center), point:y($center) - $scale) else geom:destination($center, $angles[$i - 1] - 90, $radii[$i - 1]) return switch ($i mod 4) case 0 return point:point(point:x($center), point:y($last) - $radii[$i]) case 1 return point:point(point:x($last) - $radii[$i], point:y($center)) case 2 return point:point(point:x($center), point:y($last) + $radii[$i]) case 3 return point:point(point:x($last) + $radii[$i], point:y($center)) default return () } )[position() >= 2] for $i in 1 to $n return this:reverse-arc($centers[$i], $radii[$i], 90, $angles[$i], $num-points) }

####
__Function__: angle-spiral

declare function angle-spiral($arc as xs:double,
$center as map(xs:string,item()*),
$radius as xs:double,
$loops as xs:integer,
$spread as xs:double) as map(xs:string,item()*)*

__Function__: angle-spiral

declare function angle-spiral($arc as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double) as map(xs:string,item()*)*

angle-spiral()

Create a path following a simple uniform spiral where the points are a

consistent number of degrees apart

##### Params

- arc as xs:double: degrees of arc between each point
- center as map(xs:string,item()*): center of spiral
- radius as xs:double: distance from center to first point
- loops as xs:integer: how many windings of spiral
- spread as xs:double: how far about windings are

##### Returns

- map(xs:string,item()*)*

declare function this:angle-spiral( $arc as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double ) as map(xs:string,item()*)* { let $num-points := 360 idiv $arc return fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) let $loop-radius := $radius + ($loop - 1)*$spread return ( (: new angle :) util:remap-degrees($last-angle + $num-points * $arc), (: points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $loop-radius + $t * $spread div $num-points }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }

####
__Function__: point-spiral

declare function point-spiral($num-points as xs:integer,
$center as map(xs:string,item()*),
$radius as xs:double,
$loops as xs:integer,
$spread as xs:double) as map(xs:string,item()*)*

__Function__: point-spiral

declare function point-spiral($num-points as xs:integer, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double) as map(xs:string,item()*)*

point-spiral()

Create a path following a simple uniform spiral where there are the

same number of points in each loop

##### Params

- num-points as xs:integer: number of points per loop
- center as map(xs:string,item()*): center of spiral
- radius as xs:double: distance from center to first point
- loops as xs:integer: how many windings of spiral
- spread as xs:double: how far about windings are

##### Returns

- map(xs:string,item()*)*

declare function this:point-spiral( $num-points as xs:integer, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double ) as map(xs:string,item()*)* { let $arc := 360 div $num-points return fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) let $loop-radius := $radius + ($loop - 1)*$spread return ( (: Next angle :) util:remap-degrees($last-angle + $num-points * $arc), (: Points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $loop-radius + $t * $spread div $num-points }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }

####
__Function__: uniform-spiral

declare function uniform-spiral($distance as xs:double,
$center as map(xs:string,item()*),
$radius as xs:double,
$loops as xs:integer,
$spread as xs:double) as map(xs:string,item()*)*

__Function__: uniform-spiral

declare function uniform-spiral($distance as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double) as map(xs:string,item()*)*

uniform-spiral()

Create a path following a simple uniform spiral where there is about

the same distance between each point.

##### Params

- distance as xs:double: distance between points
- center as map(xs:string,item()*): center of spiral
- radius as xs:double: distance from center to first point
- loops as xs:integer: how many windings of spiral
- spread as xs:double: how far apart windings are

##### Returns

- map(xs:string,item()*)*

declare function this:uniform-spiral( $distance as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double ) as map(xs:string,item()*)* { fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) let $loop-radius := $radius + ($loop - 1)*$spread let $avg-loop-radius := avg(($loop-radius, $radius + $loop*$spread)) let $num-points := 2*math:pi()*$avg-loop-radius idiv $distance let $arc := 360 div $num-points return ( (: Next angle :) util:remap-degrees($last-angle + $num-points * $arc), (: Points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $loop-radius + $t * $spread div $num-points }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }

####
__Function__: spiral-function

declare function spiral-function($kind as xs:string, $spread as xs:double) as function(xs:integer, xs:integer, xs:integer) as xs:double

__Function__: spiral-function

declare function spiral-function($kind as xs:string, $spread as xs:double) as function(xs:integer, xs:integer, xs:integer) as xs:double

spiral-function()

Return a function that can be used with function-spiral() to create a

spiral of a particular kind.

##### Params

- kind as xs:string: kind of spread function uniform: spiral expands uniformly (equivalent to arc-spiral()) accelerating: spiral expands in an accelerating way on each turn nautiloid: spirals pinch off at end to give nautilus like appearance wobbly: spiral veers back and forth in a wave (use higher $arc) very-wobbly: spiral veers even more
- spread as xs:double: scaling factor on the expansion

##### Returns

- function(xs:integer,xs:integer,xs:integer)asxs:double

declare function this:spiral-function($kind as xs:string, $spread as xs:double) as function(xs:integer, xs:integer, xs:integer) as xs:double { switch($kind) case "uniform" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { xs:double(($loop - 1)*$spread) + ($point*$spread div $num-points) } case "accelerating" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop * $num-points + $point) * ($loop * $num-points + $point) * $spread div ($num-points * $num-points) } case "wobbly" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop - 1)*$spread + math:pow(-1,$point) * $point * $spread div $num-points } case "very-wobbly" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop - 1)*$spread + 2*math:pow(-1,$point) * $point * $spread div $num-points } case "nautiloid" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop * $num-points + $point) * (($loop * $num-points + $point) mod $num-points) * $spread div ($num-points * $num-points) } default return errors:error("ML-BADARGS", ("kind", $kind)) }

####
__Function__: function-spiral

declare function function-spiral($arc as xs:double,
$center as map(xs:string,item()*),
$radius as xs:double,
$loops as xs:integer,
$spread as function((:loop:)xs:integer,(:point:)xs:integer,(:num-points:)xs:integer) as xs:double) as map(xs:string,item()*)*

__Function__: function-spiral

declare function function-spiral($arc as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as function((:loop:)xs:integer,(:point:)xs:integer,(:num-points:)xs:integer) as xs:double) as map(xs:string,item()*)*

function-spiral()

Create an arc spiral using a function to compute the distance for

each point in the loop.

##### Params

- arc as xs:double: degrees of arc between each point
- center as map(xs:string,item()*): center of spiral
- radius as xs:double: distance from center to first point
- loops as xs:integer: how many windings of spiral
- spread as function(xs:integer,xs:integer,xs:integer)asxs:double: function that is added to base radius to compute the distance

##### Returns

- map(xs:string,item()*)*

declare function this:function-spiral( $arc as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as function((:loop:)xs:integer,(:point:)xs:integer,(:num-points:)xs:integer) as xs:double ) as map(xs:string,item()*)* { let $num-points := 360 idiv $arc return fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) return ( (: Next angle :) util:remap-degrees($last-angle + $num-points * $arc), (: Points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $radius + $spread($loop, xs:integer($t), $num-points) }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }

####
__Function__: fern-spiral

declare function fern-spiral($start as map(xs:string,item()*),
$fern as map(xs:string,item()*),
$generations as xs:integer) as map(xs:string,item()*)*

__Function__: fern-spiral

declare function fern-spiral($start as map(xs:string,item()*), $fern as map(xs:string,item()*), $generations as xs:integer) as map(xs:string,item()*)*

fern-spiral()

Make a fern spiral

Fern spirals are created as a series of segments, where each segment

shrinks from the previous and decreases the lean by curl*curvature

curl and curvature are handled separately by branching and blade

creation

Overall path length is Σ[j=0,n-1]d*r^j = (d - rl)/(1 - r) where l=ar^(n-1)

in the limit (j=0,∞) = a/(1-r)

height will be less because of angle

##### Params

- start as map(xs:string,item()*): starting point for spiral
- fern as map(xs:string,item()*): parameter bundle defining curve, create with fern_t:fern() left: curve to the left; default=false scale: size of initial segment; default=100 lean: amount of initial lean (degrees); default=70 shrinkage: how much to shrink each segment; default=0.7 curvature: how much to change the angle (degrees); default=90 curl: tightness of curl; default=0.2
- generations as xs:integer: number of segments in spiral

##### Returns

- map(xs:string,item()*)*

declare function this:fern-spiral( $start as map(xs:string,item()*), $fern as map(xs:string,item()*), $generations as xs:integer ) as map(xs:string,item()*)* { let $is-left := fern_t:is-left($fern) let $scale := fern_t:scale($fern) let $lean := fern_t:lean($fern) let $shrinkage := fern_t:shrinkage($fern) let $curvature := fern_t:curvature($fern) let $curl := fern_t:curl($fern) return ( curve:dependent($generations, $start, function ($last as map(xs:string,item()*), $i as xs:integer) as map(xs:string,item()*) { let $d := $scale*math:pow($shrinkage,($i - 1)) let $a := if ($is-left) then util:remap-degrees($lean + ($i - 1)*($curvature*$curl)) else util:remap-degrees($lean - ($i - 1)*($curvature*$curl)) return geom:destination($last, -$a, $d) } ) ) }

####
__Function__: eulers

declare function eulers($center as map(xs:string,item()*),
$scaling as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: eulers

declare function eulers($center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

eulers()

Euler's spiral/clothoid

x = k * ∫[0:t]( sin(u²/2)du )

y = k * ∫[0:t]( cos(u²/2)du )

k is scaling; t as some number of multiples of π

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: k parameter above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs 0 to 2π; gives double spirals

##### Returns

- map(xs:string,item()*)*

declare function this:eulers( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*2*math:pi()) let $xs := let $f := function ($u as xs:double) as xs:double {math:sin($u*$u div 2)} return fold-left(2 to count($ts), $scaling * util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + $scaling * util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double {math:cos($u*$u div 2)} return fold-left(2 to count($ts), $scaling * util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + $scaling * util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) for $x at $i in $xs return point:point($x + $δx, $ys[$i] + $δy) }

####
__Function__: polynomial

declare function polynomial($center as map(xs:string,item()*),
$scaling as xs:double,
$polynomial as map(xs:string,item()*),
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: polynomial

declare function polynomial($center as map(xs:string,item()*), $scaling as xs:double, $polynomial as map(xs:string,item()*), $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

polynomial()

Polynomial spiral: dφ/ds = Σ[n=1:n](ai*s^i)

s=arc length

dφ/ds=1 => circle

dφ/ds=s => Cornu spiral

dφ/ds=s² => double clothoid

dφ/ds=s² + 1 => like Corinthian capital

dφ/ds=s² - 4 => intersecting double spiral vase shape

s = Σ[j=0:J] (a[j]/(j+1))θ^(j+1)

https://www.atlantis-press.com/journals/gaf/125935071/view

κ(s) = Pκ'(s)

P is polynomial in s

x = ∫[t=0:s]sin(Pk(t))

y = ∫[t=0:s]cos(Pk(t))

k=0 => line, k=1 => circle, k=2 => Cornu spiral, k=3 => where the fun starts

k=3 cont. κ = s² - D => 2pts inflection for D>0, 1 for D=0, none for D<0

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: overall scaling
- polynomial as map(xs:string,item()*): the polynomial (see types/polynomial)
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range; >1 tends to get wonky
- symmetric as xs:boolean: use a symmetric extent around 0 vs 0 to 2π; gives double spirals

##### Returns

- map(xs:string,item()*)*

declare function this:polynomial( $center as map(xs:string,item()*), $scaling as xs:double, $polynomial as map(xs:string,item()*), $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($num-points < 1) then () else let $δx := point:px($center) let $δy := point:py($center) let $pk := polynomial:antiderivative($polynomial) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*math:pi()) let $xs := let $f := function ($u as xs:double) as xs:double { $scaling * math:sin($pk=>polynomial:value($u)) } return fold-left(2 to count($ts), util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double { $scaling * math:cos($pk=>polynomial:value($u)) } return fold-left(2 to count($ts),util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $pts := for $x at $i in $xs return point:point($x, $ys[$i]) let $spiral-center := box:box( min($pts!point:px(.)), min($pts!point:py(.)), max($pts!point:px(.)), max($pts!point:py(.)) )=>box:center() let $delta := $center=>point:sub($spiral-center) return $pts!point:add(., $delta) }

####
__Function__: meander

declare function meander($center as map(xs:string,item()*),
$scaling as xs:double,
$amplitudes as xs:double*,
$wavelengths as xs:double*,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: meander

declare function meander($center as map(xs:string,item()*), $scaling as xs:double, $amplitudes as xs:double*, $wavelengths as xs:double*, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

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

##### Params

- center as map(xs:string,item()*): center of meander
- scaling as xs:double: overall scaling
- amplitudes as xs:double*: vector of amplitude values (A[i]); will use last A[i] if wavelength vector is longer
- wavelengths as xs:double*: vector of wavelength values (λ[i])
- num-points as xs:integer: number of points to plot
- extent as xs:double: extent of curve (multiple of π)
- symmetric as xs:boolean: symmetric extent around 0

##### Returns

- map(xs:string,item()*)*

declare function this:meander( $center as map(xs:string,item()*), $scaling as xs:double, $amplitudes as xs:double*, $wavelengths as xs:double*, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($num-points < 1) then () else let $n := count($wavelengths) let $δx := point:px($center) let $δy := point:py($center) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*math:pi()) let $invλs := $wavelengths!(2*math:pi() div .) let $xs := let $f := function ($u as xs:double) as xs:double { $scaling * math:cos( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * math:sin( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts), util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double { $scaling * math:sin( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * math:sin( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts),util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $pts := for $x at $i in $xs return point:point($x, $ys[$i]) let $wave-center := box:box( min($pts!point:px(.)), min($pts!point:py(.)), max($pts!point:px(.)), max($pts!point:py(.)) )=>box:center() let $delta := $center=>point:sub($wave-center) return $pts!point:add(., $delta) }

####
__Function__: looping-meander

declare function looping-meander($center as map(xs:string,item()*),
$scaling as xs:double,
$amplitudes as xs:double*,
$wavelengths as xs:double*,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: looping-meander

declare function looping-meander($center as map(xs:string,item()*), $scaling as xs:double, $amplitudes as xs:double*, $wavelengths as xs:double*, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

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 )

##### Params

- center as map(xs:string,item()*): center of meander
- scaling as xs:double: overall scaling
- amplitudes as xs:double*: vector of amplitude values (A[i]); will use last A[i] if wavelength vector is longer
- wavelengths as xs:double*: vector of wavelength values (λ[i])
- num-points as xs:integer: number of points to plot
- extent as xs:double: extent of curve (multiple of π)
- symmetric as xs:boolean: symmetric extent around 0

##### Returns

- map(xs:string,item()*)*

declare function this:looping-meander( $center as map(xs:string,item()*), $scaling as xs:double, $amplitudes as xs:double*, $wavelengths as xs:double*, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($num-points < 1) then () else let $n := count($wavelengths) let $δx := point:px($center) let $δy := point:py($center) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*math:pi()) let $invλs := $wavelengths!(2*math:pi() div .) let $xs := let $f := function ($u as xs:double) as xs:double { $scaling * math:cos( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * $invλs[$i] * math:cos( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts), util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double { $scaling * math:sin( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * $invλs[$i] * math:cos( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts),util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $pts := for $x at $i in $xs return point:point($x, $ys[$i]) let $wave-center := box:box( min($pts!point:px(.)), min($pts!point:py(.)), max($pts!point:px(.)), max($pts!point:py(.)) )=>box:center() let $delta := $center=>point:sub($wave-center) return $pts!point:add(., $delta) }

####
__Function__: cotes

declare function cotes($kind as xs:string,
$center as map(xs:string,item()*),
$scaling as xs:double,
$A as xs:double,
$k as xs:double,
$ε as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: cotes

declare function cotes($kind as xs:string, $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

cotes()

Cotes' spirals

A > 0, k > 0, ε real constants A=>size, k=>shape, ε=>angular position

1/r = A cosh(kθ + ε) case 1 Poinsot's

1/r = A exp(kθ + ε) case 2 Equiangular

1/r = A sinh(kθ + ε) case 3 Poinsot's

1/r = A (kθ + ε) case 4 Hyperbolic/reciprocal

1/r = A cos(kθ + ε) case 5 Epispiral

##### Params

- kind as xs:string: what kind (one of poinsot-c, pointsot-s, equiangular, reciprocal, epispiral)
- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- A as xs:double: A parameter, above; for most 1.0 is fine; epispirals need small fraction A
- k as xs:doubleind: what kind (one of poinsot-c, pointsot-s, equiangular, reciprocal, epispiral): k parameter, above; for most 0.5 is fine; epispirals better with smaller k
- ε as xs:double: ε parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs 0 to 2π; gives cardiod shapes

##### Returns

- map(xs:string,item()*)*

declare function this:cotes( $kind as xs:string, $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($A <= 0) then errors:error("ML-BADARGS", ("A", $A)) else (), if ($k <= 0) then errors:error("ML-BADARGS", ("k", $k)) else (), let $r := switch($kind) case "poinsot-c" return function ($t as xs:double) as xs:double { $scaling div ($A * util:cosh($k * $t + $ε)) } case "poinsot-s" return function ($t as xs:double) as xs:double { $scaling div ($A * util:sinh($k * $t + $ε)) } case "equiangular" return function ($t as xs:double) as xs:double { $scaling div ($A * math:exp($k * $t + $ε)) } case "reciprocal" return function ($t as xs:double) as xs:double { $scaling div ($A * ($k * $t + $ε)) } case "epispiral" return function ($t as xs:double) as xs:double { 1 div ($A * math:cos($k * $t + $ε)) } default return errors:error("ML-BADARGS", ("kind", $kind)) let $θ := function ($t as xs:double) as xs:double {$t} return ( (: point:valid() filters out NaNs and INFs :) if ($symmetric) then ( curve:polar($num-points, $center, $r, $θ, -$extent*math:pi(), $extent*math:pi())[point:valid(.)] ) else ( curve:polar($num-points, $center, $r, $θ, 0, 2*$extent*math:pi())[point:valid(.)] ) ) }

####
__Function__: poinsot-s

declare function poinsot-s($center as map(xs:string,item()*),
$scaling as xs:double,
$A as xs:double,
$k as xs:double,
$ε as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: poinsot-s

declare function poinsot-s($center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

poinsot-s()

Case 3 Cotes' spiral: 1/r = A sinh(kθ + ε)

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- A as xs:double: A parameter, above; for most 1.0 is fine; epispirals need small fraction A
- k as xs:double: k parameter, above; for most 0.5 is fine; epispirals better with smaller k
- ε as xs:double: ε parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:poinsot-s( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("poinsot-s", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }

####
__Function__: poinsot-c

declare function poinsot-c($center as map(xs:string,item()*),
$scaling as xs:double,
$A as xs:double,
$k as xs:double,
$ε as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: poinsot-c

declare function poinsot-c($center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

poinsot-c()

Case 1 Cotes' spiral: 1/r = A cosh(kθ + ε)

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- A as xs:double: A parameter, above; for most 1.0 is fine; epispirals need small fraction A
- k as xs:double: k parameter, above; for most 0.5 is fine; epispirals better with smaller k
- ε as xs:double: ε parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:poinsot-c( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("poinsot-c", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }

####
__Function__: equiangular

declare function equiangular($center as map(xs:string,item()*),
$scaling as xs:double,
$A as xs:double,
$k as xs:double,
$ε as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: equiangular

declare function equiangular($center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

equiangular()

Case 2 Cotes' spiral: 1/r = A exp(kθ + ε)

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- A as xs:double: A parameter, above; for most 1.0 is fine; epispirals need small fraction A
- k as xs:double: k parameter, above; for most 0.5 is fine; epispirals better with smaller k
- ε as xs:double: ε parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:equiangular( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("equiangular", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }

####
__Function__: reciprocal

declare function reciprocal($center as map(xs:string,item()*),
$scaling as xs:double,
$A as xs:double,
$k as xs:double,
$ε as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: reciprocal

declare function reciprocal($center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

reciprocal()

Case 4 Cotes' spiral: 1/r = A (kθ + ε)

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- A as xs:double: A parameter, above; for most 1.0 is fine; epispirals need small fraction A
- k as xs:double: k parameter, above; for most 0.5 is fine; epispirals better with smaller k
- ε as xs:double: ε parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:reciprocal( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("reciprocal", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }

####
__Function__: epispiral

declare function epispiral($center as map(xs:string,item()*),
$scaling as xs:double,
$A as xs:double,
$k as xs:double,
$ε as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: epispiral

declare function epispiral($center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

epispiral()

Case 5 Cotes' spiral: 1/r = A cos(kθ + ε)

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- A as xs:double: A parameter, above; for most 1.0 is fine; epispirals need small fraction A
- k as xs:double: k parameter, above; for most 0.5 is fine; epispirals better with smaller k
- ε as xs:double: ε parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:epispiral( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("epispiral", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }

####
__Function__: cochleoid

declare function cochleoid($center as map(xs:string,item()*),
$scaling as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: cochleoid

declare function cochleoid($center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

cochleoid()

Cochleoid spiral: r = sinφ/φ

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:cochleoid( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { if ($t = 0) then $scaling else $scaling * math:sin($t) div $t }, $min, $max ) ) }

####
__Function__: atom

declare function atom($center as map(xs:string,item()*),
$scaling as xs:double,
$a as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: atom

declare function atom($center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

atom()

Atom spiral: r=φ/(φ - a)

Spirals in then kicks out a>=1 for a < 1 more of a circle in/out

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (basic size is generally in unit square)
- a as xs:double: a parameter above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:atom( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { if ($t - $a = 0) then util:sign($scaling * $t) * xs:double("INF") else $scaling * $t div ($t - $a) }, $min, $max )[point:valid(.)] ) }

####
__Function__: circle-involute

declare function circle-involute($center as map(xs:string,item()*),
$scaling as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: circle-involute

declare function circle-involute($center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

circle-involute()

Involute of circle: r²=φ² + 1

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:circle-involute( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($symmetric) then ( curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { -$scaling * math:sqrt($t*$t + 1) }, -$extent * math:pi(), 0 ), curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { $scaling * math:sqrt($t*$t + 1) }, $extent * math:pi() div ($num-points idiv 2), $extent * math:pi() ) ) else ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { $scaling * math:sqrt($t*$t + 1) }, 0, 2 * $extent * math:pi() ) ) }

####
__Function__: lituus

declare function lituus($center as map(xs:string,item()*),
$scaling as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: lituus

declare function lituus($center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

lituus()

Lituus: r²θ = k => r = √(k/θ)

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:lituus( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($symmetric) then ( curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { if ($t = 0) then xs:double("-INF") else -math:sqrt($scaling div abs($t)) }, -$extent * math:pi(), 0 ), curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { if ($t = 0) then xs:double("INF") else math:sqrt($scaling div $t) }, $extent * math:pi() div ($num-points idiv 2), $extent * math:pi() ) ) else ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { if ($t = 0) then xs:double("INF") else math:sqrt($scaling div $t) }, 0, 2 * $extent * math:pi() ) )[point:valid(.)] }

####
__Function__: gielis

declare function gielis($center as map(xs:string,item()*),
$scaling as xs:double,
$a as xs:double,
$b as xs:double,
$c as xs:double,
$d as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: gielis

declare function gielis($center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

gielis()

Gielis curve = super ellipse

r = (|cos(dφ)|^a | + |sin(dφ)|^b)^c

a=10, b=10, c=-2/9, d=3/4: has the shape of the petiole (leaf stem) of the nuphar
luteum

a=15, b=15, c=-1/12, d=1: the shape of the cross section of the stem of the herb
called scrophularia nodosa

a=1, b=1, c=-1/4, d=5/4: idem, of the equisetum

a=6, b=6, c=-1/10, d=7/4, idem, of the raspberry

##### Params

- center as map(xs:string,item()*): center of ellipse
- scaling as xs:double: scaling of ellipse
- a as xs:double: a parameter above
- b as xs:double: b parameter above; a vs b gives relative flatness
- c as xs:doubleenter: center of ellipse: c parameter above
- d as xs:double: d parameter above => number of symmetries is 4d
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:gielis( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = (|cos(dφ)|^a | + |sin(dφ)|^b)^c :) $scaling * math:pow( math:pow(abs(math:cos($d*$t)), $a) + math:pow(abs(math:sin($d*$t)), $b), $c ) }, $min, $max ) ) }

####
__Function__: super-rose

declare function super-rose($center as map(xs:string,item()*),
$scaling as xs:double,
$a as xs:double,
$b as xs:double,
$c as xs:double,
$d as xs:double,
$f as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: super-rose

declare function super-rose($center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $f as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

super-rose()

Super rose = extension of rose, generalization of Gielis curve

r = sin(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c

a=1 b=1 c=-1 d=3 f=5/8 => spiky rose

##### Params

- center as map(xs:string,item()*): center of rose
- scaling as xs:double: scaling of rose
- a as xs:double: a parameter above
- b as xs:double: b parameter above; a vs b gives relative flatness
- c as xs:doubleenter: center of rose: c parameter above
- d as xs:double: d parameter above
- f as xs:double: f parameter above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:super-rose( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $f as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = sin(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c :) $scaling * math:sin($f*$t) * math:pow( math:pow(abs(math:cos($d*$t)), $a) + math:pow(abs(math:sin($d*$t)), $b), $c ) }, $min, $max ) ) }

####
__Function__: super-spiral

declare function super-spiral($center as map(xs:string,item()*),
$scaling as xs:double,
$a as xs:double,
$b as xs:double,
$c as xs:double,
$d as xs:double,
$f as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: super-spiral

declare function super-spiral($center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $f as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

super-spiral()

Super spiral = generalization of Gielis curve

r = e^(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c

Some cases:

a=b=5, c=-1, d=1, f=1/3; starish wide loops

a=b=5, c=-1/5, d=5/2, f=1/5; wobbly

a=b=100, c=-0.01, d=1, f=1/5; almost square

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral
- a as xs:double: a parameter above
- b as xs:double: b parameter above; a vs b gives relative flatness
- c as xs:doubleenter: center of spiral: c parameter above
- d as xs:double: d parameter above => number of symmetries is 4d
- f as xs:double: f parameter above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:super-spiral( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $f as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = e^(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c :) $scaling * math:exp($f*$t) * math:pow( math:pow(abs(math:cos($d*$t)), $a) + math:pow(abs(math:sin($d*$t)), $b), $c ) }, $min, $max ) ) }

####
__Function__: logarithmic

declare function logarithmic($center as map(xs:string,item()*),
$scaling as xs:double,
$a as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: logarithmic

declare function logarithmic($center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

logarithmic()

Logarithmic spiral: r = e^(aφ)

Examples a[0.05:0.25] smaller a, closer windings, further from center start

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral
- a as xs:double: a parameter above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:logarithmic( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = e^(aφ) :) $scaling * math:exp($a*$t) }, $min, $max ) ) }

####
__Function__: fermat

declare function fermat($center as map(xs:string,item()*),
$scaling as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: fermat

declare function fermat($center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

fermat()

Fermat's spiral: r^2 = φ => Archimedes but two joined outflows

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:fermat( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($symmetric) then ( curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { (: r = √φ :) -$scaling * math:sqrt(abs($t)) }, -$extent * math:pi(), 0 ), curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { (: r = √φ :) math:sqrt($scaling div $t) }, $extent * math:pi() div ($num-points idiv 2), $extent * math:pi() ) ) else ( (: Positive branch only = Archimedes :) curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = √φ :) math:sqrt($scaling div $t) }, 0, 2 * $extent * math:pi() ) ) }

####
__Function__: tractrix

declare function tractrix($center as map(xs:string,item()*),
$scaling as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: tractrix

declare function tractrix($center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

tractrix()

Tractrix: r = A cos(t), θ = tan(t) - t

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (A parameter)
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:tractrix( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = A cos(t) :) $scaling * math:cos($t) }, function ($t as xs:double) as xs:double { (: θ = tan(t) - t :) math:tan($t) - $t }, $min, $max ) ) }

####
__Function__: doppler

declare function doppler($center as map(xs:string,item()*),
$scaling as xs:double,
$k as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: doppler

declare function doppler($center as map(xs:string,item()*), $scaling as xs:double, $k as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

doppler()

Doppler: x = a(t cos(t) + kt), y = a t sin(t)

Like nested circles pinned to one side, but spirals

0 < k < 1 two spirals intersecting (for - and + angles)

k = 1 two spirals tangent (for - and + angles)

k > 1 two spirals increasing away from center

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (a parameter, above)
- k as xs:double: k parameter, above
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:doppler( $center as map(xs:string,item()*), $scaling as xs:double, $k as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() let $δx := point:px($center) let $δy := point:py($center) return ( curve:parametric($num-points, function ($t as xs:double) as xs:double { (: x = a(t cos(t) + kt) :) $scaling * ($t * math:cos($t) + $k*$t) + $δx }, function ($t as xs:double) as xs:double { (: y = a(t sin(t)) :) $scaling * ($t * math:sin($t)) + $δy }, $min, $max ) ) }

####
__Function__: conchospiral

declare function conchospiral($center as map(xs:string,item()*),
$scaling as xs:double,
$μ as xs:double,
$c as xs:double,
$num-points as xs:integer,
$extent as xs:double,
$symmetric as xs:boolean) as map(xs:string,item()*)*

__Function__: conchospiral

declare function conchospiral($center as map(xs:string,item()*), $scaling as xs:double, $μ as xs:double, $c as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean) as map(xs:string,item()*)*

conchospiral()

Conchospiral (3d): r = μ^t a, θ = t, z = μ^t c

μ=>opening angle, c=>slope of cone; example μ=1.07,a=1,c=1.1

##### Params

- center as map(xs:string,item()*): center of spiral
- scaling as xs:double: scaling of spiral (a parameter, above)
- μ as xs:double: opening angle (μ parameter, above)
- c as xs:doubleenter: center of spiral: slope of cone (c parameter, above)
- num-points as xs:integer: number of points to produce
- extent as xs:double: number of multiples of π in angular range, extent => number of turns
- symmetric as xs:boolean: use a symmetric extent around 0 vs extent from 0 to 2π

##### Returns

- map(xs:string,item()*)*

declare function this:conchospiral( $center as map(xs:string,item()*), $scaling as xs:double, $μ as xs:double, $c as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = μ^t a :) $scaling * math:pow($μ, $t) }, function ($t as xs:double) as xs:double {$t}, (: θ = t :) function ($t as xs:double) as xs:double { (: z = μ^t c :) $c * math:pow($μ, $t) }, $min, $max ) ) }

####
__Function__: lamé

declare function lamé($num-points as xs:integer,
$a as xs:double,
$b as xs:double,
$α as xs:double) as map(xs:string,item()*)*

__Function__: lamé

declare function lamé($num-points as xs:integer, $a as xs:double, $b as xs:double, $α as xs:double) as map(xs:string,item()*)*

lamé()

Lamé curves: x = ±a cos(t)^(2/α); y = ±b cos(t)^(2/α);

α > 2 => super-ellipse

##### Params

- num-points as xs:integer: number of points to draw
- a as xs:double: scaling of x (a parameter above)
- b as xs:double: scaling of y (b parameter above)
- α as xs:double: α parameter above

##### Returns

- map(xs:string,item()*)*

declare function this:lamé( $num-points as xs:integer, $a as xs:double, $b as xs:double, $α as xs:double ) as map(xs:string,item()*)* { curve:parametric($num-points, function ($t as xs:double) as xs:double { if ($t < 0) then -$a * math:pow(math:cos($t), 2 div $α) else $a * math:pow(math:cos($t), 2 div $α) }, function ($t as xs:double) as xs:double { if ($t < 0) then -$b * math:pow(math:sin($t), 2 div $α) else $b * math:pow(math:sin($t), 2 div $α) }, -math:pi() div 2, math:pi() div 2 ) }

####
__Function__: swoosh

declare function swoosh($center as map(xs:string,item()*),
$r as xs:double) as map(xs:string,item()*)

__Function__: swoosh

declare function swoosh($center as map(xs:string,item()*), $r as xs:double) as map(xs:string,item()*)

swoosh()

Swoosh shape: two double curves

##### Params

- center as map(xs:string,item()*)
- r as xs:double

##### Returns

- map(xs:string,item()*)

declare function this:swoosh( $center as map(xs:string,item()*), $r as xs:double ) as map(xs:string,item()*) { let $c1 := point:point(-1.5 * $r, 0) let $c2 := point:point(-0.5 * $r, 0) let $c3 := point:point( 0.5 * $r, 0) let $c4 := point:point( 1.5 * $r, 0) let $edges := ( edge:arc($c1, $r, point:point(-0.5*$r, 0), point:point(-1.5*$r, $r), false(), false()), edge:arc($c1, $r, point:point(-1.5*$r, $r), point:point(-2.5*$r, 0), false(), false()), edge:edge(point:point(-2.5*$r, 0), point:point(-1.5*$r, 0)), edge:arc($c2, $r, point:point(-1.5*$r, 0), point:point(-0.5*$r, $r), true(), false()), edge:arc($c2, $r, point:point(-0.5*$r, $r), point:point(0.5*$r, 0), true(), false()), edge:arc($c4, $r, point:point(0.5*$r, 0), point:point(1.5*$r, -$r), false(), false()), edge:arc($c4, $r, point:point(1.5*$r, -$r), point:point(2.5*$r, 0), false(), false()), edge:edge(point:point(2.5*$r, 0), point:point(1.5*$r, 0)), edge:arc($c3, $r, point:point(1.5*$r, 0), point:point(0.5*$r, -$r), true(), false()), edge:arc($c3, $r, point:point(0.5*$r, -$r), point:point(-0.5*$r, 0), true(), false()) ) return path:polygon($edges)=>geom:translate(point:px($center), point:py($center)) }

####
__Function__: waveline

declare function waveline($from as map(xs:string,item()*),
$to as map(xs:string,item()*),
$waveheight as xs:double) as map(xs:string,item()*)*

__Function__: waveline

declare function waveline($from as map(xs:string,item()*), $to as map(xs:string,item()*), $waveheight as xs:double) as map(xs:string,item()*)*

##### Params

- from as map(xs:string,item()*)
- to as map(xs:string,item()*)
- waveheight as xs:double

##### Returns

- map(xs:string,item()*)*

declare function this:waveline( $from as map(xs:string,item()*), $to as map(xs:string,item()*), $waveheight as xs:double ) as map(xs:string,item()*)* { this:waveline($from, $to, $waveheight, 4 * $waveheight) }

####
__Function__: waveline

declare function waveline($from as map(xs:string,item()*),
$to as map(xs:string,item()*),
$waveheight as xs:double,
$wavelength as xs:double) as map(xs:string,item()*)*

__Function__: waveline

declare function waveline($from as map(xs:string,item()*), $to as map(xs:string,item()*), $waveheight as xs:double, $wavelength as xs:double) as map(xs:string,item()*)*

##### Params

- from as map(xs:string,item()*)
- to as map(xs:string,item()*)
- waveheight as xs:double
- wavelength as xs:double

##### Returns

- map(xs:string,item()*)*

declare function this:waveline( $from as map(xs:string,item()*), $to as map(xs:string,item()*), $waveheight as xs:double, $wavelength as xs:double ) as map(xs:string,item()*)* { let $distance := geom:distance($from, $to) let $waves := ($distance idiv $wavelength) let $slant := edge:angle($from, $to) return ( curve:parametric( max((4, $waves * 6)) cast as xs:integer, function ($t as xs:double) as xs:double {$t * $distance}, function ($t as xs:double) as xs:double {$waveheight * math:sin($t * math:pi() * 2 * $waves)}, 0.0, 1.0 )=>geom:rotate(-$slant)=>geom:translate(point:px($from), point:py($from)) ) }

####
__Function__: complex-circular-arc

declare function complex-circular-arc($r as xs:double,
$z0 as map(xs:string,item()*),
$θ as xs:double*,
$n-points as xs:integer,
$gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

__Function__: complex-circular-arc

declare function complex-circular-arc($r as xs:double, $z0 as map(xs:string,item()*), $θ as xs:double*, $n-points as xs:integer, $gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

complex-circular-arc()

Calculate circular arc at radius r from z0 between θ[1] and θ[2] using

n-points in the complex plane. Produces sequence of points in the

complex plane.

##### Params

- r as xs:double: base radius
- z0 as map(xs:string,item()*): center point
- θ as xs:double*: starting and ending angles
- n-points as xs:integer: number of points to generate
- gain-f as function(xs:double)asxs:double: multiplier of radii of points along the arc; a function of fraction of arc running from 1/n-points to first point in arc to 1 for last; use constant 1 for actual circular arc

##### Returns

- map(xs:string,item()*)*

declare function this:complex-circular-arc( $r as xs:double, $z0 as map(xs:string,item()*), $θ as xs:double*, $n-points as xs:integer, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { for $α at $i in util:linspace($n-points, $θ[1], $θ[2]) return ( z:complex-rφ($r * $gain-f($i div $n-points), $α)=>z:add($z0) ) }

####
__Function__: complex-rounded-rec

declare function complex-rounded-rec($r as xs:double,
$z0 as map(xs:string,item()*),
$θ as xs:double*,
$p as xs:double, (: power: 0 to infinity; p < 2 is convex :)
$n-points as xs:integer,
$gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

__Function__: complex-rounded-rec

declare function complex-rounded-rec($r as xs:double, $z0 as map(xs:string,item()*), $θ as xs:double*, $p as xs:double, (: power: 0 to infinity; p < 2 is convex :) $n-points as xs:integer, $gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

complex-rounded-rec()

Calculate a quasi-rectangular arc of power p, radius r from z0 between

θ(1) to θ(2) using n points in the complex plane;

The original used matlab cosd and sind, but I don't have those, so I'm

just going to round things close to zero.

"This program has been rewritten with degrees to avoid problems with

cos(pi/2) and sin(pi) raised to small powers" Cye H. Waldman, 2013

When p = 2 you get the square corners; p > 2 bulges in converging to plus

sign; p < 2, bulges out converging to square.

##### Params

- r as xs:double: base radius
- z0 as map(xs:string,item()*): center point
- θ as xs:double*: starting and ending angles
- p as xs:double: power
- n-points as xs:integer: number of points to generate
- gain-f as function(xs:double)asxs:double: multiplier of radii of points along the arc; a function of fraction of arc running from 1/n-points to first point in arc to 1 for last; use constant 1 for actual circular arc

##### Returns

- map(xs:string,item()*)*

declare function this:complex-rounded-rec( $r as xs:double, $z0 as map(xs:string,item()*), $θ as xs:double*, $p as xs:double, (: power: 0 to infinity; p < 2 is convex :) $n-points as xs:integer, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { for $α at $i in util:linspace($n-points, $θ[1], $θ[2]) let $cos := math:cos($α) let $cos := if (abs($cos) < $config:ε) then 0 else $cos let $sin := math:sin($α) let $sin := if (abs($sin) < $config:ε) then 0 else $sin let $adjust := $gain-f($i div $n-points) return ( z:complex( $adjust * $r * util:sign($cos) * math:pow(abs($cos), $p), $adjust * $r * util:sign($sin) * math:pow(abs($sin), $p) )=>z:add($z0) ) }

####
__Function__: pseudospiral

declare function pseudospiral($n-points as xs:integer,
$sequence as xs:integer*,
$gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

__Function__: pseudospiral

declare function pseudospiral($n-points as xs:integer, $sequence as xs:integer*, $gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

pseudospiral()

Construct a pseudospiral consisting of joined circular arcs.

Result is a set of points.

If sequence is monotonically increasing and positive, we get an actual

spiral. Negative or decreasing values create loops and overlaps of various

sorts.

##### Params

- n-points as xs:integer: number of points per arc
- sequence as xs:integer*: sequence of radii
- gain-f as function(xs:double)asxs:double: multiplier of radii of points along the arc; a function of fraction of arc running from 1/n-points to first point in arc to 1 for last; use constant 1 for actual circular arc

##### Returns

- map(xs:string,item()*)*

declare function this:pseudospiral( $n-points as xs:integer, $sequence as xs:integer*, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { let $rotation := math:pi() div 2 (: angular rotation for each arc :) let $z0 := z:complex(0,0) (: initial starting point :) let $θ := (0, $rotation) (: initial θ range :) let $n := count($sequence) - 1 return fold-left(1 to $n, ($θ, $z0, ()), function ($data as item()*, $k as xs:integer) as item()* { let $θ := (head($data), head(tail($data))) let $z0 := head(tail(tail($data))) let $zstep := tail(tail(tail($data))) let $r := $sequence[$k] let $c4th := this:complex-circular-arc($r, $z0, $θ, $n-points, $gain-f) return ( if ($k < $n) then ( let $rnext := $sequence[$k + 1] let $z0 := $c4th[last()]=>z:sub(z:complex-rφ($rnext, $θ[2])) let $θ := $θ!(. + $rotation) return ($θ, $z0, ($zstep, tail($c4th))) ) else ( ($θ, $z0, ($zstep, tail($c4th), $c4th[last()])) ) ) })=>tail()=>tail()=>tail() }

####
__Function__: pseudospiral-p

declare function pseudospiral-p($n-points as xs:integer,
$sequence as xs:integer*,
$p as xs:double,
$gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

__Function__: pseudospiral-p

declare function pseudospiral-p($n-points as xs:integer, $sequence as xs:integer*, $p as xs:double, $gain-f as function(xs:double) as xs:double) as map(xs:string,item()*)*

pseudospiral-p()

Construct a pseudospiral consisting of joined circular arcs

Result is a set of points. If sequence is monotonically increasing and

positive, we get an actual spiral. Negative or decreasing values create

loops and overlaps of various sorts. The idea here is to call this with

various values of p to create a sheaf of curves following the path.

##### Params

- n-points as xs:integer: number of points per arc
- sequence as xs:integer*: sequence of radii
- p as xs:double: the power to use (collapse of circular arc)

##### Returns

- map(xs:string,item()*)*

declare function this:pseudospiral-p( $n-points as xs:integer, $sequence as xs:integer*, $p as xs:double, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { let $rotation := math:pi() div 2 (: angular rotation for each arc :) let $z0 := z:complex(0,0) (: initial starting point :) let $θ := (0, $rotation) (: initial θ range :) let $n := count($sequence) - 1 return fold-left(1 to $n, ($θ, $z0, ()), function ($data as item()*, $k as xs:integer) as item()* { let $θ := (head($data), head(tail($data))) let $z0 := head(tail(tail($data))) let $zstep := tail(tail(tail($data))) let $r := $sequence[$k] let $c4th := this:complex-rounded-rec($r, $z0, $θ, $p, $n-points, $gain-f) return ( if ($k < $n) then ( let $rnext := $sequence[$k + 1] let $z0 := $c4th[last()]=>z:sub(z:complex-rφ($rnext, $θ[2])) let $θ := $θ!(. + $rotation) return ($θ, $z0, ($zstep, tail($c4th))) ) else ( ($θ, $z0, ($zstep, tail($c4th), $c4th[last()])) ) ) })=>tail()=>tail()=>tail() }

####
__Function__: pseudospiral-triangles

declare function pseudospiral-triangles($sequence as xs:integer*, (: driver sequence :)
$debug as xs:boolean) as map(xs:string,item()*)*

__Function__: pseudospiral-triangles

declare function pseudospiral-triangles($sequence as xs:integer*, (: driver sequence :) $debug as xs:boolean) as map(xs:string,item()*)*

pseudospiral-triangles()

Construct gnomic tiling triangles following the pseudospiral path

defined by the radius sequence. Result is a set of polygons, preceded,

if debug is true(), by the path of the outer controlling spiral.

##### Params

- sequence as xs:integer*: sequence of radii
- debug as xs:boolean

##### Returns

- map(xs:string,item()*)*

declare function this:pseudospiral-triangles( $sequence as xs:integer*, (: driver sequence :) $debug as xs:boolean ) as map(xs:string,item()*)* { let $kmax := count($sequence) - 1 let $θ1 := math:sqrt(3) div 2 let $θ := math:pi() div 3 (: Trapezoidal gnomons :) let $gnomons := ( let $ztri := array { z:as-complex(0), z:complex(-1, $θ1)=>z:times(0.5), z:as-complex(-1), z:as-complex(0), z:as-complex(0) } let $Z := array:for-each($ztri, function ($v as map(*)) as map(*) {$v=>z:times($sequence[1])}) return ( fold-left(1 to $kmax, ($ztri, $Z), function ($data as array(*)*, $k as xs:integer) as array(*)* { let $zprev := head($data) let $Z := tail($data) let $zgno := ( z:as-complex(0), z:as-complex(-$sequence[$k]), z:as-complex(-$sequence[$k])=>z:add( z:complex-rφ($sequence[$k + 1] - $sequence[$k], 4 * $θ) ), z:complex-rφ($sequence[$k + 1] - $sequence[$k], 5 * $θ), z:as-complex(0) ) let $znext := ( for $val in $zgno return ( $val=>z:multiply(z:complex-rφ(1, -2*($k - 1)*$θ)) ) ) let $znext := ( if ($k = 1) then ( array { for $v in $znext return $v=>z:sub($znext[1])=>z:add($zprev(1)) } ) else ( array { for $v in $znext return $v=>z:sub($znext[1])=>z:add($zprev(3)) } ) ) return ( $znext, ($Z, $znext) ) } )=>tail() ) ) (: 'super' triangles: previous triangle plus trapezoid :) let $triangles := ( path:polygon(geom:to-edges(( for $i in 1 to 4 return $gnomons[1]($i) )))=>map:put("pid", 1), path:triangle( $gnomons[2](4), $gnomons[1](2), $gnomons[2](3) )=>map:put("pid", 2), for $k in 3 to $kmax + 1 return ( path:triangle( $gnomons[$k](4), $gnomons[$k - 1](4), $gnomons[$k](3) )=>map:put("pid", $k) ) ) let $outer-spiral := ( let $n-points := 50 let $no-gain := function ($t as xs:double) as xs:double {1} let $z0 := point:centroid($gnomons[1]?*) let $θ := (7 * math:pi() div 6, math:pi() div 2) let $r := point:distance($z0, geom:vertices($triangles[1])[1]) return ( this:complex-circular-arc($r, $z0, $θ, $n-points, $no-gain), fold-left(2 to $kmax + 1, ($θ, ()), function ($data as item()*, $k as xs:integer) as item()* { let $θ := (head($data), head(tail($data))) let $zstep := tail(tail($data)) let $w := $triangles[$k] let $θ := $θ!(. + -2 * math:pi() div 3) let $z0 := point:centroid(geom:vertices($w)[position() < last()]) let $r := point:distance($z0, geom:vertices($triangles[$k])[1]) let $c4th := this:complex-circular-arc($r, $z0, $θ, $n-points, $no-gain) return ( ($θ, ($zstep, $c4th)) ) })=>tail()=>tail() ) ) return ( if ($debug) then path:path(geom:to-edges($outer-spiral)) else (), $triangles ) }

####
__Function__: circle-spiral

declare function circle-spiral($circle as map(xs:string,item()*),
$turns as xs:double,
$fineness as xs:integer) as map(xs:string,item()*)

__Function__: circle-spiral

declare function circle-spiral($circle as map(xs:string,item()*), $turns as xs:double, $fineness as xs:integer) as map(xs:string,item()*)

circle-spiral()

Fill a circle with a spiral with the given number of turns.

##### Params

- circle as map(xs:string,item()*): the circle to fill
- turns as xs:double: how many turns of the spiral
- fineness as xs:integer: how many points per turn

##### Returns

- map(xs:string,item()*): simple path of the points

declare function this:circle-spiral( $circle as map(xs:string,item()*), $turns as xs:double, $fineness as xs:integer ) as map(xs:string,item()*) { let $center := ellipse:center($circle) let $radius := ellipse:radius($circle) let $n-points := util:round($turns * $fineness) let $f := function ($t as xs:double) as map(xs:string,item()*) { point:destination( $center, 360 * ($t mod 1), $radius * ($t div $turns) ) } return ( curve:plot($n-points, $f, $turns, false(), false()) ) }

####
__Function__: circle-spiral

declare function circle-spiral($circle as map(xs:string,item()*),
$turns as xs:double) as map(xs:string,item()*)

__Function__: circle-spiral

declare function circle-spiral($circle as map(xs:string,item()*), $turns as xs:double) as map(xs:string,item()*)

circle-spiral()

Fill a circle with a spiral with the given number of turns using default

fineness (50).

##### Params

- circle as map(xs:string,item()*): the circle to fill
- turns as xs:double: how many turns of the spiral

##### Returns

- map(xs:string,item()*): simple path of the points

declare function this:circle-spiral( $circle as map(xs:string,item()*), $turns as xs:double ) as map(xs:string,item()*) { this:circle-spiral($circle, $turns, 50) }

####
__Function__: bend

declare function bend($path as map(xs:string,item()*),
$center as map(xs:string,item()*),
$radius as xs:double,
$arc as xs:double, (: degrees :)
$scaling as xs:double) as map(xs:string,item()*)*

__Function__: bend

declare function bend($path as map(xs:string,item()*), $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, (: degrees :) $scaling as xs:double) as map(xs:string,item()*)*

bend()

Map a path onto a circular arc. Each point in the path is mapped to a point

an equivalent fraction along the arc. The y-axis of each input point is mapped

to a point perpendicular to the target arc at the corresponding point with the

distance from the arc being the distance of the input point from the midline

of the bounding box of the input path, scaled. This method is therefore most

appropriate for paths that are broadly horizontal.

##### Params

- path as map(xs:string,item()*): input path
- center as map(xs:string,item()*): center of circular arc
- radius as xs:double: radius of circular arc
- arc as xs:double: degrees of arc (starting at 0°)
- scaling as xs:double: how much to scale the y distance

##### Returns

- map(xs:string,item()*)*: mapped points of the input path

declare function this:bend( $path as map(xs:string,item()*), $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, (: degrees :) $scaling as xs:double ) as map(xs:string,item()*)* { let $path := $path=>path:with-edge-ts() let $edge-ts := (0.0, $path("edge-ts")) let $points := path:vertices($path) let $n := count($points) let $bb := geom:bounding-box($points) let $mid-y := box:min-py($bb) + box:height($bb) div 2 for $p at $i in $points let $t := $edge-ts[$i] let $angle := $t * $arc let $d := $radius + (point:py($p) - $mid-y)*$scaling return $center=>point:destination($angle, $d) }

####
__Function__: bend-path

declare function bend-path($path as map(xs:string,item()*),
$target-path as map(xs:string,item()*),
$scaling as xs:double) as map(xs:string,item()*)*

__Function__: bend-path

declare function bend-path($path as map(xs:string,item()*), $target-path as map(xs:string,item()*), $scaling as xs:double) as map(xs:string,item()*)*

bend-path()

Map one path onto another. Each point in the input path is mapped to a point

an equivalent fraction along the target path. The y-axis of each input point

is mapped to a point perpendicular to the target path at the corresponding point

with the distance from the path being the distance of the input point from the

midline of the bounding box of the input path, scaled. This method is therefore

most appropriate for paths that are broadly horizontal.

##### Params

- path as map(xs:string,item()*): input path
- target-path as map(xs:string,item()*)
- scaling as xs:double: how much to scale the y distance

##### Returns

- map(xs:string,item()*)*: mapped points of the input path

declare function this:bend-path( $path as map(xs:string,item()*), $target-path as map(xs:string,item()*), $scaling as xs:double ) as map(xs:string,item()*)* { let $path := $path=>path:with-edge-ts() let $edge-ts := (0.0, $path("edge-ts")) let $points := path:vertices($path) let $target-path := $target-path=>path:with-edge-ts() let $n := count($points) let $bb := geom:bounding-box($points) let $mid-y := box:min-py($bb) + box:height($bb) div 2 for $p at $i in $points let $t := $edge-ts[$i] let $angle := $target-path=>geom:normal-angle($t) let $d := (point:py($p) - $mid-y)*$scaling return $target-path=>path:path-point($t)=>point:destination($angle, $d) }

####
__Function__: stroke-path

declare function stroke-path($path as map(xs:string,item()*),
$fatness as xs:double) as map(xs:string,item()*)

__Function__: stroke-path

declare function stroke-path($path as map(xs:string,item()*), $fatness as xs:double) as map(xs:string,item()*)

stroke-path()

Convert the path into a stroke that smoothly widens towards the middle

and tapers to the ends.

##### Params

- path as map(xs:string,item()*): source path (Bézier ok, arcs not)
- fatness as xs:double: how wide to get at the middle

##### Returns

- map(xs:string,item()*): polygon for the stroke

declare function this:stroke-path( $path as map(xs:string,item()*), $fatness as xs:double ) as map(xs:string,item()*) { let $reverse := ( let $rev-path := path:reverse($path)=>path:with-edge-ts() let $edge-ts := (0.0, $rev-path("edge-ts")) for $edge at $i in path:edges($rev-path) let $nstart := geom:normal-angle($edge, 0.0) let $nend := geom:normal-angle($edge, 1.0) let $dstart := util:mix(0, $fatness, 0.5 - abs(0.5 - $edge-ts[$i])) let $dend := util:mix(0, $fatness, 0.5 - abs(0.5 - $edge-ts[$i + 1])) let $nmid := geom:normal-angle($edge, 0.5) let $dmid := avg(($dstart, $dend)) return ( switch(edge:kind($edge)) case "edge" return ( edge:edge( edge:start($edge)=>point:destination($nstart, $dstart), edge:end($edge)=>point:destination($nend, $dend) ) ) case "quad" return ( edge:quad( edge:start($edge)=>point:destination($nstart, $dstart), edge:end($edge)=>point:destination($nend, $dend), edge:controls($edge)[1]=>point:destination($nmid, $dmid) ) ) case "cubic" return ( edge:cubic( edge:start($edge)=>point:destination($nstart, $dstart), edge:end($edge)=>point:destination($nend, $dend), edge:controls($edge)[1]=>point:destination($nmid, $dmid), edge:controls($edge)[2]=>point:destination($nmid, $dmid) ) ) default return $edge ) ) return ( util:merge-into($path=>path:property-map(), path:polygon( (path:edges($path), $reverse) ) ) ) }

### Original Source Code

xquery version "3.1"; (:~ : Module with functions providing some interesting paths : No global parameters or randomizers : : Copyright© Mary Holstege 2020-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since November 2021 : @custom:Status Active :) module namespace this="http://mathling.com/shape/paths"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace seq="http://mathling.com/core/sequences" at "../core/sequences.xqy"; import module namespace z="http://mathling.com/core/complex" at "../core/complex.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 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 ellipse="http://mathling.com/geometric/ellipse" at "../geo/ellipse.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; import module namespace curve="http://mathling.com/geometric/curve" at "../geo/curves.xqy"; import module namespace fern_t="http://mathling.com/type/fern" at "../types/fern.xqy"; import module namespace knot_t="http://mathling.com/type/modulated-knot" at "../types/modulated-knot.xqy"; import module namespace lissa_t="http://mathling.com/type/modulated-lissajous" at "../types/modulated-lissajous.xqy"; import module namespace pend_t="http://mathling.com/type/pendulum" at "../types/pendulum.xqy"; import module namespace wiggle_t="http://mathling.com/type/wiggle" at "../types/wiggle.xqy"; import module namespace polynomial="http://mathling.com/type/polynomial" at "../types/polynomial.xqy"; declare namespace svg="http://www.w3.org/2000/svg"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; declare namespace array="http://www.w3.org/2005/xpath-functions/array"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; (:~ : arc() : Make a circular path of $arc degrees from $start-angle : : @param $center: center of the circle : @param $radius: radius of the circle : @param $arc: degrees of arc : @param $start-angle: starting angle (degrees) : @param $num-points: number of points to create along arc :) declare function this:arc( $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, $start-angle as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { (: for (α=β; α<θ+β α+=θ/n) :) for $point in 0 to $num-points - 1 (: α = β + point*θ/n :) let $α := $start-angle + $point * ($arc div $num-points) return ( geom:destination($center, $α, $radius) ) }; (:~ : reverse-arc() : Make a circular path from $start-angle down to $start-angle - $arc : : @param $center: center of the circle : @param $radius: radius of the circle : @param $arc: degrees of arc : @param $start-angle: starting angle (degrees) : @param $num-points: number of points to create along arc :) declare function this:reverse-arc( $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, $start-angle as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { (: for (α=θ+β; α>=β α-=θ/n) :) for $point in 0 to $num-points (: α = β - point*θ/n :) let $α := $start-angle - $point * ($arc div $num-points) return ( geom:destination($center, $α, $radius) ) }; (:~ : helix() : Create a right-handed 3-D helix path : : @param $center: starting point, center of turn : @param $radius: radius of helix : @param $slope: slope of helix : @param $num-points: how many points to draw : : helix(x) = {a cos(t), a sin(t), b t} : a = radius; b/a = slope; so b = slope*a :) declare function this:helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { for $t in 0 to $num-points - 1 let $x := $radius * math:cos($t) + point:px($center) let $y := $radius * math:sin($t) + point:py($center) let $z := $slope * $radius * $t + point:pz($center) return point:point($x, $y, $z) }; (:~ : helix() : Create a right-handed 3-D helix path : : @param $center: starting point, center of turn : @param $radius: radius of helix : @param $slope: slope of helix : @param $num-points: how many points to draw : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs 0 to 2π; gives double spirals : : helix(x) = {a cos(t), a sin(t), b t} : a = radius; b/a = slope; so b = slope*a :) declare function this:helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*math:cos($t) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($t) + $δy }, function ($t as xs:double) as xs:double { $slope*$radius*$t + $δz }, $min, $max ) ) }; (:~ : left-helix() : Create a right-handed 3-D helix path : : @param $center: starting point, center of turn : @param $radius: radius of helix : @param $slope: slope of helix : @param $num-points: how many points to draw : : helix(x) = {-a cos(t), a sin(t), b t} : a = radius; b/a = slope; so b = slope*a :) declare function this:left-helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { for $t in 0 to $num-points - 1 let $x := -$radius * math:cos($t) + point:px($center) let $y := $radius * math:sin($t) + point:py($center) let $z := $slope * $radius * $t + point:pz($center) return point:point($x, $y, $z) }; (:~ : left-helix() : Create a left-handed 3-D helix path : : @param $center: starting point, center of turn : @param $radius: radius of helix : @param $slope: slope of helix : @param $num-points: how many points to draw : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs 0 to 2π; gives double spirals : : helix(x) = {-a cos(t), a sin(t), b t} : a = radius; b/a = slope; so b = slope*a :) declare function this:left-helix( $center as map(xs:string,item()*), $radius as xs:double, $slope as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { -$radius*math:cos($t) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($t) + $δy }, function ($t as xs:double) as xs:double { $slope*$radius*$t + $δz }, $min, $max ) ) }; (:~ : torus-knot() : Create a torus knot. : : @param $center: center point of knot : @param $radius: scaling of knot : @param $p: knot parameter : @param $q: know parameter : For clean knots p and q should be mutually prime, if they aren't you : miss some of the lobes : bridges = q; crossings = p(q-1); generally p-lobed shape : Normally p > q and you get clean lobes : When p < q, you get involuted circles : @param $num-points: number of points to draw :) declare function this:torus-knot( $center as map(xs:string,item()*), $radius as xs:double, $p as xs:integer, $q as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*math:cos($q*$t)*(3 + math:cos($p*$t)) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($q*$t)*(3 + math:cos($p*$t)) + $δy }, function ($t as xs:double) as xs:double { $radius*math:sin($p*$t) + $δz }, 0, 2*math:pi() ) ) }; (:~ : skewed-torus-knot() : Create a torus knot. : : @param $center: center point of knot : @param $radius: scaling of knot : @param $p: knot parameter : @param $q: knot parameter : For clean knots p and q should be mutually prime, if they aren't you : miss some of the lobes : bridges = q; crossings = p(q-1); generally p-lobed shape : Normally p > q and you get clean lobes : When p < q, you get involuted circles : @param $s: skew parameter < 4 : @param $r: skew parameter < 4 : A normal torus knot has s=r=3; : s!=r gives smooshed knots; : s is very different from r gives overlaps perceived as complicated twists : s=r>3 gives pudgier links : s=r<3 gives linked circles : @param $num-points: number of points to draw :) declare function this:skewed-torus-knot( $center as map(xs:string,item()*), $radius as xs:double, $p as xs:integer, $q as xs:integer, $s as xs:integer, $r as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric($num-points, function ($t as xs:double) as xs:double { $radius*math:cos($q*$t)*($s + math:cos($p*$t)) + $δx }, function ($t as xs:double) as xs:double { $radius*math:sin($q*$t)*($r + math:cos($p*$t)) + $δy }, function ($t as xs:double) as xs:double { $radius*math:sin($p*$t) + $δz }, 0, 2*math:pi() ) ) }; (:~ : modulated-torus-knot() : Create a modulated torus knot. : : @param $center: center point of knot : @param $radius: scaling of knot : @param $knot: knot parameters (see modulated torus knot type information) : @param $num-points: number of points to draw :) declare function this:modulated-torus-knot( $center as map(xs:string,item()*), $radius as xs:double, $knot as map(xs:string,item()*), $num-points as xs:integer ) as map(xs:string,item()*)* { trace((), "@"||$radius||" "||geom:quote($center)||" "||knot_t:describe($knot)), let $p := $knot=>knot_t:p() let $openness := $knot=>knot_t:openness() let $x-factors := $knot=>knot_t:x-factors() let $y-factors := $knot=>knot_t:y-factors() let $stretch := $knot=>knot_t:stretch() let $openness := $knot=>knot_t:openness() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric($num-points, function ($t as xs:double) as xs:double { $radius*$stretch*( math:cos($p*$t)* ($openness + sum( let $n := count($x-factors) for $j in 1 to $n return ( let $factor := $x-factors[$j] let $r := $factor=>knot_t:r() let $q := $factor=>knot_t:q() return ( if ($factor=>knot_t:skew()) then ( $r*math:sin($q*$t) ) else ( $r*math:cos($q*$t) ) ) ) ) ) ) + $δx }, function ($t as xs:double) as xs:double { $radius*( math:sin($p*$t)* ($openness + sum( let $n := count($y-factors) for $j in 1 to $n return ( let $factor := $y-factors[$j] let $r := $factor=>knot_t:r() let $q := $factor=>knot_t:q() return ( if ($factor=>knot_t:skew()) then ( $r*math:sin($q*$t) ) else ( $r*math:cos($q*$t) ) ) ) ) ) ) + $δy }, function ($t as xs:double) as xs:double { $radius*0.2*( $openness + math:sin(max(($p,$x-factors!knot_t:q(.)))*$t) ) + $δz }, 0, 2*math:pi() ) ) }; (:~ : modulated-lissajous() : Create a modulated Lissajous curve. : : @param $center: center point of curve : @param $radius: scaling of curve : @param $curve: curve parameters (see Lissajous type information) : @param $num-points: number of points to draw :) declare function this:modulated-lissajous( $center as map(xs:string,item()*), $radius as xs:double, $curve as map(xs:string,item()*), $num-points as xs:integer ) as map(xs:string,item()*)* { let $x-factors := $curve=>lissa_t:x-factors() let $y-factors := $curve=>lissa_t:y-factors() let $stretch := $curve=>lissa_t:stretch() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*$stretch*sum( for $factor in $x-factors let $n := $factor=>lissa_t:n() let $φ := util:radians($factor=>lissa_t:phase()) let $skew := $factor=>lissa_t:skew() return if ($skew) then math:sin($n*$t + $φ) else math:cos($n*$t + $φ) ) + $δx }, function ($t as xs:double) as xs:double { $radius*sum( for $factor in $y-factors let $n := $factor=>lissa_t:n() let $φ := util:radians($factor=>lissa_t:phase()) let $skew := $factor=>lissa_t:skew() return if ($skew) then math:sin($n*$t + $φ) else math:cos($n*$t + $φ) ) + $δy }, function ($t as xs:double) as xs:double { $radius*math:sin(max($x-factors=>lissa_t:n())*$t) + $δz }, 0, 2 * math:pi() ) ) }; (:~ : rose-curve() : Create a closed looping curve : (see https://en.wikipedia.org/wiki/Rose_(mathematics)) : : @param $center: center point of rose : @param $n: rose parameter : @param $d: rose parameter : k = n/d : $n > $d gives petals around a center : $n < $d gives involuted petals : k = 1 is a circle : Number of petals depends on k : k is integer => k if odd; 2k if even : k is n/3 w/ n mod 3 != 0; n petals if n is odd; 2k if even : n and d mutually prime gives cleaner roses; not mutually prime drops : some of the petals : @param $radius: scaling of rose : @param $num-points: number of points to draw :) declare function this:rose-curve( $center as map(xs:string,item()*), $radius as xs:double, $n as xs:integer, (: k=n/d petals: 2k if even, k :) $d as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $k := $n div $d let $angle := if ($n mod $d = 0) then ( if ($k mod 2 = 0) then 2 * math:pi() else math:pi() ) else ( 2 * math:pi() * $d ) let $δx := point:px($center) let $δy := point:py($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*math:cos($k*$t)*math:cos($t) + $δx }, function ($t as xs:double) as xs:double { $radius*math:cos($k*$t)*math:sin($t) + $δy }, 0, $angle ) ) }; (:~ : trefoil() : A trefoil; as a 3D curve (torus surface) : Compared to a torus(3,2) the lobes are more drop-like and smaller : Compared to a rose(3,1) the lobes are separated and larger : : @param $center: center point of curve : @param $radius: scaling of curve : @param $num-points: number of points to draw :) declare function this:trefoil( $center as map(xs:string,item()*), $radius as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $radius*(math:sin($t) + 2*math:sin(2*$t)) + $δx }, function ($t as xs:double) as xs:double { $radius*(math:cos($t) - 2*math:cos(2*$t)) + $δy }, function ($t as xs:double) as xs:double { $radius*(-math:sin(3*$t)) + $δz }, 0, 2*math:pi() ) ) }; (:~ : maurer-rose() : Construct a Maurer rose. : (see https://en.wikipedia.org/wiki/Maurer_rose) : : @param $center: center point of rose : @param $n: rose parameter : @param $d: rose parameter : Generally n=small; d=order of magnitude larger; relatively prime : @param $radius: scaling of rose : @param $num-points: number of points to draw for each line set : Since the rose if formed by cross-cutting lines; a small n is more : noticeable in a way the other curves are not; splining doesn't help : num-points=360 (i.e. one per degree) gives a filled out appearance :) declare function this:maurer-rose( $center as map(xs:string,item()*), $radius as xs:double, $n as xs:integer, $d as xs:integer, $num-points as xs:integer ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { let $k := $t * $d return $radius * math:sin($n * $k) * math:cos($k) + $δx }, function ($t as xs:double) as xs:double { let $k := $t * $d return $radius * math:sin($n * $k) * math:sin($k) + $δy }, 0, 2*math:pi() ) ) }; (:~ : granny-knot() : Construct a granny knot. : : @param $center: center point of knot : @param $radius: scaling of knot : @param $num-points: number of points to draw :) declare function this:granny-knot( $center as map(xs:string,item()*), $radius as xs:double, $num-points as xs:integer ) as map(xs:string,item()*)* { let $scaled-radius := $radius div 100 let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( $num-points, function ($t as xs:double) as xs:double { $scaled-radius*(-22*math:cos($t) - 128*math:sin($t) - 44*math:cos(3*$t) - 78*math:sin(3*$t)) + $δx }, function ($t as xs:double) as xs:double { $scaled-radius*(-10*math:cos(2*$t) - 27*math:sin(2*$t) + 38*math:cos(4*$t) + 46*math:sin(4*$t)) + $δy }, function ($t as xs:double) as xs:double { $scaled-radius*(70*math:cos(3*$t) - 40*math:sin(3*$t)) + $δz }, 0, 2*math:pi() ) ) }; (:~ : harmonograph() : Draw the path of a harmonograph (multi-axis multi-pendulum system) : : @param $center: Center of graph : @param $radius: Basic scaling of graph : @param $harmonograph: Harmonograph descriptor (see pend_t:harmonograph()) : @param $density: Parameter to adjust fineness of the time ticks (t=i/density) : @param $extent: Basic extent of drawing : Final number of ticks (and therefore points) is num-points*density :) declare function this:harmonograph( $center as map(xs:string,item()*), $radius as xs:double, $harmonograph as map(xs:string,item()*), $density as xs:integer, $extent as xs:double ) as map(xs:string,item()*)* { let $num-x-pendulums := pend_t:num-pendulums(pend_t:x($harmonograph)) let $x-pendulums := pend_t:x($harmonograph)=>pend_t:pendulums() let $num-y-pendulums := pend_t:num-pendulums(pend_t:y($harmonograph)) let $y-pendulums := pend_t:y($harmonograph)=>pend_t:pendulums() let $num-z-pendulums := pend_t:num-pendulums(pend_t:z($harmonograph)) let $z-pendulums := pend_t:z($harmonograph)=>pend_t:pendulums() let $δx := point:px($center) let $δy := point:py($center) let $δz := point:pz($center) return ( curve:parametric( util:round($extent * $density), function ($t as xs:double) as xs:double { $radius*sum( $x-pendulums!pend_t:damped-pendulum($t, .) ) + $δx }, function ($t as xs:double) as xs:double { $radius*sum( $y-pendulums!pend_t:damped-pendulum($t, .) ) + $δy }, function ($t as xs:double) as xs:double { $radius*sum( $z-pendulums!pend_t:damped-pendulum($t, .) ) + $δz }, 1 div $density, $extent ) ) (: for $i in 1 to $extent*$density let $t := $i div $density let $x := $radius*sum( $x-pendulums!pend_t:damped-pendulum($t, .) ) + point:px($center) let $y := $radius*sum( $y-pendulums!pend_t:damped-pendulum($t, .) ) + point:py($center) let $z := $radius*sum( $z-pendulums!pend_t:damped-pendulum($t, .) ) + point:py($center) return point:point($x, $y, $z) :) }; (:~ : wiggle() : Draw the path of a knot wiggle. Number of points will depend on wiggle : smoothness : : @param $start: Starting point : @param $length: Length : @param $wiggle: Wiggle descriptor :) declare function this:wiggle( $start as map(xs:string,item()*), $length as xs:double, $wiggle as map(xs:string,item()*) ) as map(xs:string,item()*)* { let $smoothness := $length * ($wiggle=>wiggle_t:smoothness()) return ( curve:parametric( util:round($length), function ($t as xs:double) as xs:double { $t }, function ($t as xs:double) as xs:double { let $θ := ($t * $length) div (2 * math:pi() * $smoothness) return point:y($start) + $wiggle=>wiggle_t:value($θ, $length) }, point:x($start), point:x($start) + util:round($length) ) ) }; (:~ : golden-spiral() : Create a sequence of points following a (Fibonacci approximation of) a golden spiral : : @param $num-points: Number of points in each (90 degree) arc of spiral : @param $n: Number of Fibonacci generations : @param $scale: Scale of the spiral (need to do at this level to avoid jaggies) :) declare function this:golden-spiral( $num-points as xs:integer, $n as xs:integer, $scale as xs:double ) as map(xs:string,item()*)* { let $fibs := seq:fibonacci-sequence($n) let $radii := for $i in 1 to $n return if ($i=1) then $scale else $scale*$fibs[$i - 1] let $angles := for $i in 1 to $n return switch ($i mod 4) case 0 return 90 case 1 return 360 case 2 return 270 case 3 return 180 default return () let $centers := fold-left(1 to $n, point:point(0,0), function ($points as map(xs:string,item()*)*, $i as xs:integer) as map(xs:string,item()*)* { $points, let $center := $points[last()] let $last := if ($i = 1) then point:point(point:x($center), point:y($center) - $scale) else geom:destination($center, $angles[$i - 1] - 90, $radii[$i - 1]) return switch ($i mod 4) case 0 return point:point(point:x($center), point:y($last) - $radii[$i]) case 1 return point:point(point:x($last) - $radii[$i], point:y($center)) case 2 return point:point(point:x($center), point:y($last) + $radii[$i]) case 3 return point:point(point:x($last) + $radii[$i], point:y($center)) default return () } )[position() >= 2] for $i in 1 to $n return this:reverse-arc($centers[$i], $radii[$i], 90, $angles[$i], $num-points) }; (:~ : angle-spiral() : Create a path following a simple uniform spiral where the points are a : consistent number of degrees apart : : @param $arc: degrees of arc between each point : @param $center: center of spiral : @param $radius: distance from center to first point : @param $loops: how many windings of spiral : @param $spread: how far about windings are :) declare function this:angle-spiral( $arc as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double ) as map(xs:string,item()*)* { let $num-points := 360 idiv $arc return fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) let $loop-radius := $radius + ($loop - 1)*$spread return ( (: new angle :) util:remap-degrees($last-angle + $num-points * $arc), (: points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $loop-radius + $t * $spread div $num-points }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }; (:~ : point-spiral() : Create a path following a simple uniform spiral where there are the : same number of points in each loop : : @param $num-points: number of points per loop : @param $center: center of spiral : @param $radius: distance from center to first point : @param $loops: how many windings of spiral : @param $spread: how far about windings are :) declare function this:point-spiral( $num-points as xs:integer, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double ) as map(xs:string,item()*)* { let $arc := 360 div $num-points return fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) let $loop-radius := $radius + ($loop - 1)*$spread return ( (: Next angle :) util:remap-degrees($last-angle + $num-points * $arc), (: Points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $loop-radius + $t * $spread div $num-points }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }; (:~ : uniform-spiral() : Create a path following a simple uniform spiral where there is about : the same distance between each point. : : @param $distance: distance between points : @param $center: center of spiral : @param $radius: distance from center to first point : @param $loops: how many windings of spiral : @param $spread: how far apart windings are :) declare function this:uniform-spiral( $distance as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as xs:double ) as map(xs:string,item()*)* { fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) let $loop-radius := $radius + ($loop - 1)*$spread let $avg-loop-radius := avg(($loop-radius, $radius + $loop*$spread)) let $num-points := 2*math:pi()*$avg-loop-radius idiv $distance let $arc := 360 div $num-points return ( (: Next angle :) util:remap-degrees($last-angle + $num-points * $arc), (: Points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $loop-radius + $t * $spread div $num-points }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }; (:~ : spiral-function() : Return a function that can be used with function-spiral() to create a : spiral of a particular kind. : : @param $kind: kind of spread function : uniform: spiral expands uniformly (equivalent to arc-spiral()) : accelerating: spiral expands in an accelerating way on each turn : nautiloid: spirals pinch off at end to give nautilus like appearance : wobbly: spiral veers back and forth in a wave (use higher $arc) : very-wobbly: spiral veers even more : @param $spread: scaling factor on the expansion :) declare function this:spiral-function($kind as xs:string, $spread as xs:double) as function(xs:integer, xs:integer, xs:integer) as xs:double { switch($kind) case "uniform" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { xs:double(($loop - 1)*$spread) + ($point*$spread div $num-points) } case "accelerating" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop * $num-points + $point) * ($loop * $num-points + $point) * $spread div ($num-points * $num-points) } case "wobbly" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop - 1)*$spread + math:pow(-1,$point) * $point * $spread div $num-points } case "very-wobbly" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop - 1)*$spread + 2*math:pow(-1,$point) * $point * $spread div $num-points } case "nautiloid" return function ($loop as xs:integer, $point as xs:integer, $num-points as xs:integer) as xs:double { ($loop * $num-points + $point) * (($loop * $num-points + $point) mod $num-points) * $spread div ($num-points * $num-points) } default return errors:error("ML-BADARGS", ("kind", $kind)) }; (:~ : function-spiral() : Create an arc spiral using a function to compute the distance for : each point in the loop. : : @param $arc: degrees of arc between each point : @param $center: center of spiral : @param $radius: distance from center to first point : @param $loops: how many windings of spiral : @param $spread: function that is added to base radius to compute the distance :) declare function this:function-spiral( $arc as xs:double, $center as map(xs:string,item()*), $radius as xs:double, $loops as xs:integer, $spread as function((:loop:)xs:integer,(:point:)xs:integer,(:num-points:)xs:integer) as xs:double ) as map(xs:string,item()*)* { let $num-points := 360 idiv $arc return fold-left(1 to $loops, (0), function ($angle-and-points as item()*, $loop as xs:integer) as item()* { let $last-angle := head($angle-and-points) return ( (: Next angle :) util:remap-degrees($last-angle + $num-points * $arc), (: Points :) tail($angle-and-points), curve:polar($num-points, $center, (:r:) function ($t as xs:double) as xs:double { $radius + $spread($loop, xs:integer($t), $num-points) }, (:θ:) function ($t as xs:double) as xs:double { $last-angle + $t * $arc }, 0, $num-points - 1 ) ) } )=>tail() }; (:~ : fern-spiral() : Make a fern spiral : Fern spirals are created as a series of segments, where each segment : shrinks from the previous and decreases the lean by curl*curvature : curl and curvature are handled separately by branching and blade : creation : : Overall path length is Σ[j=0,n-1]d*r^j = (d - rl)/(1 - r) where l=ar^(n-1) : in the limit (j=0,∞) = a/(1-r) : height will be less because of angle : : @param $start: starting point for spiral : @param $fern: parameter bundle defining curve, create with fern_t:fern() : left: curve to the left; default=false : scale: size of initial segment; default=100 : lean: amount of initial lean (degrees); default=70 : shrinkage: how much to shrink each segment; default=0.7 : curvature: how much to change the angle (degrees); default=90 : curl: tightness of curl; default=0.2 : @param $generations: number of segments in spiral :) declare function this:fern-spiral( $start as map(xs:string,item()*), $fern as map(xs:string,item()*), $generations as xs:integer ) as map(xs:string,item()*)* { let $is-left := fern_t:is-left($fern) let $scale := fern_t:scale($fern) let $lean := fern_t:lean($fern) let $shrinkage := fern_t:shrinkage($fern) let $curvature := fern_t:curvature($fern) let $curl := fern_t:curl($fern) return ( curve:dependent($generations, $start, function ($last as map(xs:string,item()*), $i as xs:integer) as map(xs:string,item()*) { let $d := $scale*math:pow($shrinkage,($i - 1)) let $a := if ($is-left) then util:remap-degrees($lean + ($i - 1)*($curvature*$curl)) else util:remap-degrees($lean - ($i - 1)*($curvature*$curl)) return geom:destination($last, -$a, $d) } ) ) }; (:~ : eulers() : Euler's spiral/clothoid : : x = k * ∫[0:t]( sin(u²/2)du ) : y = k * ∫[0:t]( cos(u²/2)du ) : : k is scaling; t as some number of multiples of π : : @param $center: center of spiral : @param $scaling: k parameter above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs 0 to 2π; gives double spirals :) declare function this:eulers( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $δx := point:px($center) let $δy := point:py($center) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*2*math:pi()) let $xs := let $f := function ($u as xs:double) as xs:double {math:sin($u*$u div 2)} return fold-left(2 to count($ts), $scaling * util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + $scaling * util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double {math:cos($u*$u div 2)} return fold-left(2 to count($ts), $scaling * util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + $scaling * util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) for $x at $i in $xs return point:point($x + $δx, $ys[$i] + $δy) }; (:~ : polynomial() : : Polynomial spiral: dφ/ds = Σ[n=1:n](ai*s^i) : s=arc length : dφ/ds=1 => circle : dφ/ds=s => Cornu spiral : dφ/ds=s² => double clothoid : dφ/ds=s² + 1 => like Corinthian capital : dφ/ds=s² - 4 => intersecting double spiral vase shape : : s = Σ[j=0:J] (a[j]/(j+1))θ^(j+1) : : https://www.atlantis-press.com/journals/gaf/125935071/view : κ(s) = Pκ'(s) : P is polynomial in s : x = ∫[t=0:s]sin(Pk(t)) : y = ∫[t=0:s]cos(Pk(t)) : k=0 => line, k=1 => circle, k=2 => Cornu spiral, k=3 => where the fun starts : k=3 cont. κ = s² - D => 2pts inflection for D>0, 1 for D=0, none for D<0 : : @param $center: center of spiral : @param $scaling: overall scaling : @param $polynomial: the polynomial (see types/polynomial) : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range; >1 tends to get wonky : @param $symmetric: use a symmetric extent around 0 vs 0 to 2π; gives double spirals :) declare function this:polynomial( $center as map(xs:string,item()*), $scaling as xs:double, $polynomial as map(xs:string,item()*), $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($num-points < 1) then () else let $δx := point:px($center) let $δy := point:py($center) let $pk := polynomial:antiderivative($polynomial) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*math:pi()) let $xs := let $f := function ($u as xs:double) as xs:double { $scaling * math:sin($pk=>polynomial:value($u)) } return fold-left(2 to count($ts), util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double { $scaling * math:cos($pk=>polynomial:value($u)) } return fold-left(2 to count($ts),util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $pts := for $x at $i in $xs return point:point($x, $ys[$i]) let $spiral-center := box:box( min($pts!point:px(.)), min($pts!point:py(.)), max($pts!point:px(.)), max($pts!point:py(.)) )=>box:center() let $delta := $center=>point:sub($spiral-center) return $pts!point:add(., $delta) }; (:~ : 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)) : : @param $center: center of meander : @param $scaling: overall scaling : @param $amplitudes: vector of amplitude values (A[i]); will use last A[i] : if wavelength vector is longer : @param $wavelengths: vector of wavelength values (λ[i]) : @param $num-points: number of points to plot : @param $extent: extent of curve (multiple of π) : @param $symmetric: symmetric extent around 0 :) declare function this:meander( $center as map(xs:string,item()*), $scaling as xs:double, $amplitudes as xs:double*, $wavelengths as xs:double*, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($num-points < 1) then () else let $n := count($wavelengths) let $δx := point:px($center) let $δy := point:py($center) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*math:pi()) let $invλs := $wavelengths!(2*math:pi() div .) let $xs := let $f := function ($u as xs:double) as xs:double { $scaling * math:cos( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * math:sin( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts), util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double { $scaling * math:sin( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * math:sin( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts),util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $pts := for $x at $i in $xs return point:point($x, $ys[$i]) let $wave-center := box:box( min($pts!point:px(.)), min($pts!point:py(.)), max($pts!point:px(.)), max($pts!point:py(.)) )=>box:center() let $delta := $center=>point:sub($wave-center) return $pts!point:add(., $delta) }; (:~ : 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 ) : : @param $center: center of meander : @param $scaling: overall scaling : @param $amplitudes: vector of amplitude values (A[i]); will use last A[i] : if wavelength vector is longer : @param $wavelengths: vector of wavelength values (λ[i]) : @param $num-points: number of points to plot : @param $extent: extent of curve (multiple of π) : @param $symmetric: symmetric extent around 0 :) declare function this:looping-meander( $center as map(xs:string,item()*), $scaling as xs:double, $amplitudes as xs:double*, $wavelengths as xs:double*, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($num-points < 1) then () else let $n := count($wavelengths) let $δx := point:px($center) let $δy := point:py($center) let $ts := if ($symmetric) then util:linspace($num-points, -$extent*math:pi(), $extent*math:pi()) else util:linspace($num-points, 0, $extent*math:pi()) let $invλs := $wavelengths!(2*math:pi() div .) let $xs := let $f := function ($u as xs:double) as xs:double { $scaling * math:cos( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * $invλs[$i] * math:cos( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts), util:integral(0, $ts[1], $f, 4), function ($xs as xs:double*, $i as xs:integer) as xs:double* { $xs, $xs[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $ys := let $f := function ($u as xs:double) as xs:double { $scaling * math:sin( sum( for $i in 1 to $n return ( ($amplitudes[$i], $amplitudes[last()])[1] * $invλs[$i] * math:cos( $invλs[$i] * $u ) ) ) ) } return fold-left(2 to count($ts),util:integral(0, $ts[1], $f, 4), function ($ys as xs:double*, $i as xs:integer) as xs:double* { $ys, $ys[last()] + util:integral($ts[$i - 1], $ts[$i], $f, 4) } ) let $pts := for $x at $i in $xs return point:point($x, $ys[$i]) let $wave-center := box:box( min($pts!point:px(.)), min($pts!point:py(.)), max($pts!point:px(.)), max($pts!point:py(.)) )=>box:center() let $delta := $center=>point:sub($wave-center) return $pts!point:add(., $delta) }; (:~ : cotes() : Cotes' spirals : : A > 0, k > 0, ε real constants A=>size, k=>shape, ε=>angular position : 1/r = A cosh(kθ + ε) case 1 Poinsot's : 1/r = A exp(kθ + ε) case 2 Equiangular : 1/r = A sinh(kθ + ε) case 3 Poinsot's : 1/r = A (kθ + ε) case 4 Hyperbolic/reciprocal : 1/r = A cos(kθ + ε) case 5 Epispiral : : @param $kind: what kind (one of poinsot-c, pointsot-s, equiangular, reciprocal, epispiral) : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $A: A parameter, above; for most 1.0 is fine; epispirals need small fraction A : @param $k: k parameter, above; for most 0.5 is fine; epispirals better with smaller k : @param $ε: ε parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs 0 to 2π; gives cardiod shapes :) declare function this:cotes( $kind as xs:string, $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($A <= 0) then errors:error("ML-BADARGS", ("A", $A)) else (), if ($k <= 0) then errors:error("ML-BADARGS", ("k", $k)) else (), let $r := switch($kind) case "poinsot-c" return function ($t as xs:double) as xs:double { $scaling div ($A * util:cosh($k * $t + $ε)) } case "poinsot-s" return function ($t as xs:double) as xs:double { $scaling div ($A * util:sinh($k * $t + $ε)) } case "equiangular" return function ($t as xs:double) as xs:double { $scaling div ($A * math:exp($k * $t + $ε)) } case "reciprocal" return function ($t as xs:double) as xs:double { $scaling div ($A * ($k * $t + $ε)) } case "epispiral" return function ($t as xs:double) as xs:double { 1 div ($A * math:cos($k * $t + $ε)) } default return errors:error("ML-BADARGS", ("kind", $kind)) let $θ := function ($t as xs:double) as xs:double {$t} return ( (: point:valid() filters out NaNs and INFs :) if ($symmetric) then ( curve:polar($num-points, $center, $r, $θ, -$extent*math:pi(), $extent*math:pi())[point:valid(.)] ) else ( curve:polar($num-points, $center, $r, $θ, 0, 2*$extent*math:pi())[point:valid(.)] ) ) }; (:~ : poinsot-s() : Case 3 Cotes' spiral: 1/r = A sinh(kθ + ε) : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $A: A parameter, above; for most 1.0 is fine; epispirals need small fraction A : @param $k: k parameter, above; for most 0.5 is fine; epispirals better with smaller k : @param $ε: ε parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:poinsot-s( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("poinsot-s", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }; (:~ : poinsot-c() : Case 1 Cotes' spiral: 1/r = A cosh(kθ + ε) : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $A: A parameter, above; for most 1.0 is fine; epispirals need small fraction A : @param $k: k parameter, above; for most 0.5 is fine; epispirals better with smaller k : @param $ε: ε parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:poinsot-c( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("poinsot-c", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }; (:~ : equiangular() : Case 2 Cotes' spiral: 1/r = A exp(kθ + ε) : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $A: A parameter, above; for most 1.0 is fine; epispirals need small fraction A : @param $k: k parameter, above; for most 0.5 is fine; epispirals better with smaller k : @param $ε: ε parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:equiangular( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("equiangular", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }; (:~ : reciprocal() : Case 4 Cotes' spiral: 1/r = A (kθ + ε) : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $A: A parameter, above; for most 1.0 is fine; epispirals need small fraction A : @param $k: k parameter, above; for most 0.5 is fine; epispirals better with smaller k : @param $ε: ε parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:reciprocal( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("reciprocal", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }; (:~ : epispiral() : Case 5 Cotes' spiral: 1/r = A cos(kθ + ε) : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $A: A parameter, above; for most 1.0 is fine; epispirals need small fraction A : @param $k: k parameter, above; for most 0.5 is fine; epispirals better with smaller k : @param $ε: ε parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:epispiral( $center as map(xs:string,item()*), $scaling as xs:double, $A as xs:double, $k as xs:double, $ε as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { this:cotes("epispiral", $center, $scaling, $A, $k, $ε, $num-points, $extent, $symmetric) }; (:~ : cochleoid() : Cochleoid spiral: r = sinφ/φ : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:cochleoid( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { if ($t = 0) then $scaling else $scaling * math:sin($t) div $t }, $min, $max ) ) }; (:~ : atom() : Atom spiral: r=φ/(φ - a) : Spirals in then kicks out a>=1 for a < 1 more of a circle in/out : : @param $center: center of spiral : @param $scaling: scaling of spiral (basic size is generally in unit square) : @param $a: a parameter above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:atom( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { if ($t - $a = 0) then util:sign($scaling * $t) * xs:double("INF") else $scaling * $t div ($t - $a) }, $min, $max )[point:valid(.)] ) }; (:~ : circle-involute() : Involute of circle: r²=φ² + 1 : : @param $center: center of spiral : @param $scaling: scaling of spiral : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:circle-involute( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($symmetric) then ( curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { -$scaling * math:sqrt($t*$t + 1) }, -$extent * math:pi(), 0 ), curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { $scaling * math:sqrt($t*$t + 1) }, $extent * math:pi() div ($num-points idiv 2), $extent * math:pi() ) ) else ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { $scaling * math:sqrt($t*$t + 1) }, 0, 2 * $extent * math:pi() ) ) }; (:~ : lituus() : Lituus: r²θ = k => r = √(k/θ) : : @param $center: center of spiral : @param $scaling: scaling of spiral : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:lituus( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($symmetric) then ( curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { if ($t = 0) then xs:double("-INF") else -math:sqrt($scaling div abs($t)) }, -$extent * math:pi(), 0 ), curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { if ($t = 0) then xs:double("INF") else math:sqrt($scaling div $t) }, $extent * math:pi() div ($num-points idiv 2), $extent * math:pi() ) ) else ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { if ($t = 0) then xs:double("INF") else math:sqrt($scaling div $t) }, 0, 2 * $extent * math:pi() ) )[point:valid(.)] }; (:~ : gielis() : Gielis curve = super ellipse : r = (|cos(dφ)|^a | + |sin(dφ)|^b)^c : : a=10, b=10, c=-2/9, d=3/4: has the shape of the petiole (leaf stem) of the nuphar luteum : a=15, b=15, c=-1/12, d=1: the shape of the cross section of the stem of the herb called scrophularia nodosa : a=1, b=1, c=-1/4, d=5/4: idem, of the equisetum : a=6, b=6, c=-1/10, d=7/4, idem, of the raspberry : : @param $center: center of ellipse : @param $scaling: scaling of ellipse : @param $a: a parameter above : @param $b: b parameter above; a vs b gives relative flatness : @param $c: c parameter above : @param $d: d parameter above => number of symmetries is 4d : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:gielis( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = (|cos(dφ)|^a | + |sin(dφ)|^b)^c :) $scaling * math:pow( math:pow(abs(math:cos($d*$t)), $a) + math:pow(abs(math:sin($d*$t)), $b), $c ) }, $min, $max ) ) }; (:~ : super-rose() : Super rose = extension of rose, generalization of Gielis curve : r = sin(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c : a=1 b=1 c=-1 d=3 f=5/8 => spiky rose : : @param $center: center of rose : @param $scaling: scaling of rose : @param $a: a parameter above : @param $b: b parameter above; a vs b gives relative flatness : @param $c: c parameter above : @param $d: d parameter above : @param $f: f parameter above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:super-rose( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $f as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = sin(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c :) $scaling * math:sin($f*$t) * math:pow( math:pow(abs(math:cos($d*$t)), $a) + math:pow(abs(math:sin($d*$t)), $b), $c ) }, $min, $max ) ) }; (:~ : super-spiral() : Super spiral = generalization of Gielis curve : r = e^(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c : Some cases: : a=b=5, c=-1, d=1, f=1/3; starish wide loops : a=b=5, c=-1/5, d=5/2, f=1/5; wobbly : a=b=100, c=-0.01, d=1, f=1/5; almost square : : @param $center: center of spiral : @param $scaling: scaling of spiral : @param $a: a parameter above : @param $b: b parameter above; a vs b gives relative flatness : @param $c: c parameter above : @param $d: d parameter above => number of symmetries is 4d : @param $f: f parameter above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:super-spiral( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $b as xs:double, $c as xs:double, $d as xs:double, $f as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = e^(fφ)(|cos(dφ)|^a + |sin(dφ)|^b)^c :) $scaling * math:exp($f*$t) * math:pow( math:pow(abs(math:cos($d*$t)), $a) + math:pow(abs(math:sin($d*$t)), $b), $c ) }, $min, $max ) ) }; (:~ : logarithmic() : Logarithmic spiral: r = e^(aφ) : Examples a[0.05:0.25] smaller a, closer windings, further from center start : : @param $center: center of spiral : @param $scaling: scaling of spiral : @param $a: a parameter above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:logarithmic( $center as map(xs:string,item()*), $scaling as xs:double, $a as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = e^(aφ) :) $scaling * math:exp($a*$t) }, $min, $max ) ) }; (:~ : fermat() : Fermat's spiral: r^2 = φ => Archimedes but two joined outflows : : @param $center: center of spiral : @param $scaling: scaling of spiral : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:fermat( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { if ($symmetric) then ( curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { (: r = √φ :) -$scaling * math:sqrt(abs($t)) }, -$extent * math:pi(), 0 ), curve:polar($num-points idiv 2, $center, function ($t as xs:double) as xs:double { (: r = √φ :) math:sqrt($scaling div $t) }, $extent * math:pi() div ($num-points idiv 2), $extent * math:pi() ) ) else ( (: Positive branch only = Archimedes :) curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = √φ :) math:sqrt($scaling div $t) }, 0, 2 * $extent * math:pi() ) ) }; (:~ : tractrix() : Tractrix: r = A cos(t), θ = tan(t) - t : : @param $center: center of spiral : @param $scaling: scaling of spiral (A parameter) : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:tractrix( $center as map(xs:string,item()*), $scaling as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = A cos(t) :) $scaling * math:cos($t) }, function ($t as xs:double) as xs:double { (: θ = tan(t) - t :) math:tan($t) - $t }, $min, $max ) ) }; (:~ : doppler() : Doppler: x = a(t cos(t) + kt), y = a t sin(t) : Like nested circles pinned to one side, but spirals : 0 < k < 1 two spirals intersecting (for - and + angles) : k = 1 two spirals tangent (for - and + angles) : k > 1 two spirals increasing away from center : : @param $center: center of spiral : @param $scaling: scaling of spiral (a parameter, above) : @param $k: k parameter, above : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:doppler( $center as map(xs:string,item()*), $scaling as xs:double, $k as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() let $δx := point:px($center) let $δy := point:py($center) return ( curve:parametric($num-points, function ($t as xs:double) as xs:double { (: x = a(t cos(t) + kt) :) $scaling * ($t * math:cos($t) + $k*$t) + $δx }, function ($t as xs:double) as xs:double { (: y = a(t sin(t)) :) $scaling * ($t * math:sin($t)) + $δy }, $min, $max ) ) }; (:~ : conchospiral() : Conchospiral (3d): r = μ^t a, θ = t, z = μ^t c : μ=>opening angle, c=>slope of cone; example μ=1.07,a=1,c=1.1 : : @param $center: center of spiral : @param $scaling: scaling of spiral (a parameter, above) : @param $μ: opening angle (μ parameter, above) : @param $c: slope of cone (c parameter, above) : @param $num-points: number of points to produce : @param $extent: number of multiples of π in angular range, extent => number of turns : @param $symmetric: use a symmetric extent around 0 vs extent from 0 to 2π :) declare function this:conchospiral( $center as map(xs:string,item()*), $scaling as xs:double, $μ as xs:double, $c as xs:double, $num-points as xs:integer, $extent as xs:double, $symmetric as xs:boolean ) as map(xs:string,item()*)* { let $min := if ($symmetric) then -$extent * math:pi() else 0 let $max := if ($symmetric) then $extent * math:pi() else $extent * 2 * math:pi() return ( curve:polar($num-points, $center, function ($t as xs:double) as xs:double { (: r = μ^t a :) $scaling * math:pow($μ, $t) }, function ($t as xs:double) as xs:double {$t}, (: θ = t :) function ($t as xs:double) as xs:double { (: z = μ^t c :) $c * math:pow($μ, $t) }, $min, $max ) ) }; (:~ : lamé() : Lamé curves: x = ±a cos(t)^(2/α); y = ±b cos(t)^(2/α); : α > 2 => super-ellipse : : @param $num-points: number of points to draw : @param $a: scaling of x (a parameter above) : @param $b: scaling of y (b parameter above) : @param $α: α parameter above :) declare function this:lamé( $num-points as xs:integer, $a as xs:double, $b as xs:double, $α as xs:double ) as map(xs:string,item()*)* { curve:parametric($num-points, function ($t as xs:double) as xs:double { if ($t < 0) then -$a * math:pow(math:cos($t), 2 div $α) else $a * math:pow(math:cos($t), 2 div $α) }, function ($t as xs:double) as xs:double { if ($t < 0) then -$b * math:pow(math:sin($t), 2 div $α) else $b * math:pow(math:sin($t), 2 div $α) }, -math:pi() div 2, math:pi() div 2 ) }; (:~ : swoosh() : Swoosh shape: two double curves :) declare function this:swoosh( $center as map(xs:string,item()*), $r as xs:double ) as map(xs:string,item()*) { let $c1 := point:point(-1.5 * $r, 0) let $c2 := point:point(-0.5 * $r, 0) let $c3 := point:point( 0.5 * $r, 0) let $c4 := point:point( 1.5 * $r, 0) let $edges := ( edge:arc($c1, $r, point:point(-0.5*$r, 0), point:point(-1.5*$r, $r), false(), false()), edge:arc($c1, $r, point:point(-1.5*$r, $r), point:point(-2.5*$r, 0), false(), false()), edge:edge(point:point(-2.5*$r, 0), point:point(-1.5*$r, 0)), edge:arc($c2, $r, point:point(-1.5*$r, 0), point:point(-0.5*$r, $r), true(), false()), edge:arc($c2, $r, point:point(-0.5*$r, $r), point:point(0.5*$r, 0), true(), false()), edge:arc($c4, $r, point:point(0.5*$r, 0), point:point(1.5*$r, -$r), false(), false()), edge:arc($c4, $r, point:point(1.5*$r, -$r), point:point(2.5*$r, 0), false(), false()), edge:edge(point:point(2.5*$r, 0), point:point(1.5*$r, 0)), edge:arc($c3, $r, point:point(1.5*$r, 0), point:point(0.5*$r, -$r), true(), false()), edge:arc($c3, $r, point:point(0.5*$r, -$r), point:point(-0.5*$r, 0), true(), false()) ) return path:polygon($edges)=>geom:translate(point:px($center), point:py($center)) }; declare function this:waveline( $from as map(xs:string,item()*), $to as map(xs:string,item()*), $waveheight as xs:double ) as map(xs:string,item()*)* { this:waveline($from, $to, $waveheight, 4 * $waveheight) }; declare function this:waveline( $from as map(xs:string,item()*), $to as map(xs:string,item()*), $waveheight as xs:double, $wavelength as xs:double ) as map(xs:string,item()*)* { let $distance := geom:distance($from, $to) let $waves := ($distance idiv $wavelength) let $slant := edge:angle($from, $to) return ( curve:parametric( max((4, $waves * 6)) cast as xs:integer, function ($t as xs:double) as xs:double {$t * $distance}, function ($t as xs:double) as xs:double {$waveheight * math:sin($t * math:pi() * 2 * $waves)}, 0.0, 1.0 )=>geom:rotate(-$slant)=>geom:translate(point:px($from), point:py($from)) ) }; (:~ : complex-circular-arc() : Calculate circular arc at radius r from z0 between θ[1] and θ[2] using : n-points in the complex plane. Produces sequence of points in the : complex plane. : : @param $r: base radius : @param $z0: center point : @param $θ: starting and ending angles : @param $n-points: number of points to generate : @param $gain-f: multiplier of radii of points along the arc; a function of : fraction of arc running from 1/n-points to first point in arc to 1 : for last; use constant 1 for actual circular arc :) declare function this:complex-circular-arc( $r as xs:double, $z0 as map(xs:string,item()*), $θ as xs:double*, $n-points as xs:integer, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { for $α at $i in util:linspace($n-points, $θ[1], $θ[2]) return ( z:complex-rφ($r * $gain-f($i div $n-points), $α)=>z:add($z0) ) }; (:~ : complex-rounded-rec() : Calculate a quasi-rectangular arc of power p, radius r from z0 between : θ(1) to θ(2) using n points in the complex plane; : The original used matlab cosd and sind, but I don't have those, so I'm : just going to round things close to zero. : "This program has been rewritten with degrees to avoid problems with : cos(pi/2) and sin(pi) raised to small powers" Cye H. Waldman, 2013 : When p = 2 you get the square corners; p > 2 bulges in converging to plus : sign; p < 2, bulges out converging to square. : : @param $r: base radius : @param $z0: center point : @param $θ: starting and ending angles : @param $p: power : @param $n-points: number of points to generate : @param $gain-f: multiplier of radii of points along the arc; a function of : fraction of arc running from 1/n-points to first point in arc to 1 : for last; use constant 1 for actual circular arc :) declare function this:complex-rounded-rec( $r as xs:double, $z0 as map(xs:string,item()*), $θ as xs:double*, $p as xs:double, (: power: 0 to infinity; p < 2 is convex :) $n-points as xs:integer, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { for $α at $i in util:linspace($n-points, $θ[1], $θ[2]) let $cos := math:cos($α) let $cos := if (abs($cos) < $config:ε) then 0 else $cos let $sin := math:sin($α) let $sin := if (abs($sin) < $config:ε) then 0 else $sin let $adjust := $gain-f($i div $n-points) return ( z:complex( $adjust * $r * util:sign($cos) * math:pow(abs($cos), $p), $adjust * $r * util:sign($sin) * math:pow(abs($sin), $p) )=>z:add($z0) ) }; (:~ : pseudospiral() : Construct a pseudospiral consisting of joined circular arcs. : Result is a set of points. : If sequence is monotonically increasing and positive, we get an actual : spiral. Negative or decreasing values create loops and overlaps of various : sorts. : : @param $n-points: number of points per arc : @param $sequence: sequence of radii : @param $gain-f: multiplier of radii of points along the arc; a function of : fraction of arc running from 1/n-points to first point in arc to 1 : for last; use constant 1 for actual circular arc :) declare function this:pseudospiral( $n-points as xs:integer, $sequence as xs:integer*, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { let $rotation := math:pi() div 2 (: angular rotation for each arc :) let $z0 := z:complex(0,0) (: initial starting point :) let $θ := (0, $rotation) (: initial θ range :) let $n := count($sequence) - 1 return fold-left(1 to $n, ($θ, $z0, ()), function ($data as item()*, $k as xs:integer) as item()* { let $θ := (head($data), head(tail($data))) let $z0 := head(tail(tail($data))) let $zstep := tail(tail(tail($data))) let $r := $sequence[$k] let $c4th := this:complex-circular-arc($r, $z0, $θ, $n-points, $gain-f) return ( if ($k < $n) then ( let $rnext := $sequence[$k + 1] let $z0 := $c4th[last()]=>z:sub(z:complex-rφ($rnext, $θ[2])) let $θ := $θ!(. + $rotation) return ($θ, $z0, ($zstep, tail($c4th))) ) else ( ($θ, $z0, ($zstep, tail($c4th), $c4th[last()])) ) ) })=>tail()=>tail()=>tail() }; (:~ : pseudospiral-p() : Construct a pseudospiral consisting of joined circular arcs : Result is a set of points. If sequence is monotonically increasing and : positive, we get an actual spiral. Negative or decreasing values create : loops and overlaps of various sorts. The idea here is to call this with : various values of p to create a sheaf of curves following the path. : : @param $n-points: number of points per arc : @param $sequence: sequence of radii : @param $p: the power to use (collapse of circular arc) : @param $gain-f: multiplier of radii of points along the arc; a function of : fraction of arc running from 1/n-points to first point in arc to 1 : for last; use constant 1 for actual circular arc :) declare function this:pseudospiral-p( $n-points as xs:integer, $sequence as xs:integer*, $p as xs:double, $gain-f as function(xs:double) as xs:double ) as map(xs:string,item()*)* { let $rotation := math:pi() div 2 (: angular rotation for each arc :) let $z0 := z:complex(0,0) (: initial starting point :) let $θ := (0, $rotation) (: initial θ range :) let $n := count($sequence) - 1 return fold-left(1 to $n, ($θ, $z0, ()), function ($data as item()*, $k as xs:integer) as item()* { let $θ := (head($data), head(tail($data))) let $z0 := head(tail(tail($data))) let $zstep := tail(tail(tail($data))) let $r := $sequence[$k] let $c4th := this:complex-rounded-rec($r, $z0, $θ, $p, $n-points, $gain-f) return ( if ($k < $n) then ( let $rnext := $sequence[$k + 1] let $z0 := $c4th[last()]=>z:sub(z:complex-rφ($rnext, $θ[2])) let $θ := $θ!(. + $rotation) return ($θ, $z0, ($zstep, tail($c4th))) ) else ( ($θ, $z0, ($zstep, tail($c4th), $c4th[last()])) ) ) })=>tail()=>tail()=>tail() }; (:~ : pseudospiral-triangles() : Construct gnomic tiling triangles following the pseudospiral path : defined by the radius sequence. Result is a set of polygons, preceded, : if debug is true(), by the path of the outer controlling spiral. : : @param $sequence: sequence of radii :) declare function this:pseudospiral-triangles( $sequence as xs:integer*, (: driver sequence :) $debug as xs:boolean ) as map(xs:string,item()*)* { let $kmax := count($sequence) - 1 let $θ1 := math:sqrt(3) div 2 let $θ := math:pi() div 3 (: Trapezoidal gnomons :) let $gnomons := ( let $ztri := array { z:as-complex(0), z:complex(-1, $θ1)=>z:times(0.5), z:as-complex(-1), z:as-complex(0), z:as-complex(0) } let $Z := array:for-each($ztri, function ($v as map(*)) as map(*) {$v=>z:times($sequence[1])}) return ( fold-left(1 to $kmax, ($ztri, $Z), function ($data as array(*)*, $k as xs:integer) as array(*)* { let $zprev := head($data) let $Z := tail($data) let $zgno := ( z:as-complex(0), z:as-complex(-$sequence[$k]), z:as-complex(-$sequence[$k])=>z:add( z:complex-rφ($sequence[$k + 1] - $sequence[$k], 4 * $θ) ), z:complex-rφ($sequence[$k + 1] - $sequence[$k], 5 * $θ), z:as-complex(0) ) let $znext := ( for $val in $zgno return ( $val=>z:multiply(z:complex-rφ(1, -2*($k - 1)*$θ)) ) ) let $znext := ( if ($k = 1) then ( array { for $v in $znext return $v=>z:sub($znext[1])=>z:add($zprev(1)) } ) else ( array { for $v in $znext return $v=>z:sub($znext[1])=>z:add($zprev(3)) } ) ) return ( $znext, ($Z, $znext) ) } )=>tail() ) ) (: 'super' triangles: previous triangle plus trapezoid :) let $triangles := ( path:polygon(geom:to-edges(( for $i in 1 to 4 return $gnomons[1]($i) )))=>map:put("pid", 1), path:triangle( $gnomons[2](4), $gnomons[1](2), $gnomons[2](3) )=>map:put("pid", 2), for $k in 3 to $kmax + 1 return ( path:triangle( $gnomons[$k](4), $gnomons[$k - 1](4), $gnomons[$k](3) )=>map:put("pid", $k) ) ) let $outer-spiral := ( let $n-points := 50 let $no-gain := function ($t as xs:double) as xs:double {1} let $z0 := point:centroid($gnomons[1]?*) let $θ := (7 * math:pi() div 6, math:pi() div 2) let $r := point:distance($z0, geom:vertices($triangles[1])[1]) return ( this:complex-circular-arc($r, $z0, $θ, $n-points, $no-gain), fold-left(2 to $kmax + 1, ($θ, ()), function ($data as item()*, $k as xs:integer) as item()* { let $θ := (head($data), head(tail($data))) let $zstep := tail(tail($data)) let $w := $triangles[$k] let $θ := $θ!(. + -2 * math:pi() div 3) let $z0 := point:centroid(geom:vertices($w)[position() < last()]) let $r := point:distance($z0, geom:vertices($triangles[$k])[1]) let $c4th := this:complex-circular-arc($r, $z0, $θ, $n-points, $no-gain) return ( ($θ, ($zstep, $c4th)) ) })=>tail()=>tail() ) ) return ( if ($debug) then path:path(geom:to-edges($outer-spiral)) else (), $triangles ) }; (:~ : circle-spiral() : Fill a circle with a spiral with the given number of turns. : : @param $circle: the circle to fill : @param $turns: how many turns of the spiral : @param $fineness: how many points per turn : @return simple path of the points :) declare function this:circle-spiral( $circle as map(xs:string,item()*), $turns as xs:double, $fineness as xs:integer ) as map(xs:string,item()*) { let $center := ellipse:center($circle) let $radius := ellipse:radius($circle) let $n-points := util:round($turns * $fineness) let $f := function ($t as xs:double) as map(xs:string,item()*) { point:destination( $center, 360 * ($t mod 1), $radius * ($t div $turns) ) } return ( curve:plot($n-points, $f, $turns, false(), false()) ) }; (:~ : circle-spiral() : Fill a circle with a spiral with the given number of turns using default : fineness (50). : : @param $circle: the circle to fill : @param $turns: how many turns of the spiral : @return simple path of the points :) declare function this:circle-spiral( $circle as map(xs:string,item()*), $turns as xs:double ) as map(xs:string,item()*) { this:circle-spiral($circle, $turns, 50) }; (:~ : bend() : Map a path onto a circular arc. Each point in the path is mapped to a point : an equivalent fraction along the arc. The y-axis of each input point is mapped : to a point perpendicular to the target arc at the corresponding point with the : distance from the arc being the distance of the input point from the midline : of the bounding box of the input path, scaled. This method is therefore most : appropriate for paths that are broadly horizontal. : : @param $path: input path : @param $center: center of circular arc : @param $radius: radius of circular arc : @param $arc: degrees of arc (starting at 0°) : @param $scaling: how much to scale the y distance : @return mapped points of the input path :) declare function this:bend( $path as map(xs:string,item()*), $center as map(xs:string,item()*), $radius as xs:double, $arc as xs:double, (: degrees :) $scaling as xs:double ) as map(xs:string,item()*)* { let $path := $path=>path:with-edge-ts() let $edge-ts := (0.0, $path("edge-ts")) let $points := path:vertices($path) let $n := count($points) let $bb := geom:bounding-box($points) let $mid-y := box:min-py($bb) + box:height($bb) div 2 for $p at $i in $points let $t := $edge-ts[$i] let $angle := $t * $arc let $d := $radius + (point:py($p) - $mid-y)*$scaling return $center=>point:destination($angle, $d) }; (:~ : bend-path() : Map one path onto another. Each point in the input path is mapped to a point : an equivalent fraction along the target path. The y-axis of each input point : is mapped to a point perpendicular to the target path at the corresponding point : with the distance from the path being the distance of the input point from the : midline of the bounding box of the input path, scaled. This method is therefore : most appropriate for paths that are broadly horizontal. : : @param $path: input path : @param $center: center of circular arc : @param $radius: radius of circular arc : @param $arc: degrees of arc (starting at 0°) : @param $scaling: how much to scale the y distance : @return mapped points of the input path :) declare function this:bend-path( $path as map(xs:string,item()*), $target-path as map(xs:string,item()*), $scaling as xs:double ) as map(xs:string,item()*)* { let $path := $path=>path:with-edge-ts() let $edge-ts := (0.0, $path("edge-ts")) let $points := path:vertices($path) let $target-path := $target-path=>path:with-edge-ts() let $n := count($points) let $bb := geom:bounding-box($points) let $mid-y := box:min-py($bb) + box:height($bb) div 2 for $p at $i in $points let $t := $edge-ts[$i] let $angle := $target-path=>geom:normal-angle($t) let $d := (point:py($p) - $mid-y)*$scaling return $target-path=>path:path-point($t)=>point:destination($angle, $d) }; (:~ : stroke-path() : Convert the path into a stroke that smoothly widens towards the middle : and tapers to the ends. : : @param $path: source path (Bézier ok, arcs not) : @param $fatness: how wide to get at the middle : @return polygon for the stroke :) declare function this:stroke-path( $path as map(xs:string,item()*), $fatness as xs:double ) as map(xs:string,item()*) { let $reverse := ( let $rev-path := path:reverse($path)=>path:with-edge-ts() let $edge-ts := (0.0, $rev-path("edge-ts")) for $edge at $i in path:edges($rev-path) let $nstart := geom:normal-angle($edge, 0.0) let $nend := geom:normal-angle($edge, 1.0) let $dstart := util:mix(0, $fatness, 0.5 - abs(0.5 - $edge-ts[$i])) let $dend := util:mix(0, $fatness, 0.5 - abs(0.5 - $edge-ts[$i + 1])) let $nmid := geom:normal-angle($edge, 0.5) let $dmid := avg(($dstart, $dend)) return ( switch(edge:kind($edge)) case "edge" return ( edge:edge( edge:start($edge)=>point:destination($nstart, $dstart), edge:end($edge)=>point:destination($nend, $dend) ) ) case "quad" return ( edge:quad( edge:start($edge)=>point:destination($nstart, $dstart), edge:end($edge)=>point:destination($nend, $dend), edge:controls($edge)[1]=>point:destination($nmid, $dmid) ) ) case "cubic" return ( edge:cubic( edge:start($edge)=>point:destination($nstart, $dstart), edge:end($edge)=>point:destination($nend, $dend), edge:controls($edge)[1]=>point:destination($nmid, $dmid), edge:controls($edge)[2]=>point:destination($nmid, $dmid) ) ) default return $edge ) ) return ( util:merge-into($path=>path:property-map(), path:polygon( (path:edges($path), $reverse) ) ) ) };