http://mathling.com/shape/patterns library module
http://mathling.com/shape/patterns
Various simple patterns.
Copyright© Mary Holstege 2022-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Active
Imports
http://mathling.com/geometric/delaunayimport module namespace delaunay="http://mathling.com/geometric/delaunay" at "../geo/delaunay.xqy"http://mathling.com/type/reach
import module namespace reach="http://mathling.com/type/reach" at "../types/reach.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/type/slot
import module namespace slot="http://mathling.com/type/slot" at "../types/slot.xqy"http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"http://mathling.com/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/geometric/ellipse
import module namespace ellipse="http://mathling.com/geometric/ellipse" at "../geo/ellipse.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"
Functions
Function: hvpath
declare function hvpath($moves as xs:integer*) as map(xs:string,item()*)
declare function hvpath($moves as xs:integer*) as map(xs:string,item()*)
Flip-invert-reverse
A path of alternating horizonal and vertical distances
Apply a series of mutations (flip/invert/reverse)
Draw with various symmetries
Params
- moves as xs:integer*
Returns
- map(xs:string,item()*)
declare function this:hvpath( $moves as xs:integer* (: distances :) ) as map(xs:string,item()*) { map { "kind": "hv-path", "dirs": for $move in 1 to count($moves) return if ($move mod 2 = 1) then "h" else "v" , "moves": $moves , "draw": this:hvdraw#3 } }
Function: flip
declare function flip($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
declare function flip($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- hvpath as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:flip($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { $hvpath=>map:put("moves", for $move at $i in $hvpath("moves") return -1 * $move ) }
Function: flipv
declare function flipv($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
declare function flipv($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- hvpath as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:flipv($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { let $dirs := $hvpath("dirs") return ( $hvpath=>map:put("moves", for $move at $i in $hvpath("moves") return if ($dirs[$i]="h") then $move else -1 * $move ) ) }
Function: fliph
declare function fliph($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
declare function fliph($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- hvpath as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:fliph($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { let $dirs := $hvpath("dirs") return ( $hvpath=>map:put("moves", for $move at $i in $hvpath("moves") return if ($dirs[$i]="v") then $move else -1 * $move ) ) }
Function: invert
declare function invert($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
declare function invert($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- hvpath as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:invert($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { $hvpath=>map:put("dirs", for $dir at $i in $hvpath("dirs") return if ($dir="v") then "h" else "v" ) }
Function: reverse
declare function reverse($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
declare function reverse($hvpath as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- hvpath as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:reverse($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { $hvpath=> map:put("dirs", reverse($hvpath("dirs")))=> map:put("moves", reverse($hvpath("moves"))) }
Function: mutate
declare function mutate($hvpath as map(xs:string,item()*),
$operations as xs:string*) as map(xs:string,item()*)
declare function mutate($hvpath as map(xs:string,item()*), $operations as xs:string*) as map(xs:string,item()*)
Params
- hvpath as map(xs:string,item()*)
- operations as xs:string*
Returns
- map(xs:string,item()*)
declare function this:mutate( $hvpath as map(xs:string,item()*), $operations as xs:string* ) as map(xs:string,item()*) { fold-left($operations, $hvpath, function($hvpath as map(xs:string,item()*), $op as xs:string) as map(xs:string,item()*) { switch ($op) case "flip" return this:flip($hvpath) case "flipv" return this:flipv($hvpath) case "fliph" return this:fliph($hvpath) case "invert" return this:invert($hvpath) case "reverse" return this:reverse($hvpath) default return $hvpath } ) }
Function: hvdraw
declare function hvdraw($hvpath as map(xs:string,item()*),
$properties as map(xs:string,item()*),
$drawing as map(xs:string,function(*)?))
declare function hvdraw($hvpath as map(xs:string,item()*), $properties as map(xs:string,item()*), $drawing as map(xs:string,function(*)?))
Params
- hvpath as map(xs:string,item()*)
- properties as map(xs:string,item()*)
- drawing as map(xs:string,function(*)?)
declare function this:hvdraw( $hvpath as map(xs:string,item()*), $properties as map(xs:string,item()*), $drawing as map(xs:string,function(*)?) ) { let $style-properties := util:merge-into(( $drawing("draw:svg-style")($properties), $drawing("draw:svg-style")(util:exclude($hvpath, ("kind","dirs","moves","start"))) )) let $start := head(($hvpath("start"), $point:ORIGIN)) let $d := "M "||point:px($start)||" "||point:py($start)|| string-join( let $moves := $hvpath("moves") for $d at $i in $hvpath("dirs") return $d||" "||$moves[$i] ) return <svg:path d="{$d}" class="path">{ util:as-attributes($style-properties) }</svg:path> }
Function: hvpoints
declare function hvpoints($hvpaths as map(xs:string,item()*)*) as map(xs:string,item()*)*
declare function hvpoints($hvpaths as map(xs:string,item()*)*) as map(xs:string,item()*)*
Params
- hvpaths as map(xs:string,item()*)*
Returns
- map(xs:string,item()*)*
declare function this:hvpoints( $hvpaths as map(xs:string,item()*)* ) as map(xs:string,item()*)* { for $hvpath in $hvpaths return if ($hvpath("kind") = "slot") then this:hvpoints(slot:body($hvpath)) else let $moves := $hvpath("moves") let $dirs := $hvpath("dirs") let $start := head(($hvpath("start"), $point:ORIGIN)) return ( fold-left(1 to count($moves), $start, function($points as map(xs:string,item()*)*, $i as xs:integer) { $points, point:destination($points[last()], if ($dirs[$i]="h") then 0 else 90, $moves[$i] ) } ) ) }
Function: fir
declare function fir($start as map(xs:string,item()*),
$moves as xs:integer*,
$operations as xs:string*,
$symmetries as xs:integer*,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)
declare function fir($start as map(xs:string,item()*), $moves as xs:integer*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*)) as map(xs:string,item()*)
fir()
Create a flip-invert-reverse pattern. Each pattern starts with a sequence of
alternating horizontal and vertical lines of the given lengths. Then we apply
a sequence of operations to it. Finally we render out symmetric versions of
the line also.
Params
- start as map(xs:string,item()*): starting point
- moves as xs:integer*: sequence of lengths
- operations as xs:string*: sequence of operations reverse: reverse the sequence of steps invert: horizontal steps <=> vertical steps fliph: reverse the direction of horizonal steps flipv: reverse the direction of vertical steps flip: reverse the direction of all steps
- symmetries as xs:integer*: a sequence of numbers in the range 3 to 8; if the given number is in the sequence, we get that symmetry variant included. We always get symmetry variants 1 (base path) and 2 (base path with operations applied) 3: 1+flipv 4: 2+flipv 5: 1+fliph 6: 2+fliph 7: 1+flip 8: 2+flip
- properties as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:fir( $start as map(xs:string,item()*), $moves as xs:integer*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) (: slot :) { let $base-path := this:hvpath($moves) let $p1 := $base-path=>map:put("start", $start) let $p2 := this:mutate($p1, $operations) let $p3 := if ($symmetries=3) then this:mutate($p1, "flipv") else () let $p4 := if ($symmetries=4) then this:mutate($p2, "flipv") else () let $p5 := if ($symmetries=5) then this:mutate($p1, "fliph") else () let $p6 := if ($symmetries=6) then this:mutate($p2, "fliph") else () let $p7 := if ($symmetries=7) then this:mutate($p1, "flip") else () let $p8 := if ($symmetries=8) then this:mutate($p2, "flip") else () return ( slot:slot( ($p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8)=>util:with-properties($properties) ) ) }
Function: bounded-fir
declare function bounded-fir($bounds as map(xs:string,item()*), (: bounding box :)
$moves as xs:numeric*,
$operations as xs:string*,
$symmetries as xs:integer*,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function bounded-fir($bounds as map(xs:string,item()*), (: bounding box :) $moves as xs:numeric*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- bounds as map(xs:string,item()*)
- moves as xs:numeric*
- operations as xs:string*
- symmetries as xs:integer*
- properties as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:bounded-fir( $bounds as map(xs:string,item()*), (: bounding box :) $moves as xs:numeric*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: clip def + slot with clip-path :) { let $reach := reach:reach(rand:id("clip"), $bounds) let $scaling := box:diagonal($bounds) div count($moves) let $moves := $moves!(util:round(. * $scaling)) return ( $reach, this:fir(box:center($bounds), $moves, $operations, $symmetries, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: bounded-fir
declare function bounded-fir($bounds as map(xs:string,item()*), (: bounding box :)
$moves as xs:numeric*,
$operations as xs:string*,
$symmetries as xs:integer*,
$properties as map(xs:string,item()*),
$clip-id as xs:string?) as map(xs:string,item()*)*
declare function bounded-fir($bounds as map(xs:string,item()*), (: bounding box :) $moves as xs:numeric*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*), $clip-id as xs:string?) as map(xs:string,item()*)*
Params
- bounds as map(xs:string,item()*)
- moves as xs:numeric*
- operations as xs:string*
- symmetries as xs:integer*
- properties as map(xs:string,item()*)
- clip-id as xs:string?
Returns
- map(xs:string,item()*)*
declare function this:bounded-fir( $bounds as map(xs:string,item()*), (: bounding box :) $moves as xs:numeric*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: clip def + slot with clip-path :) { let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) let $scaling := box:diagonal($bounds) div count($moves) let $moves := $moves!(util:round(. * $scaling)) return ( $reach, this:fir(box:center($bounds), $moves, $operations, $symmetries, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: nested-arcs
declare function nested-arcs($start as map(xs:string,item()*),
$n-arcs as xs:integer,
$n-starts as xs:integer,
$n-lines as xs:integer,
$scaling as xs:integer,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)
declare function nested-arcs($start as map(xs:string,item()*), $n-arcs as xs:integer, $n-starts as xs:integer, $n-lines as xs:integer, $scaling as xs:integer, $properties as map(xs:string,item()*)) as map(xs:string,item()*)
nested-arcs()
Params
- start as map(xs:string,item()*): starting point (minimum point of arrangement)
- n-arcs as xs:integer: number of nested arcs in each piece of design
- n-starts as xs:integer: number of the largest arc on each line
- n-lines as xs:integer: number of lines of arcs
- scaling as xs:integer: size of arcs
- properties as map(xs:string,item()*): drawing properties for the arcs: you'll want to set fill=background for best look
Returns
- map(xs:string,item()*)
declare function this:nested-arcs( $start as map(xs:string,item()*), $n-arcs as xs:integer, $n-starts as xs:integer, $n-lines as xs:integer, $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) (: slot with clip-path :) { let $arc-scales := util:linspace($n-arcs + 1, 0, 1)=>tail()=>reverse()=>tail() return ( slot:slot( for $line in 1 to $n-lines let $offset := if ($line mod 2 = 0) then -1 else 0 for $st in 1 to $n-starts - $offset let $start-pt := $start=> geom:translate(($st - 1) * (2 * $scaling) + $offset * $scaling, $line * $scaling) let $end-pt := $start-pt=>geom:translate(3 * $scaling, 0) let $mid-pt := $start-pt=>geom:translate(1.5 * $scaling, -$scaling) let $scale-pt := $start-pt=>geom:translate(1.5 * $scaling, 0) let $circle := ellipse:circle-from($start-pt, $end-pt, $mid-pt) let $arc := edge:arc(ellipse:center($circle), ellipse:radius($circle), $start-pt, $end-pt, false(), false())=> geom:with-properties($properties) order by $st mod 2 return ( $arc=>geom:snap(), for $arc-scale in $arc-scales return $arc=>geom:scale($arc-scale, $arc-scale, $scale-pt)=>geom:snap() ) ) ) }
Function: bounded-nested-arcs
declare function bounded-nested-arcs($n-arcs as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$properties as map(xs:string,item()*),
$clip-id as xs:string?) as map(xs:string,item()*)*
declare function bounded-nested-arcs($n-arcs as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*), $clip-id as xs:string?) as map(xs:string,item()*)*
Params
- n-arcs as xs:integer
- bounds as map(xs:string,item()*)
- scaling as xs:integer
- properties as map(xs:string,item()*)
- clip-id as xs:string?
Returns
- map(xs:string,item()*)*
declare function this:bounded-nested-arcs( $n-arcs as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) return ( $reach, this:nested-arcs(box:min-point($bounds), $n-arcs, $n-starts, $n-lines, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: bounded-nested-arcs
declare function bounded-nested-arcs($n-arcs as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function bounded-nested-arcs($n-arcs as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- n-arcs as xs:integer
- bounds as map(xs:string,item()*)
- scaling as xs:integer
- properties as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:bounded-nested-arcs( $n-arcs as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $reach := reach:reach(rand:id("clip"), $bounds) return ( $reach, this:nested-arcs(box:min-point($bounds), $n-arcs, $n-starts, $n-lines, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: diamonds
declare function diamonds($start as map(xs:string,item()*),
$n-diamonds as xs:integer,
$n-starts as xs:integer,
$n-lines as xs:integer,
$scaling as xs:integer,
$alternating as xs:boolean,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)
declare function diamonds($start as map(xs:string,item()*), $n-diamonds as xs:integer, $n-starts as xs:integer, $n-lines as xs:integer, $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*)) as map(xs:string,item()*)
diamonds()
Like nested-arcs with straight lines
Params
- start as map(xs:string,item()*): starting point (minimum point of arrangement)
- n-diamonds as xs:integer: number of nested angles in each piece of design
- n-starts as xs:integer: number of the largest angled line on each line
- n-lines as xs:integer: number of lines of angles
- scaling as xs:integer: size of angles
- alternating as xs:boolean: set to false() to get a different (but nice) variation of the pattern: instead of nested diamonds you get some overlaps
- properties as map(xs:string,item()*): drawing properties for the angles
Returns
- map(xs:string,item()*)
declare function this:diamonds( $start as map(xs:string,item()*), $n-diamonds as xs:integer, $n-starts as xs:integer, $n-lines as xs:integer, $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) (: slot with clip-path :) { let $diamond-scales := util:linspace($n-diamonds + 1, 0, 1)=>tail()=>reverse()=>tail() return ( slot:slot( for $line in 1 to $n-lines let $offset := if ($line mod 2 = 0) then -1 else 0 for $st in 1 to $n-starts - $offset (: Keeping same start point for every line makes nice intersection pattern :) let $start-pt := if ($line mod 2 = 1 or not($alternating)) then ( $start=> geom:translate(($st - 1) * (2 * $scaling) + $offset * $scaling, $line * $scaling) ) else ( $start=> geom:translate(($st - 1) * (2 * $scaling) + $offset * $scaling, ($line - 1) * $scaling) ) let $end-pt := $start-pt=>geom:translate(3 * $scaling, 0) let $mid-pt := if ($line mod 2 = 1) then $start-pt=>geom:translate(1.5 * $scaling, -$scaling) else $start-pt=>geom:translate(1.5 * $scaling, $scaling) let $scale-pt := $start-pt=>geom:translate(1.5 * $scaling, 0) let $diamond := path:path(( edge:edge($start-pt, $mid-pt), edge:edge($mid-pt, $end-pt) ))=>geom:with-properties($properties) order by $st return ( $diamond=>geom:snap(), for $diamond-scale in $diamond-scales return $diamond=>geom:scale($diamond-scale, $diamond-scale, $scale-pt)=>geom:snap() ) ) ) }
Function: bounded-diamonds
declare function bounded-diamonds($n-diamonds as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$alternating as xs:boolean,
$properties as map(xs:string,item()*),
$clip-id as xs:string?) as map(xs:string,item()*)*
declare function bounded-diamonds($n-diamonds as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*), $clip-id as xs:string?) as map(xs:string,item()*)*
Params
- n-diamonds as xs:integer
- bounds as map(xs:string,item()*)
- scaling as xs:integer
- alternating as xs:boolean
- properties as map(xs:string,item()*)
- clip-id as xs:string?
Returns
- map(xs:string,item()*)*
declare function this:bounded-diamonds( $n-diamonds as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) return ( $reach, this:diamonds(box:min-point($bounds), $n-diamonds, $n-starts, $n-lines, $scaling, $alternating, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: bounded-diamonds
declare function bounded-diamonds($n-diamonds as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$alternating as xs:boolean,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function bounded-diamonds($n-diamonds as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- n-diamonds as xs:integer
- bounds as map(xs:string,item()*)
- scaling as xs:integer
- alternating as xs:boolean
- properties as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:bounded-diamonds( $n-diamonds as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $reach := reach:reach(rand:id("clip"), $bounds) return ( $reach, this:diamonds(box:min-point($bounds), $n-diamonds, $n-starts, $n-lines, $scaling, $alternating, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: voronoi
declare function voronoi($n-repetitions as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)
declare function voronoi($n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*)) as map(xs:string,item()*)
voronoi()
Nested random Voronoi regions.
Params
- n-repetitions as xs:integer: number of nestings of each region
- bounds as map(xs:string,item()*): boundary in which the points are placed
- scaling as xs:integer: basic scaling of the regions
- properties as map(xs:string,item()*): drawing properties for the regions
Returns
- map(xs:string,item()*)
declare function this:voronoi( $n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) { let $region-scales := util:linspace($n-repetitions + 1, 0, 1)=>tail()=>reverse()=>tail() let $n-points := 1 + box:width($bounds) idiv $scaling let $points := geom:random-points-in($n-points, $bounds) let $regions := delaunay:voronoi($points, $bounds) return ( slot:slot( for $region in $regions let $center := point:centroid(geom:vertices($region)) return ( $region=>geom:snap()=>geom:with-properties($properties), for $region-scale in $region-scales return ( $region=>geom:scale($region-scale, $region-scale, $center)=> geom:snap()=>geom:with-properties($properties) ) ) ) ) }
Function: bounded-voronoi
declare function bounded-voronoi($n-repetitions as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$properties as map(xs:string,item()*),
$clip-id as xs:string?) as map(xs:string,item()*)*
declare function bounded-voronoi($n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*), $clip-id as xs:string?) as map(xs:string,item()*)*
Params
- n-repetitions as xs:integer
- bounds as map(xs:string,item()*)
- scaling as xs:integer
- properties as map(xs:string,item()*)
- clip-id as xs:string?
Returns
- map(xs:string,item()*)*
declare function this:bounded-voronoi( $n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: reach + slot :) { let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) return ( $reach, this:voronoi($n-repetitions, $bounds, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Function: bounded-voronoi
declare function bounded-voronoi($n-repetitions as xs:integer,
$bounds as map(xs:string,item()*), (: bounding box :)
$scaling as xs:integer,
$properties as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function bounded-voronoi($n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- n-repetitions as xs:integer
- bounds as map(xs:string,item()*)
- scaling as xs:integer
- properties as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:bounded-voronoi( $n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: reach + slot :) { let $reach := reach:reach(rand:id("clip"), $bounds) return ( $reach, this:voronoi($n-repetitions, $bounds, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }
Original Source Code
xquery version "3.1"; (:~ : Various simple patterns. : : Copyright© Mary Holstege 2022-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since February 2023 : @custom:Status Active :) module namespace this="http://mathling.com/shape/patterns"; 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 rand="http://mathling.com/core/random" at "../core/random.xqy"; import module namespace reach="http://mathling.com/type/reach" at "../types/reach.xqy"; import module namespace slot="http://mathling.com/type/slot" at "../types/slot.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 ellipse="http://mathling.com/geometric/ellipse" at "../geo/ellipse.xqy"; import module namespace path="http://mathling.com/geometric/path" at "../geo/path.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; import module namespace delaunay="http://mathling.com/geometric/delaunay" at "../geo/delaunay.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"; (:~ : Flip-invert-reverse : A path of alternating horizonal and vertical distances : Apply a series of mutations (flip/invert/reverse) : Draw with various symmetries :) declare function this:hvpath( $moves as xs:integer* (: distances :) ) as map(xs:string,item()*) { map { "kind": "hv-path", "dirs": for $move in 1 to count($moves) return if ($move mod 2 = 1) then "h" else "v" , "moves": $moves , "draw": this:hvdraw#3 } }; declare function this:flip($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { $hvpath=>map:put("moves", for $move at $i in $hvpath("moves") return -1 * $move ) }; declare function this:flipv($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { let $dirs := $hvpath("dirs") return ( $hvpath=>map:put("moves", for $move at $i in $hvpath("moves") return if ($dirs[$i]="h") then $move else -1 * $move ) ) }; declare function this:fliph($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { let $dirs := $hvpath("dirs") return ( $hvpath=>map:put("moves", for $move at $i in $hvpath("moves") return if ($dirs[$i]="v") then $move else -1 * $move ) ) }; declare function this:invert($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { $hvpath=>map:put("dirs", for $dir at $i in $hvpath("dirs") return if ($dir="v") then "h" else "v" ) }; declare function this:reverse($hvpath as map(xs:string,item()*)) as map(xs:string,item()*) { $hvpath=> map:put("dirs", reverse($hvpath("dirs")))=> map:put("moves", reverse($hvpath("moves"))) }; declare function this:mutate( $hvpath as map(xs:string,item()*), $operations as xs:string* ) as map(xs:string,item()*) { fold-left($operations, $hvpath, function($hvpath as map(xs:string,item()*), $op as xs:string) as map(xs:string,item()*) { switch ($op) case "flip" return this:flip($hvpath) case "flipv" return this:flipv($hvpath) case "fliph" return this:fliph($hvpath) case "invert" return this:invert($hvpath) case "reverse" return this:reverse($hvpath) default return $hvpath } ) }; declare function this:hvdraw( $hvpath as map(xs:string,item()*), $properties as map(xs:string,item()*), $drawing as map(xs:string,function(*)?) ) { let $style-properties := util:merge-into(( $drawing("draw:svg-style")($properties), $drawing("draw:svg-style")(util:exclude($hvpath, ("kind","dirs","moves","start"))) )) let $start := head(($hvpath("start"), $point:ORIGIN)) let $d := "M "||point:px($start)||" "||point:py($start)|| string-join( let $moves := $hvpath("moves") for $d at $i in $hvpath("dirs") return $d||" "||$moves[$i] ) return <svg:path d="{$d}" class="path">{ util:as-attributes($style-properties) }</svg:path> }; declare function this:hvpoints( $hvpaths as map(xs:string,item()*)* ) as map(xs:string,item()*)* { for $hvpath in $hvpaths return if ($hvpath("kind") = "slot") then this:hvpoints(slot:body($hvpath)) else let $moves := $hvpath("moves") let $dirs := $hvpath("dirs") let $start := head(($hvpath("start"), $point:ORIGIN)) return ( fold-left(1 to count($moves), $start, function($points as map(xs:string,item()*)*, $i as xs:integer) { $points, point:destination($points[last()], if ($dirs[$i]="h") then 0 else 90, $moves[$i] ) } ) ) }; (:~ : fir() : Create a flip-invert-reverse pattern. Each pattern starts with a sequence of : alternating horizontal and vertical lines of the given lengths. Then we apply : a sequence of operations to it. Finally we render out symmetric versions of : the line also. : : @param $start: starting point : @param $moves: sequence of lengths : @param $operations: sequence of operations : reverse: reverse the sequence of steps : invert: horizontal steps <=> vertical steps : fliph: reverse the direction of horizonal steps : flipv: reverse the direction of vertical steps : flip: reverse the direction of all steps : @param $symmetries: a sequence of numbers in the range 3 to 8; if the given number is : in the sequence, we get that symmetry variant included. We always get symmetry : variants 1 (base path) and 2 (base path with operations applied) : 3: 1+flipv 4: 2+flipv : 5: 1+fliph 6: 2+fliph : 7: 1+flip 8: 2+flip :) declare function this:fir( $start as map(xs:string,item()*), $moves as xs:integer*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) (: slot :) { let $base-path := this:hvpath($moves) let $p1 := $base-path=>map:put("start", $start) let $p2 := this:mutate($p1, $operations) let $p3 := if ($symmetries=3) then this:mutate($p1, "flipv") else () let $p4 := if ($symmetries=4) then this:mutate($p2, "flipv") else () let $p5 := if ($symmetries=5) then this:mutate($p1, "fliph") else () let $p6 := if ($symmetries=6) then this:mutate($p2, "fliph") else () let $p7 := if ($symmetries=7) then this:mutate($p1, "flip") else () let $p8 := if ($symmetries=8) then this:mutate($p2, "flip") else () return ( slot:slot( ($p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8)=>util:with-properties($properties) ) ) }; declare function this:bounded-fir( $bounds as map(xs:string,item()*), (: bounding box :) $moves as xs:numeric*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: clip def + slot with clip-path :) { let $reach := reach:reach(rand:id("clip"), $bounds) let $scaling := box:diagonal($bounds) div count($moves) let $moves := $moves!(util:round(. * $scaling)) return ( $reach, this:fir(box:center($bounds), $moves, $operations, $symmetries, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; declare function this:bounded-fir( $bounds as map(xs:string,item()*), (: bounding box :) $moves as xs:numeric*, $operations as xs:string*, $symmetries as xs:integer*, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: clip def + slot with clip-path :) { let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) let $scaling := box:diagonal($bounds) div count($moves) let $moves := $moves!(util:round(. * $scaling)) return ( $reach, this:fir(box:center($bounds), $moves, $operations, $symmetries, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; (: : Nested arcs : Overlapping sets of nested arcs :) (:~ : nested-arcs() : : @param $start: starting point (minimum point of arrangement) : @param $n-arcs: number of nested arcs in each piece of design : @param $n-starts: number of the largest arc on each line : @param $n-lines: number of lines of arcs : @param $scaling: size of arcs : @param $properties: drawing properties for the arcs: you'll want to set fill=background : for best look :) declare function this:nested-arcs( $start as map(xs:string,item()*), $n-arcs as xs:integer, $n-starts as xs:integer, $n-lines as xs:integer, $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) (: slot with clip-path :) { let $arc-scales := util:linspace($n-arcs + 1, 0, 1)=>tail()=>reverse()=>tail() return ( slot:slot( for $line in 1 to $n-lines let $offset := if ($line mod 2 = 0) then -1 else 0 for $st in 1 to $n-starts - $offset let $start-pt := $start=> geom:translate(($st - 1) * (2 * $scaling) + $offset * $scaling, $line * $scaling) let $end-pt := $start-pt=>geom:translate(3 * $scaling, 0) let $mid-pt := $start-pt=>geom:translate(1.5 * $scaling, -$scaling) let $scale-pt := $start-pt=>geom:translate(1.5 * $scaling, 0) let $circle := ellipse:circle-from($start-pt, $end-pt, $mid-pt) let $arc := edge:arc(ellipse:center($circle), ellipse:radius($circle), $start-pt, $end-pt, false(), false())=> geom:with-properties($properties) order by $st mod 2 return ( $arc=>geom:snap(), for $arc-scale in $arc-scales return $arc=>geom:scale($arc-scale, $arc-scale, $scale-pt)=>geom:snap() ) ) ) }; declare function this:bounded-nested-arcs( $n-arcs as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) return ( $reach, this:nested-arcs(box:min-point($bounds), $n-arcs, $n-starts, $n-lines, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; declare function this:bounded-nested-arcs( $n-arcs as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $reach := reach:reach(rand:id("clip"), $bounds) return ( $reach, this:nested-arcs(box:min-point($bounds), $n-arcs, $n-starts, $n-lines, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; (: : Diamond patterns :) (:~ : diamonds() : Like nested-arcs with straight lines : : @param $start: starting point (minimum point of arrangement) : @param $n-diamonds: number of nested angles in each piece of design : @param $n-starts: number of the largest angled line on each line : @param $n-lines: number of lines of angles : @param $scaling: size of angles : @param $alternating: set to false() to get a different (but nice) variation of the pattern: : instead of nested diamonds you get some overlaps : @param $properties: drawing properties for the angles :) declare function this:diamonds( $start as map(xs:string,item()*), $n-diamonds as xs:integer, $n-starts as xs:integer, $n-lines as xs:integer, $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) (: slot with clip-path :) { let $diamond-scales := util:linspace($n-diamonds + 1, 0, 1)=>tail()=>reverse()=>tail() return ( slot:slot( for $line in 1 to $n-lines let $offset := if ($line mod 2 = 0) then -1 else 0 for $st in 1 to $n-starts - $offset (: Keeping same start point for every line makes nice intersection pattern :) let $start-pt := if ($line mod 2 = 1 or not($alternating)) then ( $start=> geom:translate(($st - 1) * (2 * $scaling) + $offset * $scaling, $line * $scaling) ) else ( $start=> geom:translate(($st - 1) * (2 * $scaling) + $offset * $scaling, ($line - 1) * $scaling) ) let $end-pt := $start-pt=>geom:translate(3 * $scaling, 0) let $mid-pt := if ($line mod 2 = 1) then $start-pt=>geom:translate(1.5 * $scaling, -$scaling) else $start-pt=>geom:translate(1.5 * $scaling, $scaling) let $scale-pt := $start-pt=>geom:translate(1.5 * $scaling, 0) let $diamond := path:path(( edge:edge($start-pt, $mid-pt), edge:edge($mid-pt, $end-pt) ))=>geom:with-properties($properties) order by $st return ( $diamond=>geom:snap(), for $diamond-scale in $diamond-scales return $diamond=>geom:scale($diamond-scale, $diamond-scale, $scale-pt)=>geom:snap() ) ) ) }; declare function this:bounded-diamonds( $n-diamonds as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) return ( $reach, this:diamonds(box:min-point($bounds), $n-diamonds, $n-starts, $n-lines, $scaling, $alternating, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; declare function this:bounded-diamonds( $n-diamonds as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $alternating as xs:boolean, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: reach + slot :) { let $n-starts := 1 + box:width($bounds) idiv ($scaling * 2) let $n-lines := 1 + box:height($bounds) idiv $scaling let $reach := reach:reach(rand:id("clip"), $bounds) return ( $reach, this:diamonds(box:min-point($bounds), $n-diamonds, $n-starts, $n-lines, $scaling, $alternating, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; (: : Voronoi :) (:~ : voronoi() : Nested random Voronoi regions. : : @param $n-repetitions: number of nestings of each region : @param $bounds: boundary in which the points are placed : @param $scaling: basic scaling of the regions : @param $properties: drawing properties for the regions :) declare function this:voronoi( $n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*) { let $region-scales := util:linspace($n-repetitions + 1, 0, 1)=>tail()=>reverse()=>tail() let $n-points := 1 + box:width($bounds) idiv $scaling let $points := geom:random-points-in($n-points, $bounds) let $regions := delaunay:voronoi($points, $bounds) return ( slot:slot( for $region in $regions let $center := point:centroid(geom:vertices($region)) return ( $region=>geom:snap()=>geom:with-properties($properties), for $region-scale in $region-scales return ( $region=>geom:scale($region-scale, $region-scale, $center)=> geom:snap()=>geom:with-properties($properties) ) ) ) ) }; declare function this:bounded-voronoi( $n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*), $clip-id as xs:string? ) as map(xs:string,item()*)* (: reach + slot :) { let $clip-id := ($clip-id, rand:id("clip"))[1] let $reach := reach:reach($clip-id, $bounds) return ( $reach, this:voronoi($n-repetitions, $bounds, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) }; declare function this:bounded-voronoi( $n-repetitions as xs:integer, $bounds as map(xs:string,item()*), (: bounding box :) $scaling as xs:integer, $properties as map(xs:string,item()*) ) as map(xs:string,item()*)* (: reach + slot :) { let $reach := reach:reach(rand:id("clip"), $bounds) return ( $reach, this:voronoi($n-repetitions, $bounds, $scaling, $properties)=> geom:with-properties( map {"clip-path": reach:ref($reach)} ) ) };