Skip to content

Getting Started

forma is built on three concepts: cells, patterns, and neighbourhoods. Everything else in the library is built on top of these.

Cells

A cell is an integer 2D point with x and y fields.

local cell = require('forma.cell')

local a = cell.new(3, 4)
local b = cell.new(1, 2)

local c = a + b             -- cell.new(4, 6)
local d = a - b             -- cell.new(2, 2)
print(a == cell.new(3, 4))  --> true (structural equality)

The cell module also provides distance measures; cell.manhattan, cell.chebyshev, and cell.euclidean which are used by sampling, Voronoi tessellation, and other operations that need a notion of proximity.

Patterns

A pattern is a set of cells — the central data structure of forma. It uses spatial hashing internally, so lookups, insertions, and removals are all O(1).

local pattern = require('forma.pattern')

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

print(p:has_cell(1, 0))  --> true
print(p:size())          --> 3

The primitives module provides convenient constructors for common shapes:

local primitives = require('forma.primitives')

local square = primitives.square(80, 20)       -- 80×20 rectangle
local circle = primitives.circle(10)           -- circle of radius 10
local line   = primitives.line(cell.new(0,0), cell.new(20,10))

All pattern functions can be called as functions or as methods:

-- These are equivalent
local hull = pattern.exterior_hull(p, nbh)
local hull = p:exterior_hull(nbh)

Set operations

Patterns support set operations, both as functions and as operators:

local a = primitives.square(10)
local b = primitives.circle(5):translate(5, 5)

local combined    = a + b    -- union
local overlap     = a * b    -- intersection
local difference  = a - b    -- subtraction
local symmetric   = a ^ b    -- XOR

These return new patterns — forma never modifies a pattern in place (except for insert and remove).

Transforms

Patterns can be translated, rotated, reflected, and enlarged:

local moved   = square:translate(10, 5)
local rotated = square:rotate()       -- 90° clockwise
local flipped = square:hreflect()     -- horizontal mirror
local bigger  = square:enlarge(2)     -- scale by 2×

Printing

For quick visualisation, patterns can be printed to the terminal:

primitives.circle(8):print()

Iteration

To use a pattern's cells in your own code, iterate over them directly:

for icell in p:cells() do
    place_tile(icell.x, icell.y)
end

-- Or just the coordinates
for x, y in p:cell_coordinates() do
    place_tile(x, y)
end

Neighbourhoods

A neighbourhood defines which cells are considered adjacent. This definition is used by many pattern operations. For example finding borders, flood fills, connected components, cellular automata, and morphological operations all depend on a neighbourhood.

local neighbourhood = require('forma.neighbourhood')

local moore = neighbourhood.moore()         -- 8 surrounding cells
local vn    = neighbourhood.von_neumann()   -- 4 cardinal neighbours

Custom neighbourhoods can also be defined from a list of relative cell positions using neighbourhood.new().

Here's an example using a neighbourhood to find the exterior border of a shape:

local domain = primitives.square(10)
local border = domain:exterior_hull(neighbourhood.von_neumann())

Multipatterns

Several operations decompose a pattern into component pieces. The result is a multipattern: a collection of patterns.

-- Find the 4-connected regions of a pattern
local regions = p:connected_components(neighbourhood.von_neumann())

-- Access individual sub-patterns
local first = regions[1]

-- Filter to regions above a size threshold
local big = regions:filter(function(r) return r:size() > 20 end)

-- Recombine into a single pattern
local merged = big:union_all()

What next

The API reference covers the full set of operations available on patterns. The automata module documents cellular automata. The examples demonstrate how these combine to produce interesting structures.