http://mathling.com/shape/grid  library module

http://mathling.com/shape/grid

Simple rectangular and hexagonal grids.

February 2023
Status: Active

Imports

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

Functions

Function: slots-per-linedeclare function slots-per-line(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*)) as xs:integer

Params
• slot as map(xs:string,item()*)
• canvas as map(xs:string,item()*)
Returns
• xs:integer
declare function this:slots-per-line(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*)) as xs:integer
{
let \$slot-width := box:width(\$slot)
let \$slot-edge := box:edge(\$slot)
let \$canvas-width := box:width(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$end-x := \$canvas-edge idiv \$slot-width
let \$max-slots := \$canvas-width idiv (\$slot-width + \$slot-edge)
return \$max-slots - \$end-x
}

Function: lines-per-canvasdeclare function lines-per-canvas(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*)) as xs:integer

Params
• slot as map(xs:string,item()*)
• canvas as map(xs:string,item()*)
Returns
• xs:integer
declare function this:lines-per-canvas(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*)) as xs:integer
{
let \$slot-height := box:height(\$slot)
let \$slot-edge := box:edge(\$slot)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$end-y := \$canvas-edge idiv \$slot-height
let \$max-lines := \$canvas-height idiv (\$slot-height + \$slot-edge)
return \$max-lines - \$end-y
}

Function: arraydeclare function array(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*))

array()
Create a rectangular array

Params
• slot as map(xs:string,item()*): slot canvas with height/width parameters
• canvas as map(xs:string,item()*): array canvas with height/width/edge parameters
declare function this:array(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*))
{
let \$slot-width := box:width(\$slot)
let \$slot-height := box:height(\$slot)
let \$slot-edge := box:edge(\$slot)
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$max-lines := (\$canvas-height - 2*\$canvas-edge) idiv (\$slot-height + \$slot-edge)
let \$max-slots := (\$canvas-width - 2*\$canvas-edge) idiv (\$slot-width + \$slot-edge)
return this:array(\$max-slots, \$max-lines, \$slot-edge, \$canvas)
}

Function: arraydeclare function array(\$max-slots as xs:integer, \$max-lines as xs:integer, \$slot-edge as xs:double, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*

array()
Construct an NxM array of points within the canvas, separated by the edge

Params
• max-slots as xs:integer: number of columns
• max-lines as xs:integer: number of rows
• slot-edge as xs:double: extra spacing between points
• canvas as map(xs:string,item()*): space to fill
Returns
• map(xs:string,item()*)*
declare function this:array(
\$max-slots as xs:integer,
\$max-lines as xs:integer,
\$slot-edge as xs:double,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$slot-width := ((\$canvas-width - 2*\$canvas-edge - (\$max-slots - 1)*\$slot-edge) idiv \$max-slots)
let \$slot-height := ((\$canvas-height - 2*\$canvas-edge - (\$max-lines - 1)*\$slot-edge) idiv \$max-lines)
(: Divide up the canvas into lines and slots, skipping over the edges :)
let \$start-x := \$canvas-edge idiv \$slot-width
let \$start-y := \$canvas-edge idiv \$slot-height
let \$end-x := \$canvas-edge idiv \$slot-width
let \$end-y := \$canvas-edge idiv \$slot-height
let \$x-offset := \$min-x + \$slot-edge*\$start-x
let \$y-offset := \$min-y + \$slot-edge*\$start-y
for \$line in \$start-y to \$max-lines - (1 + \$end-y)
let \$trans-y := \$y-offset + \$line * (\$slot-height + \$slot-edge)
for \$slot in \$start-x to \$max-slots - (1 + \$end-x)
let \$trans-x := \$x-offset + \$slot * (\$slot-width + \$slot-edge)
return (
point:point(\$trans-x,\$trans-y)
)
}

Function: array-rectanglesdeclare function array-rectangles(\$max-slots as xs:integer, \$max-lines as xs:integer, \$slot-edge as xs:integer, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*

array-rectangles()
Construct an NxM array of rectangles within the canvas, separated by the edge
Like array() except we return the rectangles, not the grid points.
We return the inner rectangles (with the gaps between them) as polygons.

Params
• max-slots as xs:integer: number of columns
• max-lines as xs:integer: number of rows
• slot-edge as xs:integer: extra spacing between rectangles
• canvas as map(xs:string,item()*): space to fill
Returns
• map(xs:string,item()*)*
declare function this:array-rectangles(
\$max-slots as xs:integer,
\$max-lines as xs:integer,
\$slot-edge as xs:integer,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$slot-width := (\$canvas-width - 2*\$canvas-edge - (\$max-slots - 1)*\$slot-edge) idiv \$max-slots
let \$slot-height := (\$canvas-height - 2*\$canvas-edge - (\$max-lines - 1)*\$slot-edge) idiv \$max-lines
(: Divide up the canvas into lines and slots, skipping over the edges :)
let \$start-x := \$canvas-edge idiv \$slot-width
let \$start-y := \$canvas-edge idiv \$slot-height
let \$end-x := \$canvas-edge idiv \$slot-width
let \$end-y := \$canvas-edge idiv \$slot-height
let \$x-offset := \$min-x + \$slot-edge*\$start-x
let \$y-offset := \$min-y + \$slot-edge*\$start-y
for \$line in \$start-y to \$max-lines - (1 + \$end-y)
let \$y1 := \$y-offset + \$line * (\$slot-height + \$slot-edge)
let \$y2 := \$y-offset + (\$line + 1) * (\$slot-height + \$slot-edge) - \$slot-edge
for \$slot in \$start-x to \$max-slots - (1 + \$end-x)
let \$x1 := \$x-offset + \$slot * (\$slot-width + \$slot-edge)
let \$x2 := \$x-offset + (\$slot + 1) * (\$slot-width + \$slot-edge) - \$slot-edge
return (
path:polygon(
box:edges(box:box(\$x1, \$y1, \$x2, \$y2))
)
)
}

Function: array-boxesdeclare function array-boxes(\$max-slots as xs:integer, \$max-lines as xs:integer, \$slot-edge as xs:integer, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*

array-boxes()
Construct an NxM array of boxes within the canvas, separated by the edge
Like array() except we return the rectangles, not the grid points.
Like array-rectangles() except we return the rectangles as boxes not polygons.

Params
• max-slots as xs:integer: number of columns
• max-lines as xs:integer: number of rows
• slot-edge as xs:integer: extra spacing between rectangles
• canvas as map(xs:string,item()*): space to fill
Returns
• map(xs:string,item()*)*
declare function this:array-boxes(
\$max-slots as xs:integer,
\$max-lines as xs:integer,
\$slot-edge as xs:integer,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$slot-width := (\$canvas-width - 2*\$canvas-edge - (\$max-slots - 1)*\$slot-edge) idiv \$max-slots
let \$slot-height := (\$canvas-height - 2*\$canvas-edge - (\$max-lines - 1)*\$slot-edge) idiv \$max-lines
(: Divide up the canvas into lines and slots, skipping over the edges :)
let \$start-x := \$canvas-edge idiv \$slot-width
let \$start-y := \$canvas-edge idiv \$slot-height
let \$end-x := \$canvas-edge idiv \$slot-width
let \$end-y := \$canvas-edge idiv \$slot-height
let \$x-offset := \$min-x + \$slot-edge*\$start-x
let \$y-offset := \$min-y + \$slot-edge*\$start-y
for \$line in \$start-y to \$max-lines - (1 + \$end-y)
let \$y1 := \$y-offset + \$line * (\$slot-height + \$slot-edge)
let \$y2 := \$y-offset + (\$line + 1) * (\$slot-height + \$slot-edge) - \$slot-edge
for \$slot in \$start-x to \$max-slots - (1 + \$end-x)
let \$x1 := \$x-offset + \$slot * (\$slot-width + \$slot-edge)
let \$x2 := \$x-offset + (\$slot + 1) * (\$slot-width + \$slot-edge) - \$slot-edge
return (
box:box(\$x1, \$y1, \$x2, \$y2)
)
}

Function: griddeclare function grid(\$grid-size as xs:double, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*

grid()
Create a set of grid lines with the given scale. Useful for debugging.

Params
• grid-size as xs:double: Spacing between grid lines
• canvas as map(xs:string,item()*): grid canvas with height/width parameters
Returns
• map(xs:string,item()*)*
declare function this:grid(\$grid-size as xs:double, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$max-lines := \$canvas-height idiv \$grid-size
let \$max-slots := \$canvas-width idiv \$grid-size
return (
for \$slot in 0 to \$max-slots - 1 return (
edge:edge(
point:point(\$slot*\$grid-size, 0),
point:point(\$slot*\$grid-size, \$canvas-height)
)
)
,
for \$line in 0 to \$max-lines - 1 return (
edge:edge(
point:point(0, \$line * \$grid-size),
point:point(\$canvas-width, \$line * \$grid-size)
)
)
)
}

Function: hex-griddeclare function hex-grid(\$radius as xs:double, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*

hex-grid()
Return the centers of hexagons in a hexagonal gridding of the space.
Center-to-center horizontally in a row = 3r
Vertical offset from row to row = ½r√3
Horizonal offset from row to row = 3r/2

Params
• canvas as map(xs:string,item()*)
Returns
• map(xs:string,item()*)*
declare function this:hex-grid(
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$max-rows as xs:integer :=
let \$max-columns as xs:integer :=
1 + (box:width(\$canvas) - 2*box:edge(\$canvas)) idiv (3*\$radius)
for \$row in 1 to \$max-rows
let \$y := box:edge(\$canvas) + box:min-y(\$canvas) + \$row * math:sqrt(3) * \$radius div 2
let \$current-max :=
if (\$row mod 2 = 0) then \$max-columns - 1 else \$max-columns
let \$start-offset :=
box:edge(\$canvas) + box:min-x(\$canvas) + \$radius + (if (\$row mod 2 = 0) then 3*\$radius div 2 else 0)
for \$column in 1 to \$current-max
let \$x := \$start-offset + (\$column - 1) * 3 * \$radius
return point:point(\$x, \$y)
}

Function: navigable-hex-griddeclare function navigable-hex-grid(\$radius as xs:double, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)

Params
• canvas as map(xs:string,item()*)
Returns
• map(xs:string,item()*)
declare function this:navigable-hex-grid(
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$rows as xs:integer :=
let \$columns as xs:integer :=
1 + (box:width(\$canvas) - 2*box:edge(\$canvas)) idiv (3*\$radius)
return (
map {
"kind": "hex-grid",
"rows": \$rows,
"columns": \$columns,
"points": \$points,
"n": count(\$points)
}
)
}

Function: hex-pointdeclare function hex-point(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as map(xs:string,item()*)?

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• map(xs:string,item()*)?
declare function this:hex-point(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as map(xs:string,item()*)?
{
let \$columns := \$hexgrid("columns")
let \$ix :=
if (\$row mod 2 = 0) then (
(\$row idiv 2) * \$columns + (\$row idiv 2 - 1) * (\$columns - 1) + \$column
) else (
(\$row idiv 2) * \$columns + (\$row idiv 2) * (\$columns - 1) + \$column
)
return \$hexgrid("points")[\$ix]
}

Function: hex-pointdeclare function hex-point(\$hexgrid as map(xs:string,item()*), \$coords as xs:integer*) as map(xs:string,item()*)?

Params
• hexgrid as map(xs:string,item()*)
• coords as xs:integer*
Returns
• map(xs:string,item()*)?
declare function this:hex-point(
\$hexgrid as map(xs:string,item()*),
\$coords as xs:integer*
) as map(xs:string,item()*)?
{
this:hex-point(\$hexgrid, \$coords[1], \$coords[2])
}

Function: hexagondeclare function hexagon(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as map(xs:string,item()*)?

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• map(xs:string,item()*)?
declare function this:hexagon(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as map(xs:string,item()*)?
{
let \$point := \$hexgrid=>this:hex-point(\$row, \$column)
where exists(\$point)
return (
path:polygon(
(
for \$angle in (0, 60, 120, 180, 240, 300) return (
)
)=>edge:to-edges()
)
)
}

Function: hex-pixdeclare function hex-pix(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer
declare function this:hex-pix(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer
{
let \$columns := \$hexgrid("columns")
let \$ix :=
if (\$row mod 2 = 0) then (
(\$row idiv 2) * \$columns + (\$row idiv 2 - 1) * (\$columns - 1) + \$column
) else (
(\$row idiv 2) * \$columns + (\$row idiv 2) * (\$columns - 1) + \$column
)
return \$ix
}

Function: hex-pixdeclare function hex-pix(\$hexgrid as map(xs:string,item()*), \$coords as xs:integer*) as xs:integer

Params
• hexgrid as map(xs:string,item()*)
• coords as xs:integer*
Returns
• xs:integer
declare function this:hex-pix(
\$hexgrid as map(xs:string,item()*),
\$coords as xs:integer*
) as xs:integer
{
this:hex-pix(\$hexgrid, \$coords[1], \$coords[2])
}

Function: down-leftdeclare function down-left(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer*

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer*
declare function this:down-left(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = \$hexgrid("rows")) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row + 1, \$column)) then ()
else (\$row + 1, \$column)
)
else (
if (\$column = 1) then ()
else if (this:excluded(\$hexgrid, \$row + 1, \$column - 1)) then ()
else (\$row + 1, \$column - 1)
)
}

Function: up-leftdeclare function up-left(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer*

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer*
declare function this:up-left(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = 1) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row - 1, \$column)) then ()
else (\$row - 1, \$column)
)
else (
if (\$column = 1) then ()
else if (this:excluded(\$hexgrid, \$row - 1, \$column - 1)) then ()
else (\$row - 1, \$column - 1)
)
}

Function: down-rightdeclare function down-right(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer*

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer*
declare function this:down-right(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = \$hexgrid("rows")) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row + 1, \$column + 1)) then ()
else (\$row + 1, \$column + 1)
) else (
if (\$column = \$hexgrid("columns")) then ()
else if (this:excluded(\$hexgrid, \$row + 1, \$column)) then ()
else (\$row + 1, \$column)
)
}

Function: up-rightdeclare function up-right(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer*

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer*
declare function this:up-right(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = 1) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row - 1, \$column + 1)) then ()
else (\$row - 1, \$column + 1)
) else (
if (\$column = \$hexgrid("columns")) then ()
else if (this:excluded(\$hexgrid, \$row - 1, \$column)) then ()
else (\$row - 1, \$column)
)
}

Function: down-straightdeclare function down-straight(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer*

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer*
declare function this:down-straight(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row >= \$hexgrid("rows") - 1) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row + 2, \$column)) then ()
else (\$row + 2, \$column)
)
else (
if (this:excluded(\$hexgrid, \$row + 2, \$column)) then ()
else (\$row + 2, \$column)
)
}

Function: up-straightdeclare function up-straight(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:integer*

Params
• hexgrid as map(xs:string,item()*)
• row as xs:integer
• column as xs:integer
Returns
• xs:integer*
declare function this:up-straight(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row <= 2) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row - 2, \$column)) then ()
else (\$row - 2, \$column)
)
else (
if (this:excluded(\$hexgrid, \$row - 2, \$column)) then ()
else (\$row - 2, \$column)
)
}

Function: hex-triadsdeclare function hex-triads(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as array(array(xs:integer))*

Get the triangles around the given point in the hex grid, returned
as an array of arrays of (row, column) grid references.

Params
• hexgrid as map(xs:string,item()*): the navigable hex grid
• row as xs:integer: target row in grid
• column as xs:integer: target column in grid
Returns
• array(array(xs:integer))*
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as array(array(xs:integer))*
{
(:
: Get the hex points around the focus:
:
:             1
:          6     2
:             *
:          5     3
:             4
:)
let \$v1 := \$hexgrid=>this:up-straight(\$row, \$column)
let \$v2 := \$hexgrid=>this:up-right(\$row, \$column)
let \$v3 := \$hexgrid=>this:down-right(\$row, \$column)
let \$v4 := \$hexgrid=>this:down-straight(\$row, \$column)
let \$v5 := \$hexgrid=>this:down-left(\$row, \$column)
let \$v6 := \$hexgrid=>this:up-left(\$row, \$column)
(:
: Now make triangles from the ones that exist
: These "triangles" are just the grid references in an array of pairs
: Order them so that the first vertex is the lowest one
:)
let \$focus := array {(\$row, \$column)}
return (
if (exists(\$v1)) then (
if (exists(\$v2)) then array{ \$focus, array {\$v2}, array {\$v1} } else (),
if (exists(\$v6)) then array{ \$focus, array {\$v1}, array {\$v6} } else ()
) else (),
if (exists(\$v3)) then (
if (exists(\$v2)) then array{ array {\$v3}, array {\$v2}, \$focus } else (),
if (exists(\$v4)) then array{ array {\$v4}, array {\$v3}, \$focus } else ()
) else (),
if (exists(\$v5)) then (
if (exists(\$v4)) then array{ array {\$v4}, \$focus, array {\$v5} } else (),
if (exists(\$v6)) then array{ array {\$v5}, \$focus, array {\$v6} } else ()
) else ()
)
}

Function: hex-subgriddeclare function hex-subgrid(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as map(xs:string,item()*)

hex-subgrid()
Construct a subgrid for the given hexagon in the grid. We will end up
with a couple of extra points in the first row to make the navigation
work properly: be sure to use excluded() or hex-points() to ignore them.

Params
• hexgrid as map(xs:string,item()*): the navigable hex grid
• row as xs:integer: target row in grid
• column as xs:integer: target column in grid
Returns
• map(xs:string,item()*)
declare function this:hex-subgrid(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as map(xs:string,item()*)
{
let \$center := this:hex-point(\$hexgrid, \$row, \$column)
return this:hex-subgrid(\$hexgrid, \$center)
}

Function: hex-subgriddeclare function hex-subgrid(\$hexgrid as map(xs:string,item()*), \$center as map(xs:string,item()*)) as map(xs:string,item()*)

hex-subgrid()
Construct a subgrid for the given hexagon in the grid. We will end up
with a couple of extra points in the first row to make the navigation
work properly: be sure to use excluded() or hex-points() to ignore them.

Params
• hexgrid as map(xs:string,item()*): the navigable hex grid
• center as map(xs:string,item()*): target point in grid
Returns
• map(xs:string,item()*)
declare function this:hex-subgrid(
\$hexgrid as map(xs:string,item()*),
\$center as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$v := \$radius * math:sqrt(3) div 2
let \$h := 3 * \$radius div 2
let \$points := (
\$center=>geom:translate(-\$h, -3 * \$v), (: outside grid :)
\$center=>geom:translate(\$h, -3 * \$v),  (: row 1: outside grid :)
\$center=>geom:translate(0, -2 * \$v),   (: row 2: first inside :)
\$center=>geom:translate(-\$h, -\$v),     (: row 3: inside :)
\$center=>geom:translate(\$h, -\$v),      (: row 3: inside :)
\$center,                               (: row 4: inside :)
\$center=>geom:translate(-\$h, \$v),      (: row 5: inside :)
\$center=>geom:translate(\$h, \$v),       (: row 5: inside :)
\$center=>geom:translate(0, 2 * \$v)     (: row 6: inside :)
)
return (
map {
"kind": "hex-grid",
"rows": 6,
"columns": 2,
"points": \$points=>geom:snap(),
"n": count(\$points) - 2,
"exclusions": (1, 2)
}
)
}

Function: excludeddeclare function excluded(\$hexgrid as map(xs:string,item()*), \$pix as xs:integer) as xs:boolean

excluded()
Is the point at this index excluded?

Params
• hexgrid as map(xs:string,item()*): the navigable hex grid
• pix as xs:integer: target point index
Returns
• xs:boolean
declare function this:excluded(
\$hexgrid as map(xs:string,item()*),
\$pix as xs:integer
) as xs:boolean
{
\$pix = \$hexgrid("exclusions") or \$pix > \$hexgrid("n")
}

Function: excludeddeclare function excluded(\$hexgrid as map(xs:string,item()*), \$row as xs:integer, \$column as xs:integer) as xs:boolean

excluded()
Is the point at this index excluded?

Params
• hexgrid as map(xs:string,item()*): the navigable hex grid
• row as xs:integer: target row in grid
• column as xs:integer: target column in grid
Returns
• xs:boolean
declare function this:excluded(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:boolean
{
let \$pix := this:hex-pix(\$hexgrid, \$row, \$column)
return \$pix = \$hexgrid("exclusions") or \$pix > \$hexgrid("n")
}

Function: hex-pointsdeclare function hex-points(\$hexgrid as map(xs:string,item()*)) as map(xs:string,item()*)*

hex-points()
The non-excluded points in the hex grid.

Params
• hexgrid as map(xs:string,item()*): the navigable hex grid
Returns
• map(xs:string,item()*)*
declare function this:hex-points(
\$hexgrid as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
\$hexgrid("points")[not(position() = \$hexgrid("exclusions"))]
}

Function: hexagonsdeclare function hexagons(\$hexgrid as map(xs:string,item()*)) as map(xs:string,item()*)*

hexagons()
Return the hexagon tiling associated with hexgrid.

Params
• hexgrid as map(xs:string,item()*) the hexagonal grid
Returns
• map(xs:string,item()*)*
declare function this:hexagons(
\$hexgrid as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$points := this:hex-points(\$hexgrid)
for \$point in \$points return (
path:polygon(
(
for \$angle in (0, 60, 120, 180, 240, 300) return (
)
)=>edge:to-edges()
)
)
}

Original Source Code

xquery version "3.1";
(:~
: Simple rectangular and hexagonal grids.
:
: @since February 2023
: @custom:Status Active
:)
module namespace this="http://mathling.com/shape/grid";

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

declare function this:slots-per-line(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*)) as xs:integer
{
let \$slot-width := box:width(\$slot)
let \$slot-edge := box:edge(\$slot)
let \$canvas-width := box:width(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$end-x := \$canvas-edge idiv \$slot-width
let \$max-slots := \$canvas-width idiv (\$slot-width + \$slot-edge)
return \$max-slots - \$end-x
};

declare function this:lines-per-canvas(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*)) as xs:integer
{
let \$slot-height := box:height(\$slot)
let \$slot-edge := box:edge(\$slot)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$end-y := \$canvas-edge idiv \$slot-height
let \$max-lines := \$canvas-height idiv (\$slot-height + \$slot-edge)
return \$max-lines - \$end-y
};

(:~
: array()
: Create a rectangular array
:
: @param \$slot: slot canvas with height/width parameters
: @param \$canvas: array canvas with height/width/edge parameters
:)
declare function this:array(\$slot as map(xs:string,item()*), \$canvas as map(xs:string,item()*))
{
let \$slot-width := box:width(\$slot)
let \$slot-height := box:height(\$slot)
let \$slot-edge := box:edge(\$slot)
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$max-lines := (\$canvas-height - 2*\$canvas-edge) idiv (\$slot-height + \$slot-edge)
let \$max-slots := (\$canvas-width - 2*\$canvas-edge) idiv (\$slot-width + \$slot-edge)
return this:array(\$max-slots, \$max-lines, \$slot-edge, \$canvas)
};

(:~
: array()
: Construct an NxM array of points within the canvas, separated by the edge
:
: @param \$max-slots: number of columns
: @param \$max-lines: number of rows
: @param \$slot-edge: extra spacing between points
: @param \$canvas: space to fill
:)
declare function this:array(
\$max-slots as xs:integer,
\$max-lines as xs:integer,
\$slot-edge as xs:double,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$slot-width := ((\$canvas-width - 2*\$canvas-edge - (\$max-slots - 1)*\$slot-edge) idiv \$max-slots)
let \$slot-height := ((\$canvas-height - 2*\$canvas-edge - (\$max-lines - 1)*\$slot-edge) idiv \$max-lines)
(: Divide up the canvas into lines and slots, skipping over the edges :)
let \$start-x := \$canvas-edge idiv \$slot-width
let \$start-y := \$canvas-edge idiv \$slot-height
let \$end-x := \$canvas-edge idiv \$slot-width
let \$end-y := \$canvas-edge idiv \$slot-height
let \$x-offset := \$min-x + \$slot-edge*\$start-x
let \$y-offset := \$min-y + \$slot-edge*\$start-y
for \$line in \$start-y to \$max-lines - (1 + \$end-y)
let \$trans-y := \$y-offset + \$line * (\$slot-height + \$slot-edge)
for \$slot in \$start-x to \$max-slots - (1 + \$end-x)
let \$trans-x := \$x-offset + \$slot * (\$slot-width + \$slot-edge)
return (
point:point(\$trans-x,\$trans-y)
)
};

(:~
: array-rectangles()
: Construct an NxM array of rectangles within the canvas, separated by the edge
: Like array() except we return the rectangles, not the grid points.
: We return the inner rectangles (with the gaps between them) as polygons.
:
: @param \$max-slots: number of columns
: @param \$max-lines: number of rows
: @param \$slot-edge: extra spacing between rectangles
: @param \$canvas: space to fill
:)
declare function this:array-rectangles(
\$max-slots as xs:integer,
\$max-lines as xs:integer,
\$slot-edge as xs:integer,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$slot-width := (\$canvas-width - 2*\$canvas-edge - (\$max-slots - 1)*\$slot-edge) idiv \$max-slots
let \$slot-height := (\$canvas-height - 2*\$canvas-edge - (\$max-lines - 1)*\$slot-edge) idiv \$max-lines
(: Divide up the canvas into lines and slots, skipping over the edges :)
let \$start-x := \$canvas-edge idiv \$slot-width
let \$start-y := \$canvas-edge idiv \$slot-height
let \$end-x := \$canvas-edge idiv \$slot-width
let \$end-y := \$canvas-edge idiv \$slot-height
let \$x-offset := \$min-x + \$slot-edge*\$start-x
let \$y-offset := \$min-y + \$slot-edge*\$start-y
for \$line in \$start-y to \$max-lines - (1 + \$end-y)
let \$y1 := \$y-offset + \$line * (\$slot-height + \$slot-edge)
let \$y2 := \$y-offset + (\$line + 1) * (\$slot-height + \$slot-edge) - \$slot-edge
for \$slot in \$start-x to \$max-slots - (1 + \$end-x)
let \$x1 := \$x-offset + \$slot * (\$slot-width + \$slot-edge)
let \$x2 := \$x-offset + (\$slot + 1) * (\$slot-width + \$slot-edge) - \$slot-edge
return (
path:polygon(
box:edges(box:box(\$x1, \$y1, \$x2, \$y2))
)
)
};

(:~
: array-boxes()
: Construct an NxM array of boxes within the canvas, separated by the edge
: Like array() except we return the rectangles, not the grid points.
: Like array-rectangles() except we return the rectangles as boxes not polygons.
:
: @param \$max-slots: number of columns
: @param \$max-lines: number of rows
: @param \$slot-edge: extra spacing between rectangles
: @param \$canvas: space to fill
:)
declare function this:array-boxes(
\$max-slots as xs:integer,
\$max-lines as xs:integer,
\$slot-edge as xs:integer,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$canvas-edge := box:edge(\$canvas)
let \$min-x := box:min-x(\$canvas) + \$canvas-edge
let \$min-y := box:min-y(\$canvas) + \$canvas-edge
let \$slot-width := (\$canvas-width - 2*\$canvas-edge - (\$max-slots - 1)*\$slot-edge) idiv \$max-slots
let \$slot-height := (\$canvas-height - 2*\$canvas-edge - (\$max-lines - 1)*\$slot-edge) idiv \$max-lines
(: Divide up the canvas into lines and slots, skipping over the edges :)
let \$start-x := \$canvas-edge idiv \$slot-width
let \$start-y := \$canvas-edge idiv \$slot-height
let \$end-x := \$canvas-edge idiv \$slot-width
let \$end-y := \$canvas-edge idiv \$slot-height
let \$x-offset := \$min-x + \$slot-edge*\$start-x
let \$y-offset := \$min-y + \$slot-edge*\$start-y
for \$line in \$start-y to \$max-lines - (1 + \$end-y)
let \$y1 := \$y-offset + \$line * (\$slot-height + \$slot-edge)
let \$y2 := \$y-offset + (\$line + 1) * (\$slot-height + \$slot-edge) - \$slot-edge
for \$slot in \$start-x to \$max-slots - (1 + \$end-x)
let \$x1 := \$x-offset + \$slot * (\$slot-width + \$slot-edge)
let \$x2 := \$x-offset + (\$slot + 1) * (\$slot-width + \$slot-edge) - \$slot-edge
return (
box:box(\$x1, \$y1, \$x2, \$y2)
)
};

(:~
: grid()
: Create a set of grid lines with the given scale. Useful for debugging.
:
: @param \$grid-size: Spacing between grid lines
: @param \$canvas: grid canvas with height/width parameters
:)
declare function this:grid(\$grid-size as xs:double, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)*
{
let \$canvas-width := box:width(\$canvas)
let \$canvas-height := box:height(\$canvas)
let \$max-lines := \$canvas-height idiv \$grid-size
let \$max-slots := \$canvas-width idiv \$grid-size
return (
for \$slot in 0 to \$max-slots - 1 return (
edge:edge(
point:point(\$slot*\$grid-size, 0),
point:point(\$slot*\$grid-size, \$canvas-height)
)
)
,
for \$line in 0 to \$max-lines - 1 return (
edge:edge(
point:point(0, \$line * \$grid-size),
point:point(\$canvas-width, \$line * \$grid-size)
)
)
)
};

(:~
: hex-grid()
: Return the centers of hexagons in a hexagonal gridding of the space.
: Center-to-center horizontally in a row = 3r
: Vertical offset from row to row = ½r√3
: Horizonal offset from row to row = 3r/2
:)
declare function this:hex-grid(
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$max-rows as xs:integer :=
let \$max-columns as xs:integer :=
1 + (box:width(\$canvas) - 2*box:edge(\$canvas)) idiv (3*\$radius)
for \$row in 1 to \$max-rows
let \$y := box:edge(\$canvas) + box:min-y(\$canvas) + \$row * math:sqrt(3) * \$radius div 2
let \$current-max :=
if (\$row mod 2 = 0) then \$max-columns - 1 else \$max-columns
let \$start-offset :=
box:edge(\$canvas) + box:min-x(\$canvas) + \$radius + (if (\$row mod 2 = 0) then 3*\$radius div 2 else 0)
for \$column in 1 to \$current-max
let \$x := \$start-offset + (\$column - 1) * 3 * \$radius
return point:point(\$x, \$y)
};

declare function this:navigable-hex-grid(
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$rows as xs:integer :=
let \$columns as xs:integer :=
1 + (box:width(\$canvas) - 2*box:edge(\$canvas)) idiv (3*\$radius)
return (
map {
"kind": "hex-grid",
"rows": \$rows,
"columns": \$columns,
"points": \$points,
"n": count(\$points)
}
)
};

declare function this:hex-point(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as map(xs:string,item()*)?
{
let \$columns := \$hexgrid("columns")
let \$ix :=
if (\$row mod 2 = 0) then (
(\$row idiv 2) * \$columns + (\$row idiv 2 - 1) * (\$columns - 1) + \$column
) else (
(\$row idiv 2) * \$columns + (\$row idiv 2) * (\$columns - 1) + \$column
)
return \$hexgrid("points")[\$ix]
};

declare function this:hex-point(
\$hexgrid as map(xs:string,item()*),
\$coords as xs:integer*
) as map(xs:string,item()*)?
{
this:hex-point(\$hexgrid, \$coords[1], \$coords[2])
};

declare function this:hexagon(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as map(xs:string,item()*)?
{
let \$point := \$hexgrid=>this:hex-point(\$row, \$column)
where exists(\$point)
return (
path:polygon(
(
for \$angle in (0, 60, 120, 180, 240, 300) return (
)
)=>edge:to-edges()
)
)
};

declare function this:hex-pix(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer
{
let \$columns := \$hexgrid("columns")
let \$ix :=
if (\$row mod 2 = 0) then (
(\$row idiv 2) * \$columns + (\$row idiv 2 - 1) * (\$columns - 1) + \$column
) else (
(\$row idiv 2) * \$columns + (\$row idiv 2) * (\$columns - 1) + \$column
)
return \$ix
};

declare function this:hex-pix(
\$hexgrid as map(xs:string,item()*),
\$coords as xs:integer*
) as xs:integer
{
this:hex-pix(\$hexgrid, \$coords[1], \$coords[2])
};

declare function this:down-left(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = \$hexgrid("rows")) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row + 1, \$column)) then ()
else (\$row + 1, \$column)
)
else (
if (\$column = 1) then ()
else if (this:excluded(\$hexgrid, \$row + 1, \$column - 1)) then ()
else (\$row + 1, \$column - 1)
)
};

declare function this:up-left(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = 1) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row - 1, \$column)) then ()
else (\$row - 1, \$column)
)
else (
if (\$column = 1) then ()
else if (this:excluded(\$hexgrid, \$row - 1, \$column - 1)) then ()
else (\$row - 1, \$column - 1)
)
};

declare function this:down-right(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = \$hexgrid("rows")) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row + 1, \$column + 1)) then ()
else (\$row + 1, \$column + 1)
) else (
if (\$column = \$hexgrid("columns")) then ()
else if (this:excluded(\$hexgrid, \$row + 1, \$column)) then ()
else (\$row + 1, \$column)
)
};

declare function this:up-right(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row = 1) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row - 1, \$column + 1)) then ()
else (\$row - 1, \$column + 1)
) else (
if (\$column = \$hexgrid("columns")) then ()
else if (this:excluded(\$hexgrid, \$row - 1, \$column)) then ()
else (\$row - 1, \$column)
)
};

declare function this:down-straight(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row >= \$hexgrid("rows") - 1) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row + 2, \$column)) then ()
else (\$row + 2, \$column)
)
else (
if (this:excluded(\$hexgrid, \$row + 2, \$column)) then ()
else (\$row + 2, \$column)
)
};

declare function this:up-straight(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:integer* (: (row, column) or empty :)
{
if (\$row <= 2) then ()
else if (\$row mod 2 = 0) then (
if (\$column = \$hexgrid("columns")) then () (: Shouldn't happen :)
else if (this:excluded(\$hexgrid, \$row - 2, \$column)) then ()
else (\$row - 2, \$column)
)
else (
if (this:excluded(\$hexgrid, \$row - 2, \$column)) then ()
else (\$row - 2, \$column)
)
};

(:~
: Get the triangles around the given point in the hex grid, returned
: as an array of arrays of (row, column) grid references.
:
: @param \$hexgrid: the navigable hex grid
: @param \$row: target row in grid
: @param \$column: target column in grid
:)
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as array(array(xs:integer))*
{
(:
: Get the hex points around the focus:
:
:             1
:          6     2
:             *
:          5     3
:             4
:)
let \$v1 := \$hexgrid=>this:up-straight(\$row, \$column)
let \$v2 := \$hexgrid=>this:up-right(\$row, \$column)
let \$v3 := \$hexgrid=>this:down-right(\$row, \$column)
let \$v4 := \$hexgrid=>this:down-straight(\$row, \$column)
let \$v5 := \$hexgrid=>this:down-left(\$row, \$column)
let \$v6 := \$hexgrid=>this:up-left(\$row, \$column)
(:
: Now make triangles from the ones that exist
: These "triangles" are just the grid references in an array of pairs
: Order them so that the first vertex is the lowest one
:)
let \$focus := array {(\$row, \$column)}
return (
if (exists(\$v1)) then (
if (exists(\$v2)) then array{ \$focus, array {\$v2}, array {\$v1} } else (),
if (exists(\$v6)) then array{ \$focus, array {\$v1}, array {\$v6} } else ()
) else (),
if (exists(\$v3)) then (
if (exists(\$v2)) then array{ array {\$v3}, array {\$v2}, \$focus } else (),
if (exists(\$v4)) then array{ array {\$v4}, array {\$v3}, \$focus } else ()
) else (),
if (exists(\$v5)) then (
if (exists(\$v4)) then array{ array {\$v4}, \$focus, array {\$v5} } else (),
if (exists(\$v6)) then array{ array {\$v5}, \$focus, array {\$v6} } else ()
) else ()
)
};

(:~
: hex-subgrid()
: Construct a subgrid for the given hexagon in the grid. We will end up
: with a couple of extra points in the first row to make the navigation
: work properly: be sure to use excluded() or hex-points() to ignore them.
:
: @param \$hexgrid: the navigable hex grid
: @param \$row: target row in grid
: @param \$column: target column in grid
:)
declare function this:hex-subgrid(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as map(xs:string,item()*)
{
let \$center := this:hex-point(\$hexgrid, \$row, \$column)
return this:hex-subgrid(\$hexgrid, \$center)
};

(:~
: hex-subgrid()
: Construct a subgrid for the given hexagon in the grid. We will end up
: with a couple of extra points in the first row to make the navigation
: work properly: be sure to use excluded() or hex-points() to ignore them.
:
: @param \$hexgrid: the navigable hex grid
: @param \$center: target point in grid
:)
declare function this:hex-subgrid(
\$hexgrid as map(xs:string,item()*),
\$center as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$v := \$radius * math:sqrt(3) div 2
let \$h := 3 * \$radius div 2
let \$points := (
\$center=>geom:translate(-\$h, -3 * \$v), (: outside grid :)
\$center=>geom:translate(\$h, -3 * \$v),  (: row 1: outside grid :)
\$center=>geom:translate(0, -2 * \$v),   (: row 2: first inside :)
\$center=>geom:translate(-\$h, -\$v),     (: row 3: inside :)
\$center=>geom:translate(\$h, -\$v),      (: row 3: inside :)
\$center,                               (: row 4: inside :)
\$center=>geom:translate(-\$h, \$v),      (: row 5: inside :)
\$center=>geom:translate(\$h, \$v),       (: row 5: inside :)
\$center=>geom:translate(0, 2 * \$v)     (: row 6: inside :)
)
return (
map {
"kind": "hex-grid",
"rows": 6,
"columns": 2,
"points": \$points=>geom:snap(),
"n": count(\$points) - 2,
"exclusions": (1, 2)
}
)
};

(:~
: excluded()
: Is the point at this index excluded?
:
: @param \$hexgrid: the navigable hex grid
: @param \$pix: target point index
:)
declare function this:excluded(
\$hexgrid as map(xs:string,item()*),
\$pix as xs:integer
) as xs:boolean
{
\$pix = \$hexgrid("exclusions") or \$pix > \$hexgrid("n")
};

(:~
: excluded()
: Is the point at this index excluded?
:
: @param \$hexgrid: the navigable hex grid
: @param \$row: target row in grid
: @param \$column: target column in grid
:)
declare function this:excluded(
\$hexgrid as map(xs:string,item()*),
\$row as xs:integer,
\$column as xs:integer
) as xs:boolean
{
let \$pix := this:hex-pix(\$hexgrid, \$row, \$column)
return \$pix = \$hexgrid("exclusions") or \$pix > \$hexgrid("n")
};

(:~
: hex-points()
: The non-excluded points in the hex grid.
:
: @param \$hexgrid: the navigable hex grid
:)
declare function this:hex-points(
\$hexgrid as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
\$hexgrid("points")[not(position() = \$hexgrid("exclusions"))]
};

(:~
: hexagons()
: Return the hexagon tiling associated with hexgrid.
:
: @param \$hexgrid the hexagonal grid
:)
declare function this:hexagons(
\$hexgrid as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let \$points := this:hex-points(\$hexgrid)