doAdjacentMerge (face, mergeType) {
    let edge = face.edge
    let convex = true
    let it = 0
    do {
      if (it >= face.nVertices) {
        throw Error('merge recursion limit exceeded')
      }
      const oppositeFace = edge.opposite.face
      let merge = false

      // Important notes about the algorithm to merge faces
      //
      // - Given a vertex `eyeVertex` that will be added to the hull
      //   all the faces that cannot see `eyeVertex` are defined as follows
      //
      //      dot(v, not visible face normal) - not visible offset < tolerance
      //
      // - Two faces can be merged when the centroid of one of these faces
      // projected to the normal of the other face minus the other face offset
      // is in the range [tolerance, -tolerance]
      // - Since `face` (given in the input for this method) has passed the
      // check above we only have to check the lower bound e.g.
      //
      //      dot(v, not visible face normal) - not visible offset > -tolerance
      //
      if (mergeType === MERGE_NON_CONVEX) {
        if (this.oppositeFaceDistance(edge) > -this.tolerance ||
            this.oppositeFaceDistance(edge.opposite) > -this.tolerance) {
          merge = true
        }
      } else {
        if (face.area > oppositeFace.area) {
          if (this.oppositeFaceDistance(edge) > -this.tolerance) {
            merge = true
          } else if (this.oppositeFaceDistance(edge.opposite) > -this.tolerance) {
            convex = false
          }
        } else {
          if (this.oppositeFaceDistance(edge.opposite) > -this.tolerance) {
            merge = true
          } else if (this.oppositeFaceDistance(edge) > -this.tolerance) {
            convex = false
          }
        }
      }

      if (merge) {
        // when two faces are merged it might be possible that redundant faces
        // are destroyed, in that case move all the visible vertices from the
        // destroyed faces to the `unclaimed` vertex list
        const discardedFaces = face.mergeAdjacentFaces(edge, [])
        for (let i = 0; i < discardedFaces.length; i += 1) {
          this.deleteFaceVertices(discardedFaces[i], face)
        }
        return true
      }

      edge = edge.next
      it += 1
    } while (edge !== face.edge)
    if (!convex) {
      face.mark = NON_CONVEX
    }
    return false
  }
Merges a face with none/any/all its neighbors according to the strategy
used

if `mergeType` is MERGE_NON_CONVEX_WRT_LARGER_FACE then the merge will be
decided based on the face with the larger area, the centroid of the face
with the smaller area will be checked against the one with the larger area
to see if it's in the merge range [tolerance, -tolerance] i.e.

   dot(centroid smaller face, larger face normal) - larger face offset > -tolerance

Note that the first check (with +tolerance) was done on `computeHorizon`

If the above is not true then the check is done with respect to the smaller
face i.e.

   dot(centroid larger face, smaller face normal) - smaller face offset > -tolerance

If true then it means that two faces are non convex (concave), even if the
dot(...) - offset value is > 0 (that's the point of doing the merge in the
first place)

If two faces are concave then the check must also be done on the other face
but this is done in another merge pass, for this to happen the face is
marked in a temporal NON_CONVEX state

if `mergeType` is MERGE_NON_CONVEX then two faces will be merged only if
they pass the following conditions

   dot(centroid smaller face, larger face normal) - larger face offset > -tolerance
   dot(centroid larger face, smaller face normal) - smaller face offset > -tolerance

@param {Face} face
@param {number} mergeType - Either MERGE_NON_CONVEX_WRT_LARGER_FACE or
MERGE_NON_CONVEX