Skip to content

pattern

A class containing a set (or pattern) of cells.

The pattern class is the central class of forma, representing a set of points or cells. It can be initialized empty or using a prototype (an N×M matrix of 1's and 0's). Helper methods for initialization are provided in the primitives module. Once created, a pattern is modified only via the insert method—other manipulations return new patterns.

Pattern manipulators include methods like translate, enlarge, rotate, hreflect, and vreflect. Other operations, such as computing the exterior_hull or interior_hull, help determine the boundaries of a pattern. Coordinates are maintained reliably in the range [-65536, 65536], which can be adjusted via the MAX_COORDINATE constant.

Functions can be invoked either as functions:

pattern.method(input_pattern, ... )

or as methods:

input_pattern:method(... )
-- Procedural style:
local p1 = pattern.new()
pattern.insert(p1, 1, 1)

-- Method chaining:
local p2 = pattern.new():insert(1, 1)

-- Prototype style:
local p3 = pattern.new({{1,1,1}, {1,0,1}, {1,1,1}})

-- Retrieve a random cell and the medoid cell:
local random_cell = p1:rcell()
local medoid_cell = p1:medoid()

-- Compute the exterior hull using the Moore neighbourhood:
local outer_hull = p1:exterior_hull(neighbourhood.moore())
-- or equivalently:
outer_hull = pattern.exterior_hull(p1, neighbourhood.moore())

Fields

Name Type Description
max forma.cell maximum bounding box corner
min forma.cell minimum bounding box corner
offchar string character for inactive cells in tostring
onchar string character for active cells in tostring
cellkey table list of coordinate keys
cellmap table spatial hash of coordinate key to index

Basic

pattern.new

Pattern constructor. Returns a new pattern. If a prototype is provided (an N×M table of 1's and 0's), the corresponding active cells are inserted.

local p = pattern.new()
local p2 = pattern.new({{1,1,1}, {1,0,1}, {1,1,1}})

Parameters:

Name Type Description
prototype table? an N×M table of ones and zeros

Returns:

  • forma.pattern — a new pattern according to the prototype

pattern.clone

Creates a copy of an existing pattern.

Parameters:

Name Type Description
ip forma.pattern input pattern to clone

Returns:

  • forma.pattern — a new pattern that is a duplicate of ip

pattern.insert

Inserts a new cell into the pattern. Returns the modified pattern to allow for method chaining.

local p = pattern.new():insert(1, 1):insert(2, 2)

Parameters:

Name Type Description
ip forma.pattern pattern to modify
x integer x-coordinate of the new cell
y integer y-coordinate of the new cell

Returns:

  • forma.pattern — the updated pattern (for cascading calls)

pattern.remove

Removes a cell from the pattern. Returns the modified pattern to allow for method chaining.

p:remove(1, 1)

Parameters:

Name Type Description
ip forma.pattern pattern to modify
x integer x-coordinate of the cell to remove
y integer y-coordinate of the cell to remove

Returns:

  • forma.pattern — the updated pattern (for cascading calls)

pattern.has_cell

Checks if a cell at (x, y) is active in the pattern.

Parameters:

Name Type Description
ip forma.pattern pattern to check
x integer x-coordinate
y integer y-coordinate

Returns:

  • boolean — true if the cell is active, false otherwise

pattern.filter

Filters the pattern using a boolean callback, returning a subpattern.

Parameters:

Name Type Description
ip forma.pattern the original pattern
fn fun(cell: forma.cell):boolean function that determines if a cell is kept

Returns:

  • forma.pattern — a new pattern containing only the cells that pass the filter

pattern.size

Returns the number of active cells in the pattern.

Parameters:

Name Type Description
ip forma.pattern pattern to measure

Returns:

  • integer — count of active cells

Utilities

pattern.size_sort

Comparator function to sort patterns by their size (descending).

Parameters:

Name Type Description
pa forma.pattern first pattern
pb forma.pattern second pattern

Returns:

  • boolean — true if pa's size is greater than pb's

pattern.inverse_size_sort

Comparator function to sort patterns by their size (ascending).

Parameters:

Name Type Description
pa forma.pattern first pattern
pb forma.pattern second pattern

Returns:

  • boolean — true if pa's size is less than pb's

pattern.bounding_box_density

Computes how densely the bounding box is filled. Returns zero for an empty pattern.

Parameters:

Name Type Description
ip forma.pattern input pattern

Returns:

  • number — the fraction of the bounding box that is occupied

pattern.bounding_box_asymmetry

Computes the asymmetry of the pattern's bounding box. Returns zero in the case of an empty pattern.

Parameters:

Name Type Description
ip forma.pattern input pattern

Returns:

  • number — the ratio of the bounding box's longest to shortest edge

pattern.count_neighbors

Counts active neighbors around a specified cell within the pattern. Can be invoked with either a cell object or with x and y coordinates.

Parameters:

Name Type Description
p forma.pattern a pattern
nbh forma.neighbourhood a neighbourhood (e.g., neighbourhood.moore())
arg1 integer|forma.cell either a cell or the x-coordinate
arg2 integer? the y-coordinate if arg1 is not a cell

Returns:

  • integer — count of active neighbouring cells

pattern.cell_list

Returns a list (table) of active cells in the pattern.

Parameters:

Name Type Description
ip forma.pattern pattern to list cells from

Returns:

  • forma.cell[] — table of cell objects

pattern.edit_distance

Computes the edit distance between two patterns (the total number of differing cells).

Parameters:

Name Type Description
a forma.pattern first pattern
b forma.pattern second pattern

Returns:

  • integer — the edit distance

Set operations

pattern.union

Returns the union of a set of patterns.

local combined = pattern.union(p1, p2, p3)

Parameters:

Name Type Description
`` forma.pattern a table of patterns or a list of pattern arguments

Returns:

  • forma.pattern — a new pattern that is the union of the provided patterns

pattern.intersect

Returns the intersection of multiple patterns (cells common to all).

local common = pattern.intersect(p1, p2)

Parameters:

Name Type Description
`` forma.pattern two or more patterns to intersect

Returns:

  • forma.pattern — a new pattern of cells that exist in every input pattern

pattern.xor

Returns the symmetric difference (XOR) of two patterns. Cells are included if they exist in either pattern but not in both.

Parameters:

Name Type Description
a forma.pattern first pattern
b forma.pattern second pattern

Returns:

  • forma.pattern — a new pattern representing the symmetric difference

Iterators

pattern.cells

Iterator over active cells in the pattern.

for cell in p:cells() do
    print(cell.x, cell.y)
end

Parameters:

Name Type Description
ip forma.pattern pattern to iterate over

Returns:

  • fun():(forma.cell)? — that returns each active cell as a cell object

pattern.cell_coordinates

Iterator over active cell coordinates (x, y) in the pattern.

for x, y in p:cell_coordinates() do
    print(x, y)
end

Parameters:

Name Type Description
ip forma.pattern pattern to iterate over

Returns:

  • fun():integer?, integer? — that returns the x and y coordinates of each active cell

pattern.shuffled_cells

Returns an iterator over active cells in randomized order.

for cell in pattern.shuffled_cells(p) do
    print(cell.x, cell.y)
end

Parameters:

Name Type Description
ip forma.pattern pattern to iterate over
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • fun():(forma.cell)? — that yields each active cell in a random order

pattern.shuffled_coordinates

Returns an iterator over active cell coordinates in randomized order.

for x, y in pattern.shuffled_coordinates(p) do
    print(x, y)
end

Parameters:

Name Type Description
ip forma.pattern pattern to iterate over
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • fun():integer?, integer? — that yields x and y coordinates in random order

Metamethods

pattern.__tostring

Renders the pattern as a string. Active cells are shown with pattern.onchar and inactive cells with pattern.offchar.

print(p)

Parameters:

Name Type Description
ip forma.pattern pattern to render

Returns:

  • string — string representation of the pattern

pattern.__add

Adds two patterns using the '+' operator (i.e. returns their union).

local combined = p1 + p2

Parameters:

Name Type Description
a forma.pattern first pattern
b forma.pattern second pattern

Returns:

  • forma.pattern — a new pattern representing the union of a and b

pattern.__sub

Subtracts one pattern from another using the '-' operator. Returns a new pattern with cells in a that are not in b.

local diff = p1 - p2

Parameters:

Name Type Description
a forma.pattern base pattern
b forma.pattern pattern to subtract from a

Returns:

  • forma.pattern — a new pattern with the difference

pattern.__mul

Computes the intersection of two patterns using the '*' operator.

local common = p1 * p2

Parameters:

Name Type Description
a forma.pattern first pattern
b forma.pattern second pattern

Returns:

  • forma.pattern — a new pattern containing only the cells common to both

pattern.__pow

Computes the symmetric difference (XOR) of two patterns using the '^' operator.

local xor_pattern = p1 ^ p2

Parameters:

Name Type Description
a forma.pattern first pattern
b forma.pattern second pattern

Returns:

  • forma.pattern — a new pattern with cells present in either a or b, but not both

pattern.__eq

Tests whether two patterns are identical.

if p1 == p2 then
    -- patterns are identical
end

Parameters:

Name Type Description
a forma.pattern first pattern
b forma.pattern second pattern

Returns:

  • boolean — true if the patterns are equal, false otherwise

Cell accessors

pattern.centroid

Computes the centroid (arithmetic mean) of all cells in the pattern. The result is rounded to the nearest integer coordinate.

local center = p:centroid()

Parameters:

Name Type Description
ip forma.pattern pattern to process

Returns:

  • forma.cell — a cell representing the centroid (which may not be active)

pattern.medoid

Computes the medoid cell of the pattern. The medoid minimizes the total distance to all other cells (using Euclidean distance by default).

local medoid = p:medoid()

Parameters:

Name Type Description
ip forma.pattern pattern to process
measure (fun(a: forma.cell, b: forma.cell):number)? distance function (default: Euclidean)

Returns:

  • forma.cell — the medoid cell of the pattern

pattern.rcell

Returns a random cell from the pattern.

local random_cell = p:rcell()

Parameters:

Name Type Description
ip forma.pattern pattern to sample from
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • forma.cell — a random cell from the pattern

Transformations

pattern.translate

Returns a new pattern translated by a vector (sx, sy).

local p_translated = p:translate(2, 3)

Parameters:

Name Type Description
ip forma.pattern pattern to translate
sx integer translation along the x-axis
sy integer translation along the y-axis

Returns:

  • forma.pattern — a new pattern shifted by (sx, sy)

pattern.normalise

Normalizes the pattern by translating it so that its minimum coordinate is (0,0).

local p_norm = p:normalise()

Parameters:

Name Type Description
ip forma.pattern pattern to normalize

Returns:

  • forma.pattern — a new normalized pattern

pattern.enlarge

Returns an enlarged version of the pattern. Each active cell is replaced by an f×f block.

local p_big = p:enlarge(2)

Parameters:

Name Type Description
ip forma.pattern pattern to enlarge
f number enlargement factor

Returns:

  • forma.pattern — a new enlarged pattern

pattern.rotate

Returns a new pattern rotated 90° clockwise about the origin.

local p_rotated = p:rotate()

Parameters:

Name Type Description
ip forma.pattern pattern to rotate

Returns:

  • forma.pattern — a rotated pattern

pattern.vreflect

Returns a new pattern that is a vertical reflection of the original.

local p_vreflected = p:vreflect()

Parameters:

Name Type Description
ip forma.pattern pattern to reflect vertically

Returns:

  • forma.pattern — a vertically reflected pattern

pattern.hreflect

Returns a new pattern that is a horizontal reflection of the original.

local p_hreflected = p:hreflect()

Parameters:

Name Type Description
ip forma.pattern pattern to reflect horizontally

Returns:

  • forma.pattern — a horizontally reflected pattern

Sampling

pattern.sample

Returns a random subpattern containing a fixed number of cells.

local sample = p:sample(10)

Parameters:

Name Type Description
ip forma.pattern pattern (domain) to sample from
ncells integer number of cells to sample
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • forma.pattern — a new pattern with ncells randomly selected cells

pattern.sample_poisson

Returns a Poisson-disc sampled subpattern. Ensures that no two sampled cells are closer than the given radius.

local poisson_sample = p:sample_poisson(cell.euclidean, 5)

Parameters:

Name Type Description
ip forma.pattern pattern (domain) to sample from
distance fun(a: forma.cell, b: forma.cell):number distance function (e.g., cell.euclidean)
radius number minimum separation
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • forma.pattern — a new pattern sampled with Poisson-disc criteria

pattern.sample_mitchell

Returns an approximate Poisson-disc sample using Mitchell's best candidate algorithm.

local mitchell_sample = p:sample_mitchell(cell.euclidean, 10, 5)

Parameters:

Name Type Description
ip forma.pattern the input pattern to sample from
distance fun(a: forma.cell, b: forma.cell):number distance function (e.g., cell.euclidean)
n integer number of samples
k integer number of candidate attempts per iteration
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • forma.pattern — a new pattern with n samples chosen via the algorithm

Subpattern finding

pattern.floodfill

Returns the contiguous subpattern (connected component) starting from a given cell.

local component = p:floodfill(cell.new(2, 3))

Parameters:

Name Type Description
ip forma.pattern pattern upon which the flood fill is to be performed
icell forma.cell a cell specifying the origin of the flood fill
nbh (forma.neighbourhood)? neighbourhood to use (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new pattern containing the connected component

pattern.max_rectangle

Finds the largest contiguous rectangular subpattern within the pattern.

local rect = p:max_rectangle()

Parameters:

Name Type Description
ip forma.pattern pattern to analyze
alpha number? 'squareness' parameter. 0 for max rectangle, 1 for max square

Returns:

  • forma.pattern — a subpattern representing the maximal rectangle

pattern.convex_hull

Computes the convex hull of the pattern. The hull points are connected using line rasterization.

local hull = p:convex_hull()

Parameters:

Name Type Description
ip forma.pattern pattern to process

Returns:

  • forma.pattern — a new pattern representing the convex hull

pattern.thin

Returns a thinned (skeletonized) version of the pattern. Iteratively removes border cells whose Moore neighbours remain a single connected component under nbh.

local thin_p = p:thin()
local thin_4 = p:thin(neighbourhood.von_neumann())

Parameters:

Name Type Description
ip forma.pattern pattern to thin
nbh (forma.neighbourhood)? neighbourhood for connectivity (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new, thinned pattern

Morphological operations

pattern.erode

Returns the erosion of the pattern. A cell is retained only if all of its neighbours (as defined by nbh) are active.

local eroded = p:erode(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to erode
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new, eroded pattern

pattern.dilate

Returns the dilation of the pattern. Each active cell contributes its neighbours (as defined by nbh) to the result.

local dilated = p:dilate(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to dilate
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new, dilated pattern

pattern.gradient

Returns the morphological gradient of the pattern. Computes the difference between the dilation and erosion.

local grad = p:gradient(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to process
nbh (forma.neighbourhood)? neighbourhood for dilation/erosion (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new pattern representing the gradient

pattern.opening

Returns the morphological opening of the pattern. Performs erosion followed by dilation to remove small artifacts.

local opened = p:opening(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to process
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new, opened pattern

pattern.closing

Returns the morphological closing of the pattern. Performs dilation followed by erosion to fill small holes.

local closed = p:closing(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to process
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new, closed pattern

pattern.interior_hull

Returns a pattern of cells that form the interior hull. These are cells that neighbor inactive cells while still belonging to the pattern.

local interior = p:interior_hull(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to process
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new pattern representing the interior hull

pattern.exterior_hull

Returns a pattern of cells that form the exterior hull. This consists of inactive neighbours of the pattern, useful for enlarging or determining non-overlapping borders.

local exterior = p:exterior_hull(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to process
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.pattern — a new pattern representing the exterior hull

Packing

pattern.find_packing_position

Finds a packing offset where pattern a fits entirely within domain b. Returns a coordinate shift that, when applied to a, makes it tile inside b.

local offset = pattern.find_packing_position(p, domain)

Parameters:

Name Type Description
a forma.pattern pattern to pack
b forma.pattern domain pattern in which to pack a
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • (forma.cell)? — a cell (as a coordinate shift) if a valid position is found; nil otherwise

pattern.find_central_packing_position

Finds a center-weighted packing offset to place pattern a within pattern b as close as possible to the cell c. If no c is provided, then the centroid of pattern b is used.

local central_offset = pattern.find_central_packing_position(p, domain, c)

Parameters:

Name Type Description
a forma.pattern pattern to pack
b forma.pattern domain pattern
c (forma.cell)? cell to act as a center for packing

Returns:

  • (forma.cell)? — a coordinate shift if a valid position is found; nil otherwise

Decomposition

pattern.connected_components

Returns a multipattern of the connected components within the pattern. Uses flood-fill to extract contiguous subpatterns.

local components = p:connected_components(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern to analyze
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.moore())

Returns:

  • forma.multipattern — containing each connected component as a subpattern

pattern.interior_holes

Returns a multipattern of the interior holes of the pattern. Interior holes are inactive regions completely surrounded by active cells.

local holes = p:interior_holes(neighbourhood.von_neumann())

Parameters:

Name Type Description
ip forma.pattern pattern to analyze
nbh (forma.neighbourhood)? neighbourhood (default: neighbourhood.von_neumann())

Returns:

  • forma.multipattern — of interior hole subpatterns

pattern.bsp

Partitions the pattern using binary space partitioning (BSP). Recursively subdivides contiguous rectangular areas until each partition's volume is below th_volume.

local partitions = p:bsp(50)

Parameters:

Name Type Description
ip forma.pattern pattern to partition
th_volume number threshold volume for final partitions
alpha number? parameter for squareness of BSP

Returns:

  • forma.multipattern — of BSP subpatterns

pattern.neighbourhood_categories

Categorizes cells in the pattern based on neighbourhood configurations. Returns a multipattern with one subpattern per neighbourhood category.

local categories = p:neighbourhood_categories(neighbourhood.moore())

Parameters:

Name Type Description
ip forma.pattern pattern whose cells are to be categorized
nbh forma.neighbourhood neighbourhood used for categorization

Returns:

  • forma.multipattern — with each category represented as a subpattern

pattern.perlin

Applies Perlin noise sampling to the pattern. Generates a multipattern by thresholding Perlin noise values at multiple levels.

local noise_samples = p:perlin(0.1, 4, {0.3, 0.5, 0.7})

Parameters:

Name Type Description
ip forma.pattern pattern (domain) to sample from
freq number frequency for Perlin noise
depth integer sampling depth
thresholds number[] table of threshold values (each between 0 and 1)
rng (fun(m: integer):integer)? random number generator (e.g., math.random)

Returns:

  • forma.multipattern — with one component per threshold level

pattern.voronoi

Generates Voronoi tessellation segments for a domain based on seed points.

local segments = pattern.voronoi(seeds, domain, cell.euclidean)

Parameters:

Name Type Description
seeds forma.pattern pattern containing seed cells
domain forma.pattern pattern defining the tessellation domain
measure fun(a: forma.cell, b: forma.cell):number distance function (e.g., cell.euclidean)

Returns:

  • forma.multipattern — of Voronoi segments

pattern.voronoi_relax

Performs centroidal Voronoi tessellation (Lloyd's algorithm) on a set of seeds. Iteratively relaxes seed positions until convergence or a maximum number of iterations.

local segments, relaxed_seeds, converged = pattern.voronoi_relax(seeds, domain, cell.euclidean)

Parameters:

Name Type Description
seeds forma.pattern initial seed pattern
domain forma.pattern tessellation domain pattern
measure fun(a: forma.cell, b: forma.cell):number distance function (e.g., cell.euclidean)
max_ite integer? maximum iterations (default: 30)

Returns:

  • forma.multipattern — a multipattern of Voronoi segments
  • forma.pattern — a pattern of relaxed seed positions
  • boolean — whether the algorithm converged

Display and utilities

pattern.get_max_coordinate

Returns the maximum allowed coordinate for spatial hashing.

Returns:

  • number — maximum coordinate value

pattern.test_coordinate_map

Tests the conversion between (x, y) coordinates and the spatial hash key.

Parameters:

Name Type Description
x number test x-coordinate
y number test y-coordinate

Returns:

  • boolean — true if the conversion is correct, false otherwise

pattern.print

Prints the pattern within an optional domain, line-by-line.

Parameters:

Name Type Description
ip forma.pattern pattern to render
char string? single-character to draw "on" cells
domain (forma.pattern)? pattern defining the bounding box
printer fun(line: string)? function for printing each line; defaults to io.write(line.."\n")