modeling/src/measurements/measureCenterOfMass.js

const flatten = require('../utils/flatten')

const vec3 = require('../maths/vec3')

const geom2 = require('../geometries/geom2')
const geom3 = require('../geometries/geom3')

const cacheOfCenterOfMass = new WeakMap()

/*
 * Measure the center of mass for the given geometry.
 *
 * @see http://paulbourke.net/geometry/polygonmesh/
 * @return {Array} the center of mass for the geometry
 */
const measureCenterOfMassGeom2 = (geometry) => {
  let centerOfMass = cacheOfCenterOfMass.get(geometry)
  if (centerOfMass !== undefined) return centerOfMass

  const sides = geom2.toSides(geometry)

  let area = 0
  let x = 0
  let y = 0
  if (sides.length > 0) {
    for (let i = 0; i < sides.length; i++) {
      const p1 = sides[i][0]
      const p2 = sides[i][1]

      const a = p1[0] * p2[1] - p1[1] * p2[0]
      area += a
      x += (p1[0] + p2[0]) * a
      y += (p1[1] + p2[1]) * a
    }
    area /= 2

    const f = 1 / (area * 6)
    x *= f
    y *= f
  }

  centerOfMass = vec3.fromValues(x, y, 0)

  cacheOfCenterOfMass.set(geometry, centerOfMass)
  return centerOfMass
}

/*
 * Measure the center of mass for the given geometry.
 * @return {Array} the center of mass for the geometry
 */
const measureCenterOfMassGeom3 = (geometry) => {
  let centerOfMass = cacheOfCenterOfMass.get(geometry)
  if (centerOfMass !== undefined) return centerOfMass

  centerOfMass = vec3.create() // 0, 0, 0

  const polygons = geom3.toPolygons(geometry)
  if (polygons.length === 0) return centerOfMass

  let totalVolume = 0
  const vector = vec3.create() // for speed
  polygons.forEach((polygon) => {
    // calculate volume and center of each tetrahedron
    const vertices = polygon.vertices
    for (let i = 0; i < vertices.length - 2; i++) {
      vec3.cross(vector, vertices[i + 1], vertices[i + 2])
      const volume = vec3.dot(vertices[0], vector) / 6

      totalVolume += volume

      vec3.add(vector, vertices[0], vertices[i + 1])
      vec3.add(vector, vector, vertices[i + 2])
      const weightedCenter = vec3.scale(vector, vector, 1 / 4 * volume)

      vec3.add(centerOfMass, centerOfMass, weightedCenter)
    }
  })
  vec3.scale(centerOfMass, centerOfMass, 1 / totalVolume)

  cacheOfCenterOfMass.set(geometry, centerOfMass)
  return centerOfMass
}

/**
 * Measure the center of mass for the given geometries.
 * @param {...Object} geometries - the geometries to measure
 * @return {Array} the center of mass for each geometry, i.e. [X, Y, Z]
 * @alias module:modeling/measurements.measureCenterOfMass
 *
 * @example
 * let center = measureCenterOfMass(sphere())
 */
const measureCenterOfMass = (...geometries) => {
  geometries = flatten(geometries)

  const results = geometries.map((geometry) => {
    // NOTE: center of mass for geometry path2 is not possible
    if (geom2.isA(geometry)) return measureCenterOfMassGeom2(geometry)
    if (geom3.isA(geometry)) return measureCenterOfMassGeom3(geometry)
    return [0, 0, 0]
  })
  return results.length === 1 ? results[0] : results
}

module.exports = measureCenterOfMass