http://mathling.com/noise/noise-map  library module

http://mathling.com/noise/noise-map


Noise map builder. A noise map is a point matrix populated with values
from a noise function. Alternatively if you don't need point-based access
and you can contruct a static point map which is a simple array.

Port/refactor/expansion of https://github.com/razaekel/noise-rs

Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

April 2021
Status: Stable

Imports

http://mathling.com/core/array
import module namespace arr="http://mathling.com/core/array"
       at "../core/array.xqy"
http://mathling.com/noise/annotations
import module namespace ann="http://mathling.com/noise/annotations"
       at "../noise/annotations.xqy"
http://mathling.com/type/distribution
import module namespace dist="http://mathling.com/type/distribution"
       at "../types/distributions.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/matrix
import module namespace matrix="http://mathling.com/geometric/matrix"
       at "../geo/point-matrix.xqy"
http://mathling.com/type/space
import module namespace space="http://mathling.com/type/space"
       at "../types/space.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/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/core/vector
import module namespace v="http://mathling.com/core/vector"
       at "../core/vector.xqy"
http://mathling.com/noise/modifiers
import module namespace noise="http://mathling.com/noise/modifiers"
       at "../noise/modifiers.xqy"
http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy"

Functions

Function: flat
declare function flat($space as map(xs:string,item()*), $noise as item()) as map(*)


flat()
Construct a flat noise map covering the space.

Params
  • space as map(xs:string,item()*): space to compute noise over (canvas space)
  • noise as item(): 2D noise function
Returns
  • map(*): point map
declare function this:flat(
  $space as map(xs:string,item()*), 
  $noise as item() 
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat($space, $noise, box:rectangle(-1.0, -1.0, 1.0, 1.0), false())
}

Function: flat
declare function flat($space as map(xs:string,item()*), (: height X width :) $noise as item(), (: 2D noise function :) $bounds as map(xs:string,item()*)) as map(*)


flat()
Construct a flat noise map covering the space within the bounds.

Params
  • space as map(xs:string,item()*): space to compute noise over (canvas space)
  • noise as item(): 2D noise function
  • bounds as map(xs:string,item()*): the scaled bounds to map, e.g. generally the double unit square
Returns
  • map(*): point map
declare function this:flat(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 2D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat($space, $noise, $bounds, false())
}

Function: flat
declare function flat($space as map(xs:string,item()*), $noise as item(), $bounds as map(xs:string,item()*), $seamless as xs:boolean) as map(*)


flat()
Construct a flat noise map.

Params
  • space as map(xs:string,item()*): the point extent height x width
  • noise as item(): the 2D noise function to map
  • bounds as map(xs:string,item()*): the scaled bounds to map, e.g. generally the double unit square
  • seamless as xs:boolean: whether to smooth out the mapping
Returns
  • map(*)
declare function this:flat(
  $space as map(xs:string,item()*),
  $noise as item(),
  $bounds as map(xs:string,item()*),
  $seamless as xs:boolean
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := space:width($space) cast as xs:integer
  let $height := space:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  return (
    fold-left(
      0 to $height,
      matrix:matrix($space),
      function($matrix as map(*), $y as xs:integer) {
        let $curr-y := $min-y + $y-step * $y
        return (
          fold-left(
            0 to $width,
            $matrix,
            function($matrix as map(*), $x as xs:integer) {
              let $curr-x := $min-x + $x-step * $x
              let $final-value :=
                if ($seamless) then (
                  let $sw-value := $noisefn(($curr-x, $curr-y))
                  let $se-value := $noisefn(($curr-x + $x-extent, $curr-y))
                  let $nw-value := $noisefn(($curr-x, $curr-y + $y-extent))
                  let $ne-value := $noisefn(($curr-x + $x-extent, $curr-y + $y-extent))
                  let $x-blend := 1 - ($curr-x - $min-x) div $x-extent
                  let $y-blend := 1 - ($curr-y - $min-y) div $y-extent
                  return
                    noise:linear(
                      noise:linear($sw-value, $se-value, $x-blend),
                      noise:linear($nw-value, $ne-value, $x-blend),
                      $y-blend
                    )
                ) else (
                  $noisefn(($curr-x, $curr-y))
                )
              return $matrix=>matrix:put(point:point($x,$y), $final-value)
            }
          )
        )
      }
    )
  )
}

Function: flat-static
declare function flat-static($space as map(xs:string,item()*), $noise as item()) as map(*)


flat-static()
Construct a flat-static noise map covering the space.

Params
  • space as map(xs:string,item()*): space to compute noise over (canvas space)
  • noise as item(): 2D noise function
Returns
  • map(*): point map
declare function this:flat-static(
  $space as map(xs:string,item()*), 
  $noise as item() 
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat-static($space, $noise, box:rectangle(-1.0, -1.0, 1.0, 1.0), false())
}

Function: flat-static
declare function flat-static($space as map(xs:string,item()*), (: height X width :) $noise as item(), (: 2D noise function :) $bounds as map(xs:string,item()*)) as map(*)


flat-static()
Construct a flat-static noise map covering the space within the bounds.

Params
  • space as map(xs:string,item()*): space to compute noise over (canvas space)
  • noise as item(): 2D noise function
  • bounds as map(xs:string,item()*): the scaled bounds to map, e.g. generally the double unit square
Returns
  • map(*): point map
declare function this:flat-static(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 2D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat-static($space, $noise, $bounds, false())
}

Function: flat-static
declare function flat-static($space as map(xs:string,item()*), $noise as item(), $bounds as map(xs:string,item()*), $seamless as xs:boolean) as map(*)


flat-static()
Construct a flat noise array.

Params
  • space as map(xs:string,item()*): the point extent height x width
  • noise as item(): the 2D noise function to map
  • bounds as map(xs:string,item()*): the scaled bounds to map, e.g. generally the double unit square
  • seamless as xs:boolean: whether to smooth out the mapping
Returns
  • map(*)
declare function this:flat-static(
  $space as map(xs:string,item()*),
  $noise as item(),
  $bounds as map(xs:string,item()*),
  $seamless as xs:boolean
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := space:width($space) cast as xs:integer
  let $height := space:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  let $data := (
    for $y in 0 to $height
    let $curr-y := $min-y + $y-step * $y
    for $x in 0 to $width
    let $curr-x := $min-x + $x-step * $x
    let $final-value :=
      if ($seamless) then (
        let $sw-value := $noisefn(($curr-x, $curr-y))
        let $se-value := $noisefn(($curr-x + $x-extent, $curr-y))
        let $nw-value := $noisefn(($curr-x, $curr-y + $y-extent))
        let $ne-value := $noisefn(($curr-x + $x-extent, $curr-y + $y-extent))
        let $x-blend := 1 - ($curr-x - $min-x) div $x-extent
        let $y-blend := 1 - ($curr-y - $min-y) div $y-extent
        return
          noise:linear(
            noise:linear($sw-value, $se-value, $x-blend),
            noise:linear($nw-value, $ne-value, $x-blend),
            $y-blend
          )
      ) else (
        $noisefn(($curr-x, $curr-y))
      )
    return $final-value
  )
  return arr:array($height + 1, $width + 1, $data)
}

Function: sphere
declare function sphere($space as map(xs:string,item()*), (: height X width :) $noise as item(), (: 3D noise function :) $bounds as map(xs:string,item()*)) as map(*)


sphere()
Construct a spherical noise map.

Params
  • space as map(xs:string,item()*): the point extent height x width
  • noise as item(): the noise function to map
  • bounds as map(xs:string,item()*): the scaled bounds to map, e.g. generally the double unit square
Returns
  • map(*)
declare function this:sphere(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 3D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := box:width($space) cast as xs:integer
  let $height := box:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  return (
    fold-left(
      0 to $height,
      matrix:matrix($space),
      function($matrix as map(*), $y as xs:integer) {
        let $curr-y := $min-y + $y-step * $y
        return (
          fold-left(
            0 to $width,
            $matrix,
            function($matrix as map(*), $x as xs:integer) {
              let $curr-x := $min-x + $x-step * $x
              let $xyz-point := this:to-xyz(($curr-x, $curr-y))
              let $final-value := $noisefn($xyz-point)
              return $matrix=>matrix:put(point:point($x,$y), $final-value)
            }
          )
        )
      }
    )
  )
}

Function: sphere-array
declare function sphere-array($space as map(xs:string,item()*), (: height X width :) $noise as item(), (: 3D noise function :) $bounds as map(xs:string,item()*)) as map(*)


sphere-static()
Construct a spherical noise array

Params
  • space as map(xs:string,item()*): the point extent height x width
  • noise as item(): the noise function to map
  • bounds as map(xs:string,item()*): the scaled bounds to map, e.g. generally the double unit square
Returns
  • map(*): noise array
declare function this:sphere-array(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 3D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := box:width($space) cast as xs:integer
  let $height := box:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  let $data := (
    for $y in 0 to $height
    let $curr-y := $min-y + $y-step * $y
    let $x := 0 to $width
    let $curr-x := $min-x + $x-step * $x
    let $xyz-point := this:to-xyz(($curr-x, $curr-y))
    return $noisefn($xyz-point)
  )
  return (
    arr:array($height + 1, $width + 1, $data)
  )
}

Original Source Code

xquery version "3.1";
(:~
 : Noise map builder. A noise map is a point matrix populated with values
 : from a noise function. Alternatively if you don't need point-based access
 : and you can contruct a static point map which is a simple array.
 :
 : Port/refactor/expansion of https://github.com/razaekel/noise-rs
 :
 : Copyright© Mary Holstege 2020-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since April 2021
 : @custom:Status Stable
 :)
module namespace this="http://mathling.com/noise/noise-map";

declare namespace art="http://mathling.com/art";
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";

import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy";
import module namespace v="http://mathling.com/core/vector"
       at "../core/vector.xqy";
import module namespace arr="http://mathling.com/core/array"
       at "../core/array.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 space="http://mathling.com/type/space"
       at "../types/space.xqy";
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy";
import module namespace geom="http://mathling.com/geometric"
       at "../geo/euclidean.xqy";
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy";
import module namespace box="http://mathling.com/geometric/rectangle"
       at "../geo/rectangle.xqy";
import module namespace matrix="http://mathling.com/geometric/matrix"
       at "../geo/point-matrix.xqy";
import module namespace ann="http://mathling.com/noise/annotations"
       at "../noise/annotations.xqy";
import module namespace noise="http://mathling.com/noise/modifiers"
       at "../noise/modifiers.xqy";

(:~
 : flat()
 : Construct a flat noise map covering the space.
 : @param $space: space to compute noise over (canvas space)
 : @param $noise: 2D noise function
 : @return point map
 :)
declare function this:flat(
  $space as map(xs:string,item()*), 
  $noise as item() 
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat($space, $noise, box:rectangle(-1.0, -1.0, 1.0, 1.0), false())
};

(:~
 : flat()
 : Construct a flat noise map covering the space within the bounds.
 : @param $space: space to compute noise over (canvas space)
 : @param $bounds: the scaled bounds to map, e.g. generally the double unit square
 : @param $noise: 2D noise function
 : @return point map
 :)
declare function this:flat(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 2D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat($space, $noise, $bounds, false())
};

(:~
 : flat()
 : Construct a flat noise map.
 :
 : @param $space: the point extent height x width
 : @param $noise: the 2D noise function to map
 : @param $bounds: the scaled bounds to map, e.g. generally the double unit square
 : @param $seamless: whether to smooth out the mapping
 :)
declare function this:flat(
  $space as map(xs:string,item()*),
  $noise as item(),
  $bounds as map(xs:string,item()*),
  $seamless as xs:boolean
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := space:width($space) cast as xs:integer
  let $height := space:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  return (
    fold-left(
      0 to $height,
      matrix:matrix($space),
      function($matrix as map(*), $y as xs:integer) {
        let $curr-y := $min-y + $y-step * $y
        return (
          fold-left(
            0 to $width,
            $matrix,
            function($matrix as map(*), $x as xs:integer) {
              let $curr-x := $min-x + $x-step * $x
              let $final-value :=
                if ($seamless) then (
                  let $sw-value := $noisefn(($curr-x, $curr-y))
                  let $se-value := $noisefn(($curr-x + $x-extent, $curr-y))
                  let $nw-value := $noisefn(($curr-x, $curr-y + $y-extent))
                  let $ne-value := $noisefn(($curr-x + $x-extent, $curr-y + $y-extent))
                  let $x-blend := 1 - ($curr-x - $min-x) div $x-extent
                  let $y-blend := 1 - ($curr-y - $min-y) div $y-extent
                  return
                    noise:linear(
                      noise:linear($sw-value, $se-value, $x-blend),
                      noise:linear($nw-value, $ne-value, $x-blend),
                      $y-blend
                    )
                ) else (
                  $noisefn(($curr-x, $curr-y))
                )
              return $matrix=>matrix:put(point:point($x,$y), $final-value)
            }
          )
        )
      }
    )
  )
};

(:~
 : flat-static()
 : Construct a flat-static noise map covering the space.
 : @param $space: space to compute noise over (canvas space)
 : @param $noise: 2D noise function
 : @return point map
 :)
declare function this:flat-static(
  $space as map(xs:string,item()*), 
  $noise as item() 
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat-static($space, $noise, box:rectangle(-1.0, -1.0, 1.0, 1.0), false())
};

(:~
 : flat-static()
 : Construct a flat-static noise map covering the space within the bounds.
 : @param $space: space to compute noise over (canvas space)
 : @param $bounds: the scaled bounds to map, e.g. generally the double unit square
 : @param $noise: 2D noise function
 : @return point map
 :)
declare function this:flat-static(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 2D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  this:flat-static($space, $noise, $bounds, false())
};

(:~
 : flat-static()
 : Construct a flat  noise array.
 :
 : @param $space: the point extent height x width
 : @param $noise: the 2D noise function to map
 : @param $bounds: the scaled bounds to map, e.g. generally the double unit square
 : @param $seamless: whether to smooth out the mapping
 :)
declare function this:flat-static(
  $space as map(xs:string,item()*),
  $noise as item(),
  $bounds as map(xs:string,item()*),
  $seamless as xs:boolean
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := space:width($space) cast as xs:integer
  let $height := space:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  let $data := (
    for $y in 0 to $height
    let $curr-y := $min-y + $y-step * $y
    for $x in 0 to $width
    let $curr-x := $min-x + $x-step * $x
    let $final-value :=
      if ($seamless) then (
        let $sw-value := $noisefn(($curr-x, $curr-y))
        let $se-value := $noisefn(($curr-x + $x-extent, $curr-y))
        let $nw-value := $noisefn(($curr-x, $curr-y + $y-extent))
        let $ne-value := $noisefn(($curr-x + $x-extent, $curr-y + $y-extent))
        let $x-blend := 1 - ($curr-x - $min-x) div $x-extent
        let $y-blend := 1 - ($curr-y - $min-y) div $y-extent
        return
          noise:linear(
            noise:linear($sw-value, $se-value, $x-blend),
            noise:linear($nw-value, $ne-value, $x-blend),
            $y-blend
          )
      ) else (
        $noisefn(($curr-x, $curr-y))
      )
    return $final-value
  )
  return arr:array($height + 1, $width + 1, $data)
};

(:~
 : sphere()
 : Construct a spherical noise map.
 :
 : @param $space: the point extent height x width
 : @param $noise: the noise function to map
 : @param $bounds: the scaled bounds to map, e.g. generally the double unit square
 :)
declare function this:sphere(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 3D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := box:width($space) cast as xs:integer
  let $height := box:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  return (
    fold-left(
      0 to $height,
      matrix:matrix($space),
      function($matrix as map(*), $y as xs:integer) {
        let $curr-y := $min-y + $y-step * $y
        return (
          fold-left(
            0 to $width,
            $matrix,
            function($matrix as map(*), $x as xs:integer) {
              let $curr-x := $min-x + $x-step * $x
              let $xyz-point := this:to-xyz(($curr-x, $curr-y))
              let $final-value := $noisefn($xyz-point)
              return $matrix=>matrix:put(point:point($x,$y), $final-value)
            }
          )
        )
      }
    )
  )
};

(:~
 : sphere-static()
 : Construct a spherical noise array
 :
 : @param $space: the point extent height x width
 : @param $noise: the noise function to map
 : @param $bounds: the scaled bounds to map, e.g. generally the double unit square
 : @return noise array
 :)
declare function this:sphere-array(
  $space as map(xs:string,item()*),  (: height X width :)
  $noise as item(), (: 3D noise function :)
  $bounds as map(xs:string,item()*) (: scaled :)
) as map(*)
{
  if (not(ann:is-noise($noise))) then errors:error("ML-BADARGS", ("noise", $noise)) else (),
  if (not($space("kind")=("space","box"))) then errors:error("ML-BADARGS", ("space", $space)) else (),
  if (not($bounds("kind")=("space","box"))) then errors:error("ML-BADARGS", ("bounds", $bounds)) else (),
  let $noisefn := noise:as-vector($noise)=>ann:function()
  let $width := box:width($space) cast as xs:integer
  let $height := box:height($space) cast as xs:integer
  let $x-extent := box:width($bounds)
  let $y-extent := box:height($bounds)
  let $x-step := $x-extent div $width
  let $y-step := $y-extent div $height
  let $min-x := box:min-px($bounds)
  let $min-y := box:min-py($bounds)
  let $data := (
    for $y in 0 to $height
    let $curr-y := $min-y + $y-step * $y
    let $x := 0 to $width
    let $curr-x := $min-x + $x-step * $x
    let $xyz-point := this:to-xyz(($curr-x, $curr-y))
    return $noisefn($xyz-point)
  )
  return (
    arr:array($height + 1, $width + 1, $data)
  )
};

declare %private function this:to-xyz($point as xs:double*) as xs:double*
{
  let $lat-radians := util:radians(v:py($point))
  let $long-radians := util:radians(v:px($point))
  let $r := math:cos($lat-radians)
  return (
    $r * math:cos($long-radians),
    math:sin($lat-radians),
    $r * math:sin($long-radians)
  )
};