iterative orthogonal extrusion:
generative unit: 1-cube
n-cube = n-fold Cartesian product of 1-cube
vertex set: {0,1}^n
square (2-cube) is first case with visible combinatorial structure
binary digits give coordinates:
bit d = 0 -> -1
bit d = 1 -> +1
vertex(v) = (coord_0, coord_1, …, coord_{n-1})
all vertices:
vertices of a k-face:
face pattern templates:
example: 2-cube faces of a 4-cube (f=fixed, v=variable): ffvv, fvfv, fvvf, vffv, vfvf, vvff
example face (pattern vfvf, fixed bits v0v1): 0001, 0011, 1001, 1011
k-face counts by (vary, fix):
3-cube: edges (1,2), squares (2,1), cubes (3,0)
4-cube: edges (1,3), squares (2,2), cubes (3,1)
2-cube:
3-cube:
4-cube:
# map integer vertex index v ∈ [0, 2^n) to coordinate array in { -1, +1 }^n
# input: v (integer), n (dimensions)
# output: array of length n, each element = -1 or +1
bits_to_array = (v, n) ->  # [±1, ±1, ±1, ...]
  [0...n].map (d) ->
    if (v >> d & 1) == 0 then -1 else 1# generate all k-combinations of {0,...,n-1} as binary bitmasks
# input: n (set size), k (subset size)
# output: array of integers, each bitmask has exactly k bits set
get_bit_combinations = (n, k) ->  # [integer, ...]
  result = []
  a = (1 << k) - 1
  while a < (1 << n)
    result.push a
    b = a & -a
    c = a + b
    a = (((c ^ a) >> 2) / b) | c
  result# partition a set of vertex indices into all k-faces of the n-cube
# input:
#   vertices: array of integer vertex bitmasks (length = 2^n)
#   indices: array of vertex indices to consider
#   n: cube dimension
#   k: number of varying coordinates in each face
#   cell_length: number of vertices per k-face (= 2^k)
# output: array of k-faces, each face = array of vertex indices
group_n_cells = (vertices, indices, n, k, cell_length) ->  # [[integer, ...], ...]
  fixed_combinations = get_bit_combinations n, k
  cell_indices = []
  for fixed in fixed_combinations
    cell_vertices = {}
    for i in indices
      key = fixed & vertices[i]
      if cell_vertices[key]
        cell_vertices[key].push i
      else
        cell_vertices[key] = [i]
    new_cells = Object.values(cell_vertices).filter (a) -> cell_length == a.length
    cell_indices = cell_indices.concat new_cells
  cell_indices# enumerate all cells of an n-cube, grouped by dimension
# input:
#   vertices: array of integer vertex bitmasks
#   n: cube dimension
# output:
#   nested arrays; level 0 = 1-faces (edges), ..., level n-1 = n-faces (the cube itself)
#   each cell represented as array of vertex indices
get_cells = (vertices, n) ->  # [[[integer, ...], ...], [[integer, ...], ...], ...]
  subcells = (indices, k) ->
    return indices unless k < n
    indices = group_n_cells vertices, indices, n, k, 2 ** (n - k)
    subcells a, k + 1 for a in indices
  subcells [0...vertices.length], 1n = 4
bit_vertices = [0...2 ** n]
# vertices as coordinate arrays in { -1, +1 }^n
vertices = bit_vertices.map (v) -> bits_to_array v, n
# all cells, grouped by dimension (edges, squares, cubes, hypercubes)
cells = get_cells bit_vertices, n