xquery version "3.1"; (:~ : Port/refactor of implementation of basicmulti fractal noise from : https://github.com/razaekel/noise-rs : : Noise function that outputs heterogenous Multifractal noise. : : This is a multifractal method, meaning that it has a fractal dimension : that varies with location. : : The result of this multifractal method is that in areas near zero, higher : frequencies will be heavily damped, resulting in the terrain remaining : smooth. As the value moves further away from zero, higher frequencies will : not be as damped and thus will grow more jagged as iteration progresses. : : Copyright© Mary Holstege 2020-2024 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since April 2021 : @custom:Status Stable :) module namespace this="http://mathling.com/noise/fractal/basic-multi"; declare namespace art="http://mathling.com/art"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace v="http://mathling.com/core/vector" at "../core/vector.xqy"; import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"; import module namespace dist="http://mathling.com/type/distribution" at "../types/distributions.xqy"; import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"; import module namespace ann="http://mathling.com/noise/annotations" at "../noise/annotations.xqy"; import module namespace perlin="http://mathling.com/noise/perlin" at "../noise/perlin.xqy"; (:====================================================================== : Context : "octaves": : Total number of frequency octaves to generate the noise with. : The number of octaves control the amount of detail in the noise : function. Adding more octaves increases the detail, with the drawback : of increasing the calculation time. : "frequency": : The number of cycles per unit length that the noise function outputs. : "lacunarity": : A multiplier that determines how quickly the frequency increases for : each successive octave in the noise function. : The frequency of each successive octave is equal to the product of the : previous octave's frequency and the lacunarity value. : A lacunarity of 2.0 results in the frequency doubling every octave. For : almost all cases, 2.0 is a good value to use. : "persistence": : A multiplier that determines how quickly the amplitudes diminish for : each successive octave in the noise function. : The amplitude of each successive octave is equal to the product of the : previous octave's amplitude and the persistence value. Increasing the : persistence produces "rougher" noise. :======================================================================:) declare variable $this:MAX-OCTAVES as xs:integer := 32; declare variable $this:DEFAULT-OCTAVES as xs:integer := 6; declare variable $this:DEFAULT-FREQUENCY as xs:double := xs:double(2.0); declare variable $this:DEFAULT-LACUNARITY as xs:double := 2 * math:pi() div 3; declare variable $this:DEFAULT-PERSISTENCE as xs:double := xs:double(0.5); declare function this:context() as map(xs:string, item()*) { map { "dimension": 2, "octaves": $this:DEFAULT-OCTAVES, "frequency": $this:DEFAULT-FREQUENCY, "lacunarity": $this:DEFAULT-LACUNARITY, "persistence": $this:DEFAULT-PERSISTENCE, "sources": for $i in 1 to $this:DEFAULT-OCTAVES return ( perlin:noise2-vector(perlin:context())=>ann:function() ) } }; declare function this:dimension($context as map(xs:string, item()*), $dimension as xs:integer) as map(xs:string, item()*) { if ($dimension = $context("dimension")) then $context else let $octaves := $context=>map:get("octaves") return $context=> map:put("dimension", $dimension)=> map:put("sources", switch($dimension) case 2 return for $i in 1 to $octaves return perlin:noise2-vector(perlin:context())=>ann:function() case 3 return for $i in 1 to $octaves return perlin:noise3-vector(perlin:context())=>ann:function() case 4 return for $i in 1 to $octaves return perlin:noise4-vector(perlin:context())=>ann:function() default return util:assert(false(), "Illegal dimensionality") ) }; declare function this:octaves($context as map(xs:string, item()*), $octaves as xs:integer) as map(xs:string, item()*) { let $octaves := util:clamp($octaves, 1, $this:MAX-OCTAVES) cast as xs:integer let $dimension := $context=>map:get("dimension") return $context=> map:put("octaves", $octaves)=> map:put("sources", switch($dimension) case 2 return for $i in 1 to $octaves return perlin:noise2-vector(perlin:context())=>ann:function() case 3 return for $i in 1 to $octaves return perlin:noise3-vector(perlin:context())=>ann:function() case 4 return for $i in 1 to $octaves return perlin:noise4-vector(perlin:context())=>ann:function() default return util:assert(false(), "Illegal dimensionality") ) }; declare function this:frequency($context as map(xs:string, item()*), $frequency as xs:double) as map(xs:string, item()*) { $context=>map:put("frequency", $frequency) }; declare function this:lacunarity($context as map(xs:string, item()*), $lacunarity as xs:double) as map(xs:string, item()*) { $context=>map:put("lacunarity", $lacunarity) }; declare function this:persistence($context as map(xs:string, item()*), $persistence as xs:double) as map(xs:string, item()*) { $context=>map:put("persistence", $persistence) }; (:~ : noise() : Basic multi-fractal noise: dimensionality is in context :) declare %art:noise function this:noise( $context as map(xs:string, item()*) ) as map(*) { ann:f("basic:noise", function($point as map(xs:string,item()*)) as xs:double { this:basic(point:pcoordinates($point), $context) }, $context=>map:remove("sources") ) }; (:~ : noise-vector() : Basic multi-fractal noise: dimensionality is in context :) declare %art:noise function this:noise-vector( $context as map(xs:string, item()*) ) as map(*) { ann:fv("basic:noise", function($point as xs:double*) as xs:double { this:basic($point, $context) }, $context=>map:remove("sources") ) }; (:~ : noise2() : 2D basic multi-fractal noise :) declare %art:noise function this:noise2( $context as map(xs:string, item()*) ) as map(*) { ann:f("basic:noise2", function($point as map(xs:string,item()*)) as xs:double { this:basic(point:pcoordinates($point, 2), $context) }, $context=>map:remove("sources") ) }; (:~ : noise2-vector() : 2D basic multi-fractal noise :) declare %art:noise function this:noise2-vector( $context as map(xs:string, item()*) ) as map(*) { ann:fv("basic:noise2", function($point as xs:double*) as xs:double { this:basic(v:as-dimension($point, 2), $context) }, $context=>map:remove("sources") ) }; (:~ : noise3() : 3D basic multi-fractal noise :) declare %art:noise function this:noise3( $context as map(xs:string, item()*) ) as map(*) { ann:f("basic:noise3", function($point as map(xs:string,item()*)) as xs:double { this:basic(point:pcoordinates($point, 3), $context) }, $context=>map:remove("sources") ) }; (:~ : noise3-vector() : 3D basic multi-fractal noise :) declare %art:noise function this:noise3-vector( $context as map(xs:string, item()*) ) as map(*) { ann:fv("basic:noise3", function($point as xs:double*) as xs:double { this:basic(v:as-dimension($point, 3), $context) }, $context=>map:remove("sources") ) }; (:~ : noise4() : 3D basic multi-fractal noise :) declare %art:noise function this:noise4( $context as map(xs:string, item()*) ) as map(*) { ann:f("basic:noise4", function($point as map(xs:string,item()*)) as xs:double { this:basic(point:pcoordinates($point, 4), $context) }, $context=>map:remove("sources") ) }; (:~ : noise4-vector() : 3D basic multi-fractal noise :) declare %art:noise function this:noise4-vector( $context as map(xs:string, item()*) ) as map(*) { ann:fv("basic:noise4", function($point as xs:double*) as xs:double { this:basic(v:as-dimension($point, 4), $context) }, $context=>map:remove("sources") ) }; (: dimension-specific functions are just wrappers :) declare %private function this:basic( $pcoords as xs:double*, $context as map(xs:string, item()*) ) as xs:double { let $f := map:get($context, "sources") let $frequency := map:get($context, "frequency") let $octaves := map:get($context, "octaves") let $lacunarity := map:get($context, "lacunarity") let $persistence := map:get($context, "persistence") (: First unscaled octave of function; later octaves are scaled. :) let $pcoords := $pcoords=>v:times($frequency) let $result := $f[1]($pcoords) let $data := fold-left( 2 to $octaves, ($result, $pcoords), function($data as item()*, $octave as xs:integer) as item()* { (: Unmarshalling :) let $result := head($data) let $pcoords := tail($data) (: Raise spatial frequency :) let $pcoords := $pcoords=>v:times($lacunarity) let $signal := (: Computed noise value :) $f[$octave]($pcoords) * (: Scale the amplitude appropriately for this frequency. :) math:pow($persistence, $octave) * (: Scale the signal by the current 'altitude' of the function. :) $result return ( (: Add signal to result :) $result + $signal, $pcoords ) } ) let $result := head($data) (: Scale result to [-1,1] range :) return $result * 0.5 }; (: basic-multi :)