index.js

'use strict'

/**
 * @module 3d-adjacency
 * @description this module outputs sets of cells of which
 * are adjacent to one another.  see `.find`
 */
const Cluster = require('./cluster')
const Mass = require('./mass')

/**
 * find contingent clusters in a 3d array. touching entities
 * with truthy values are considered `Mass`. a collection
 * of mass is a `Cluster`. entites are touching IFF they share
 * a face (that is, a piece of mass may at most have 6 adjacent
 * neighbors)
 * @param {array[][]} arr
 * @param {object} opts
 * @param {function} [opts.isAdjacent] custom function to decide whether cells
 *                                     are adjacent or not. defaults to `truthy`
 *                                     values === adjacent. fn gets cell value,
 *                                     and must return truthy value 
 * @returns {array} e.g. [ [{x:0,y:0,z:1}], [{x:3,y:3,z:3},{x:4,y:3,z:3}]]
 */
function find(arr, opts) {
  var opts = opts || {}
  var isAdjacent = opts.isAdjacent || ((v) => v)
  var knownMass = {} // { 000: 1, 001: 2 } ==> { address: value }
  var clusters = []
  var clust
  var value
  for (var x = 0; x < arr.length; ++x) {
    for (var y = 0; y < arr[0].length; ++y) {
      for (var z = 0; z < arr[0][0].length; ++z) {
        value = arr[x][y][z]
        if (isAdjacent(value) && !knownMass.hasOwnProperty(`${x}${y}${z}`)) {
          knownMass[`${x}${y}${z}`] = value
          clust = new Cluster({
            domain: arr,
            knownMass,
            root: new Mass({ domain: arr, x, y, z, value }),
          })
          clust.clusterify()
          clusters.push(clust.serialize())
        }
      }
    }
  }
  return clusters
}

const SORT_PROPS = ['x', 'y', 'z']

/**
 * sorts nodes. see `sort`
 * @param {object} ma { x, y, z } node
 * @param {object} mb { x, y, z } node
 * @returns {number} -1/0/1
 */
function nodeSorter(ma, mb) {
  let axis
  for (var n in SORT_PROPS) {
    axis = SORT_PROPS[n]
    if (ma[axis] < mb[axis]) return -1
    if (ma[axis] > mb[axis]) return 1
  }
  // generally, 0 means we have two identically addressed
  // nodes. this is viably an error case, FYI!
  /* istanbul ignore next */
  return 0
}

/**
 * sort a cluster set, where x, y, z values rank
 * higher in the sort, respectively
 * @param {array[]} clusterSet array of clusters
 * @returns {array} sorted clusterSet
 */
function sort (clusterSet) {
  clusterSet = clusterSet.map(clust => clust.sort(nodeSorter))
  return clusterSet.sort((cla, clb) => {
    return nodeSorter(cla[0], clb[0])
  })
}

module.exports = {
	find,
  sort,
  nodeSorter,
	Cluster,
	Mass
}