// Begin License:
// Copyright (C) 2006-2014 Tobias Sargeant (tobias.sargeant@gmail.com).
// All rights reserved.
//
// This file is part of the Carve CSG Library (http://carve-csg.com/)
//
// This file may be used under the terms of either the GNU General
// Public License version 2 or 3 (at your option) as published by the
// Free Software Foundation and appearing in the files LICENSE.GPL2
// and LICENSE.GPL3 included in the packaging of this file.
//
// This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
// INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE.
// End:


#pragma once

#include <carve/carve.hpp>
#include <carve/mesh.hpp>
#include <carve/mesh_ops.hpp>
#include <carve/geom2d.hpp>
#include <carve/heap.hpp>
#include <carve/rtree.hpp>
#include <carve/triangle_intersection.hpp>

#include <fstream>
#include <string>
#include <utility>
#include <set>
#include <algorithm>
#include <vector>


namespace carve {
  namespace mesh {


    class MeshSimplifier {
      typedef carve::mesh::MeshSet<3> meshset_t;
      typedef carve::mesh::Mesh<3> mesh_t;
      typedef mesh_t::vertex_t vertex_t;
      typedef vertex_t::vector_t vector_t;
      typedef mesh_t::edge_t edge_t;
      typedef mesh_t::face_t face_t;
      typedef face_t::aabb_t aabb_t;

      typedef carve::geom::RTreeNode<3, carve::mesh::Face<3> *> face_rtree_t;


      struct EdgeInfo {
        edge_t *edge;
        double delta_v;

        double c[4];
        double l[2], t1[2], t2[2];
        size_t heap_idx;

        void update() {
          const vertex_t *v1 = edge->vert;
          const vertex_t *v2 = edge->next->vert;
          const vertex_t *v3 = edge->next->next->vert;
          const vertex_t *v4 = edge->rev ? edge->rev->next->next->vert : NULL;

          l[0] = (v1->v - v2->v).length();

          t1[0] = (v3->v - v1->v).length();
          t1[1] = (v3->v - v2->v).length();

          c[0] = std::max((t1[0] + t1[1]) / l[0] - 1.0, 0.0);

          if (v4) {
            l[1] = (v3->v - v4->v).length();
            t2[0] = (v4->v - v1->v).length();
            t2[1] = (v4->v - v2->v).length();
            c[1] = std::max((t2[0] + t2[1]) / l[0] - 1.0, 0.0);
            c[2] = std::max((t1[0] + t2[0]) / l[1] - 1.0, 0.0);
            c[3] = std::max((t1[1] + t2[1]) / l[1] - 1.0, 0.0);
            delta_v = carve::geom3d::tetrahedronVolume(v1->v, v2->v, v3->v, v4->v);
          } else {
            l[1] = 0.0;
            t2[0] = t2[1] = 0.0;
            c[1] = c[2] = c[3] = 0.0;
            delta_v = 0.0;
          }
        }

        EdgeInfo(edge_t *e) : edge(e) {
          update();
        }

        EdgeInfo() : edge(NULL) {
          delta_v = 0.0;
          c[0] = c[1] = c[2] = c[3] = 0.0;
          l[0] = l[1] = 0.0;
          t1[0] = t1[1] = 0.0;
          t2[0] = t2[1] = 0.0;
        }

        struct NotifyPos {
          void operator()(EdgeInfo *edge, size_t pos) const { edge->heap_idx = pos; }
          void operator()(EdgeInfo &edge, size_t pos) const { edge.heap_idx = pos; }
        };
      };



      struct FlippableBase {
        double min_dp;

        FlippableBase(double _min_dp = 0.0) : min_dp(_min_dp) {
        }

        bool open(const EdgeInfo *e) const {
          return e->edge->rev == NULL;
        }

        bool wouldCreateDegenerateEdge(const EdgeInfo *e) const {
          return e->edge->prev->vert == e->edge->rev->prev->vert;
        }

        bool flippable_DotProd(const EdgeInfo *e) const { 
          using carve::geom::dot;
          using carve::geom::cross;

          if (open(e)) return false;

          edge_t *edge = e->edge;

          const vertex_t *v1 = edge->vert;
          const vertex_t *v2 = edge->next->vert;
          const vertex_t *v3 = edge->next->next->vert;
          const vertex_t *v4 = edge->rev->next->next->vert;

          if (dot(cross(v3->v - v2->v, v1->v - v2->v).normalized(),
                  cross(v4->v - v1->v, v2->v - v1->v).normalized()) < min_dp) return false;

          if (dot(cross(v3->v - v4->v, v1->v - v4->v).normalized(),
                  cross(v4->v - v3->v, v2->v - v3->v).normalized()) < min_dp) return false;

          return true;
        }

        virtual bool canFlip(const EdgeInfo *e) const {
          return !open(e) && !wouldCreateDegenerateEdge(e) && score(e) > 0.0;
        }

        virtual double score(const EdgeInfo *e) const {
          return std::min(e->c[2], e->c[3]) - std::min(e->c[0], e->c[1]);
        }

        class Priority {
          Priority &operator=(const Priority &);
          const FlippableBase &flip;

        public:
          Priority(const FlippableBase &_flip) : flip(_flip) {}
          bool operator()(const EdgeInfo *a, const EdgeInfo *b) const { return flip.score(a) > flip.score(b); }
        };

        Priority priority() const {
          return Priority(*this);
        }
      };



      struct FlippableConservative : public FlippableBase {
        FlippableConservative() : FlippableBase(0.0) {
        }

        bool connectsAlmostCoplanarFaces(const EdgeInfo *e) const {
          // XXX: remove hard coded constants.
          if (e->c[0] < 1e-10 || e->c[1] < 1e-10) return true;
          return fabs(carve::geom::dot(e->edge->face->plane.N, e->edge->rev->face->plane.N) - 1.0) < 1e-10;
        }

        bool connectsExactlyCoplanarFaces(const EdgeInfo *e) const {
          edge_t *edge = e->edge;
          return
            carve::geom3d::orient3d(edge->vert->v,
                                    edge->next->vert->v,
                                    edge->next->next->vert->v,
                                    edge->rev->next->next->vert->v) == 0.0 &&
            carve::geom3d::orient3d(edge->rev->vert->v,
                                    edge->rev->next->vert->v,
                                    edge->rev->next->next->vert->v,
                                    edge->next->next->vert->v) == 0.0;
        }

        virtual bool canFlip(const EdgeInfo *e) const {
          return FlippableBase::canFlip(e) && connectsExactlyCoplanarFaces(e) && flippable_DotProd(e);
        }
      };



      struct FlippableColinearPair : public FlippableBase {

        FlippableColinearPair() {
        }


        virtual double score(const EdgeInfo *e) const {
          return e->l[0] - e->l[1];
        }

        virtual bool canFlip(const EdgeInfo *e) const {
          if (!FlippableBase::canFlip(e)) return false;

          if (e->c[0] > 1e-3 || e->c[1] > 1e-3) return false;

          return true;
        }
      };



      struct Flippable : public FlippableBase {
        double min_colinearity;
        double min_delta_v;

        Flippable(double _min_colinearity,
                  double _min_delta_v,
                  double _min_normal_angle) :
            FlippableBase(cos(_min_normal_angle)),
            min_colinearity(_min_colinearity),
            min_delta_v(_min_delta_v) {
        }


        virtual bool canFlip(const EdgeInfo *e) const {
          if (!FlippableBase::canFlip(e)) return false;

          if (fabs(e->delta_v) > min_delta_v) return false;

          // if (std::min(e->c[0], e->c[1]) > min_colinearity) return false;

          return flippable_DotProd(e);
        }
      };



      struct EdgeMerger {
        double min_edgelen;

        virtual bool canMerge(const EdgeInfo *e) const {
          return e->l[0] <= min_edgelen;
        }

        EdgeMerger(double _min_edgelen) : min_edgelen(_min_edgelen) {
        }

        double score(const EdgeInfo *e) const {
          return min_edgelen - e->l[0];
        }

        class Priority {
          Priority &operator=(const Priority &);

        public:
          const EdgeMerger &merger;
          Priority(const EdgeMerger &_merger) : merger(_merger) {
          }
          bool operator()(const EdgeInfo *a, const EdgeInfo *b) const {
            // collapse edges in order from shortest to longest.
            return merger.score(a) < merger.score(b);
          }
        };

        Priority priority() const {
          return Priority(*this);
        }
      };



      typedef std::unordered_map<edge_t *, EdgeInfo *> edge_info_map_t;
      std::unordered_map<edge_t *, EdgeInfo *> edge_info;



      void initEdgeInfo(mesh_t *mesh) {
        for (size_t i = 0; i < mesh->faces.size(); ++i) {
          edge_t *e = mesh->faces[i]->edge;
          do {
            edge_info[e] = new EdgeInfo(e);
            e = e->next;
          } while (e != mesh->faces[i]->edge);
        }
      }



      void initEdgeInfo(meshset_t *meshset) {
        for (size_t m = 0; m < meshset->meshes.size(); ++m) {
          mesh_t *mesh = meshset->meshes[m];
          initEdgeInfo(mesh);
        }
      }



      void clearEdgeInfo() {
        for (edge_info_map_t::iterator i = edge_info.begin(); i != edge_info.end(); ++i) {
          delete (*i).second;
        }
      }



      void updateEdgeFlipHeap(std::vector<EdgeInfo *> &edge_heap,
                              edge_t *edge,
                              const FlippableBase &flipper) {
        std::unordered_map<edge_t *, EdgeInfo *>::const_iterator i = edge_info.find(edge);
        CARVE_ASSERT(i != edge_info.end());
        EdgeInfo *e = (*i).second;

        bool heap_pre = e->heap_idx != ~0U;
        (*i).second->update();
        bool heap_post = edge->v1() < edge->v2() && flipper.canFlip(e);

        if (!heap_pre && heap_post) {
          edge_heap.push_back(e);
          carve::heap::push_heap(edge_heap.begin(),
                                 edge_heap.end(),
                                 flipper.priority(),
                                 EdgeInfo::NotifyPos());
        } else if (heap_pre && !heap_post) {
          CARVE_ASSERT(edge_heap[e->heap_idx] == e);
          carve::heap::remove_heap(edge_heap.begin(),
                                   edge_heap.end(),
                                   edge_heap.begin() + e->heap_idx,
                                   flipper.priority(),
                                   EdgeInfo::NotifyPos());
          CARVE_ASSERT(edge_heap.back() == e);
          edge_heap.pop_back();
          e->heap_idx = ~0U;
        } else if (heap_pre && heap_post) {
          CARVE_ASSERT(edge_heap[e->heap_idx] == e);
          carve::heap::adjust_heap(edge_heap.begin(),
                                   edge_heap.end(),
                                   edge_heap.begin() + e->heap_idx,
                                   flipper.priority(),
                                   EdgeInfo::NotifyPos());
          CARVE_ASSERT(edge_heap[e->heap_idx] == e);
        }
      }


      std::string vk(const vertex_t *v1,
                     const vertex_t *v2,
                     const vertex_t *v3) {
        const vertex_t *v[3];
        v[0] = v1; v[1] = v2; v[2] = v3;
        std::sort(v, v+3);
        std::ostringstream s;
        s << v[0] << ";" << v[1] << ";" << v[2];
        return s.str();
      }

      std::string vk(const face_t *f) { return vk(f->edge->vert, f->edge->next->vert, f->edge->next->next->vert); }

      int mapTriangle(const face_t *face,
                      const vertex_t *remap1, const vertex_t *remap2,
                      const vector_t &tgt,
                      vector_t tri[3]) {
        edge_t *edge = face->edge;
        int n_remaps = 0;
        for (size_t i = 0; i < 3; edge = edge->next, ++i) {
          if      (edge->vert == remap1) { tri[i] = tgt; ++n_remaps; }
          else if (edge->vert == remap2) { tri[i] = tgt; ++n_remaps; }
          else                           { tri[i] = edge->vert->v;   }
        }
        return n_remaps;
      }

      template<typename iter1_t, typename iter2_t>
      int countIntersectionPairs(iter1_t fabegin, iter1_t faend,
                                 iter2_t fbbegin, iter2_t fbend,
                                 const vertex_t *remap1, const vertex_t *remap2,
                                 const vector_t &tgt) {
        vector_t tri_a[3], tri_b[3];
        int remap_a, remap_b;
        std::set<std::pair<const face_t *, const face_t *> > ints;

        for (iter1_t i = fabegin; i != faend; ++i) {
          remap_a = mapTriangle(*i, remap1, remap2, tgt, tri_a);
          if (remap_a >= 2) continue;
          for (iter2_t j = fbbegin; j != fbend; ++j) {
            remap_b = mapTriangle(*j, remap1, remap2, tgt, tri_b);
            if (remap_b >= 2) continue;
            if (carve::geom::triangle_intersection_exact(tri_a, tri_b) == carve::geom::TR_TYPE_INT) {
              ints.insert(std::make_pair(std::min(*i, *j), std::max(*i, *j)));
            }
          }
        }

        return ints.size();
      }

      int countIntersections(const vertex_t *v1,
                             const vertex_t *v2,
                             const vertex_t *v3,
                             const std::vector<face_t *> &faces) {
        int n_int = 0;
        vector_t tri_a[3], tri_b[3];
        tri_a[0] = v1->v;
        tri_a[1] = v2->v;
        tri_a[2] = v3->v;

        for (std::vector<face_t *>::const_iterator i = faces.begin(); i != faces.end(); ++i) {
          face_t *fb = *i;
          if (fb->nEdges() != 3) continue;
          tri_b[0] = fb->edge->vert->v;
          tri_b[1] = fb->edge->next->vert->v;
          tri_b[2] = fb->edge->next->next->vert->v;

          if (carve::geom::triangle_intersection_exact(tri_a, tri_b) == carve::geom::TR_TYPE_INT) {
            n_int++;
          }
        }
        return n_int;
      }



      int _findSelfIntersections(const face_rtree_t *a_node,
                                 const face_rtree_t *b_node,
                                 bool descend_a = true) {
        int r = 0;

        if (!a_node->bbox.intersects(b_node->bbox)) {
          return 0;
        }

        if (a_node->child && (descend_a || !b_node->child)) {
          for (face_rtree_t *node = a_node->child; node; node = node->sibling) {
            r += _findSelfIntersections(node, b_node, false);
          }
        } else if (b_node->child) {
          for (face_rtree_t *node = b_node->child; node; node = node->sibling) {
            r += _findSelfIntersections(a_node, node, true);
          }
        } else {
          for (size_t i = 0; i < a_node->data.size(); ++i) {
            face_t *fa = a_node->data[i];
            if (fa->nVertices() != 3) continue;

            aabb_t aabb_a = fa->getAABB();

            vector_t tri_a[3];
            tri_a[0] = fa->edge->vert->v;
            tri_a[1] = fa->edge->next->vert->v;
            tri_a[2] = fa->edge->next->next->vert->v;

            if (!aabb_a.intersects(b_node->bbox)) continue;

            for (size_t j = 0; j < b_node->data.size(); ++j) {
              face_t *fb = b_node->data[j];
              if (fb->nVertices() != 3) continue;

              vector_t tri_b[3];
              tri_b[0] = fb->edge->vert->v;
              tri_b[1] = fb->edge->next->vert->v;
              tri_b[2] = fb->edge->next->next->vert->v;
              
              if (carve::geom::triangle_intersection_exact(tri_a, tri_b) == carve::geom::TR_TYPE_INT) {
                ++r;
              }
            }
          }
        }

        return r;
      }



      int countSelfIntersections(meshset_t *meshset) {
        int n_ints = 0;
        face_rtree_t *tree = face_rtree_t::construct_STR(meshset->faceBegin(), meshset->faceEnd(), 4, 4);

        for (meshset_t::face_iter f = meshset->faceBegin(); f != meshset->faceEnd(); ++f) {
          face_t *fa = *f;
          if (fa->nVertices() != 3) continue;

          vector_t tri_a[3];
          tri_a[0] = fa->edge->vert->v;
          tri_a[1] = fa->edge->next->vert->v;
          tri_a[2] = fa->edge->next->next->vert->v;

          std::vector<face_t *> near_faces;
          tree->search(fa->getAABB(), std::back_inserter(near_faces));

          for (size_t f2 = 0; f2 < near_faces.size(); ++f2) {
            const face_t *fb = near_faces[f2];
            if (fb->nVertices() != 3) continue;

            if (fa >= fb) continue;

            vector_t tri_b[3];
            tri_b[0] = fb->edge->vert->v;
            tri_b[1] = fb->edge->next->vert->v;
            tri_b[2] = fb->edge->next->next->vert->v;

            if (carve::geom::triangle_intersection_exact(tri_a, tri_b) == carve::geom::TR_TYPE_INT) {
              ++n_ints;
            }
          }
        }

        delete tree;

        return n_ints;
      }

      size_t flipEdges(meshset_t *mesh,
                       const FlippableBase &flipper) {
        face_rtree_t *tree = face_rtree_t::construct_STR(mesh->faceBegin(), mesh->faceEnd(), 4, 4);

        size_t n_mods = 0;

        std::vector<EdgeInfo *> edge_heap;

        edge_heap.reserve(edge_info.size());

        for (edge_info_map_t::iterator i = edge_info.begin();
             i != edge_info.end();
             ++i) {
          EdgeInfo *e = (*i).second;
          e->update();
          if (e->edge->v1() < e->edge->v2() && flipper.canFlip(e)) {
            edge_heap.push_back(e);
          } else {
            e->heap_idx = ~0U;
          }
        }

        carve::heap::make_heap(edge_heap.begin(),
                               edge_heap.end(),
                               flipper.priority(),
                               EdgeInfo::NotifyPos());

        while (edge_heap.size()) {
//           std::cerr << "test" << std::endl;
//           for (size_t m = 0; m < mesh->meshes.size(); ++m) {
//             for (size_t f = 0; f < mesh->meshes[m]->faces.size(); ++f) {
//               if (mesh->meshes[m]->faces[f]->edge) mesh->meshes[m]->faces[f]->edge->validateLoop();
//             }
//           }

          carve::heap::pop_heap(edge_heap.begin(),
                                edge_heap.end(),
                                flipper.priority(),
                                EdgeInfo::NotifyPos());
          EdgeInfo *e = edge_heap.back();
//           std::cerr << "flip " << e << std::endl;
          edge_heap.pop_back();
          e->heap_idx = ~0U;

          aabb_t aabb;
          aabb = e->edge->face->getAABB();
          aabb.unionAABB(e->edge->rev->face->getAABB());

          std::vector<face_t *> overlapping;
          tree->search(aabb, std::back_inserter(overlapping));

          // overlapping.erase(e->edge->face);
          // overlapping.erase(e->edge->rev->face);

          const vertex_t *v1 = e->edge->vert;
          const vertex_t *v2 = e->edge->next->vert;
          const vertex_t *v3 = e->edge->next->next->vert;
          const vertex_t *v4 = e->edge->rev->next->next->vert;

          int n_int1 = countIntersections(v1, v2, v3, overlapping);
          int n_int2 = countIntersections(v2, v1, v4, overlapping);
          int n_int3 = countIntersections(v3, v4, v2, overlapping);
          int n_int4 = countIntersections(v4, v3, v1, overlapping);

          if ((n_int3 + n_int4) - (n_int1 + n_int2) > 0) {
            std::cerr << "delta[ints] = " << (n_int3 + n_int4) - (n_int1 + n_int2) << std::endl;
            // avoid creating a self intersection.
            continue;
          }

          n_mods++;
          CARVE_ASSERT(flipper.canFlip(e));
          edge_info[e->edge]->update();
          edge_info[e->edge->rev]->update();

          carve::mesh::flipTriEdge(e->edge);

          tree->updateExtents(aabb);

          updateEdgeFlipHeap(edge_heap, e->edge, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->rev, flipper);

          CARVE_ASSERT(!flipper.canFlip(e));

          updateEdgeFlipHeap(edge_heap, e->edge->next, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->next->next, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->rev->next, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->rev->next->next, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->next->rev, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->next->next->rev, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->rev->next->rev, flipper);
          updateEdgeFlipHeap(edge_heap, e->edge->rev->next->next->rev, flipper);
        }

        delete tree;

        return n_mods;
      }



      void removeFromEdgeMergeHeap(std::vector<EdgeInfo *> &edge_heap,
                                   EdgeInfo *edge,
                                   const EdgeMerger &merger) {
        if (edge->heap_idx != ~0U) {
          CARVE_ASSERT(edge_heap[edge->heap_idx] == edge);
          carve::heap::remove_heap(edge_heap.begin(),
                                   edge_heap.end(),
                                   edge_heap.begin() + edge->heap_idx,
                                   merger.priority(),
                                   EdgeInfo::NotifyPos());
          CARVE_ASSERT(edge_heap.back() == edge);
          edge_heap.pop_back();
          edge->heap_idx = ~0U;
        }
      }

      void updateEdgeMergeHeap(std::vector<EdgeInfo *> &edge_heap,
                               EdgeInfo *edge,
                               const EdgeMerger &merger) {
        bool heap_pre = edge->heap_idx != ~0U;
        edge->update();
        bool heap_post = merger.canMerge(edge);

        if (!heap_pre && heap_post) {
          edge_heap.push_back(edge);
          carve::heap::push_heap(edge_heap.begin(),
                                 edge_heap.end(),
                                 merger.priority(),
                                 EdgeInfo::NotifyPos());
        } else if (heap_pre && !heap_post) {
          CARVE_ASSERT(edge_heap[edge->heap_idx] == edge);
          carve::heap::remove_heap(edge_heap.begin(),
                                   edge_heap.end(),
                                   edge_heap.begin() + edge->heap_idx,
                                   merger.priority(),
                                   EdgeInfo::NotifyPos());
          CARVE_ASSERT(edge_heap.back() == edge);
          edge_heap.pop_back();
          edge->heap_idx = ~0U;
        } else if (heap_pre && heap_post) {
          CARVE_ASSERT(edge_heap[edge->heap_idx] == edge);
          carve::heap::adjust_heap(edge_heap.begin(),
                                   edge_heap.end(),
                                   edge_heap.begin() + edge->heap_idx,
                                   merger.priority(),
                                   EdgeInfo::NotifyPos());
          CARVE_ASSERT(edge_heap[edge->heap_idx] == edge);
        }
      }



      // collapse edges edges based upon the predicate implemented by EdgeMerger.
      size_t collapseEdges(meshset_t *mesh,
                           const EdgeMerger &merger) {
        face_rtree_t *tree = face_rtree_t::construct_STR(mesh->faceBegin(), mesh->faceEnd(), 4, 4);

        size_t n_mods = 0;

        std::vector<EdgeInfo *> edge_heap;
        std::unordered_map<vertex_t *, std::set<EdgeInfo *> > vert_to_edges;

        edge_heap.reserve(edge_info.size());

        for (edge_info_map_t::iterator i = edge_info.begin();
             i != edge_info.end();
             ++i) {
          EdgeInfo *e = (*i).second;

          vert_to_edges[e->edge->v1()].insert(e);
          vert_to_edges[e->edge->v2()].insert(e);

          if (merger.canMerge(e)) {
            edge_heap.push_back(e);
          } else {
            e->heap_idx = ~0U;
          }
        }

        carve::heap::make_heap(edge_heap.begin(),
                               edge_heap.end(),
                               merger.priority(),
                               EdgeInfo::NotifyPos());

        while (edge_heap.size()) {
//           std::cerr << "test" << std::endl;
//           for (size_t m = 0; m < mesh->meshes.size(); ++m) {
//             for (size_t f = 0; f < mesh->meshes[m]->faces.size(); ++f) {
//               if (mesh->meshes[m]->faces[f]->edge) mesh->meshes[m]->faces[f]->edge->validateLoop();
//             }
//           }
          carve::heap::pop_heap(edge_heap.begin(),
                                edge_heap.end(),
                                merger.priority(),
                                EdgeInfo::NotifyPos());
          EdgeInfo *e = edge_heap.back();
          edge_heap.pop_back();
          e->heap_idx = ~0U;

          edge_t *edge = e->edge;
          vertex_t *v1 = edge->v1();
          vertex_t *v2 = edge->v2();

          std::set<face_t *> affected_faces;
          for (std::set<EdgeInfo *>::iterator f = vert_to_edges[v1].begin();
               f != vert_to_edges[v1].end();
               ++f) {
            affected_faces.insert((*f)->edge->face);
            affected_faces.insert((*f)->edge->rev->face);
          }
          for (std::set<EdgeInfo *>::iterator f = vert_to_edges[v2].begin();
               f != vert_to_edges[v2].end();
               ++f) {
            affected_faces.insert((*f)->edge->face);
            affected_faces.insert((*f)->edge->rev->face);
          }

          std::vector<EdgeInfo *> edges_to_merge;
          std::vector<EdgeInfo *> v1_incident;
          std::vector<EdgeInfo *> v2_incident;

          std::set_intersection(vert_to_edges[v1].begin(), vert_to_edges[v1].end(),
                                vert_to_edges[v2].begin(), vert_to_edges[v2].end(),
                                std::back_inserter(edges_to_merge));

          CARVE_ASSERT(edges_to_merge.size() > 0);

          std::set_difference(vert_to_edges[v1].begin(), vert_to_edges[v1].end(),
                              edges_to_merge.begin(), edges_to_merge.end(),
                              std::back_inserter(v1_incident));
          std::set_difference(vert_to_edges[v2].begin(), vert_to_edges[v2].end(),
                              edges_to_merge.begin(), edges_to_merge.end(),
                              std::back_inserter(v2_incident));

          vector_t aabb_min, aabb_max;
          assign_op(aabb_min, v1->v, v2->v, carve::util::min_functor());
          assign_op(aabb_max, v1->v, v2->v, carve::util::max_functor());
          
          for (size_t i = 0; i < v1_incident.size(); ++i) {
            assign_op(aabb_min, aabb_min, v1_incident[i]->edge->v1()->v, carve::util::min_functor());
            assign_op(aabb_max, aabb_max, v1_incident[i]->edge->v1()->v, carve::util::max_functor());
            assign_op(aabb_min, aabb_min, v1_incident[i]->edge->v2()->v, carve::util::min_functor());
            assign_op(aabb_max, aabb_max, v1_incident[i]->edge->v2()->v, carve::util::max_functor());
          }

          for (size_t i = 0; i < v2_incident.size(); ++i) {
            assign_op(aabb_min, aabb_min, v2_incident[i]->edge->v1()->v, carve::util::min_functor());
            assign_op(aabb_max, aabb_max, v2_incident[i]->edge->v1()->v, carve::util::max_functor());
            assign_op(aabb_min, aabb_min, v2_incident[i]->edge->v2()->v, carve::util::min_functor());
            assign_op(aabb_max, aabb_max, v2_incident[i]->edge->v2()->v, carve::util::max_functor());
          }

          aabb_t aabb;
          aabb.fit(aabb_min, aabb_max);

          std::vector<face_t *> near_faces;
          tree->search(aabb, std::back_inserter(near_faces));

          double frac = 0.5; // compute this based upon v1_incident and v2_incident?
          vector_t merge = frac * v1->v + (1 - frac) * v2->v;

          int i1 = countIntersectionPairs(affected_faces.begin(), affected_faces.end(),
                                          near_faces.begin(), near_faces.end(),
                                          NULL, NULL, merge);
          int i2 = countIntersectionPairs(affected_faces.begin(), affected_faces.end(),
                                          near_faces.begin(), near_faces.end(),
                                          v1, v2, merge);
          if (i2 != i1) {
            std::cerr << "near faces: " << near_faces.size() << " affected faces: " << affected_faces.size() << std::endl;
            std::cerr << "merge delta[ints] = " << i2 - i1 << " pre: " << i1 << " post: " << i2 << std::endl;
            if (i2 > i1) continue;
          }

          std::cerr << "collapse " << e << std::endl;

          v2->v = merge;
          ++n_mods;

          for (size_t i = 0; i < v1_incident.size(); ++i) {
            if (v1_incident[i]->edge->vert == v1) {
              v1_incident[i]->edge->vert = v2;
            }
          }

          for (size_t i = 0; i < v1_incident.size(); ++i) {
            updateEdgeMergeHeap(edge_heap, v1_incident[i], merger);
          }

          for (size_t i = 0; i < v2_incident.size(); ++i) {
            updateEdgeMergeHeap(edge_heap, v2_incident[i], merger);
          }

          vert_to_edges[v2].insert(vert_to_edges[v1].begin(), vert_to_edges[v1].end());
          vert_to_edges.erase(v1);

          for (size_t i = 0; i < edges_to_merge.size(); ++i) {
            EdgeInfo *e = edges_to_merge[i];

            removeFromEdgeMergeHeap(edge_heap, e, merger);
            edge_info.erase(e->edge);

            vert_to_edges[v1].erase(e);
            vert_to_edges[v2].erase(e);

            face_t *f1 = e->edge->face;

            e->edge->removeHalfEdge();

            if (f1->n_edges == 2) {
              edge_t *e1 = f1->edge;
              edge_t *e2 = f1->edge->next;
              if (e1->rev) e1->rev->rev = e2->rev;
              if (e2->rev) e2->rev->rev = e1->rev;
              EdgeInfo *e1i = edge_info[e1];
              EdgeInfo *e2i = edge_info[e2];
              CARVE_ASSERT(e1i != NULL);
              CARVE_ASSERT(e2i != NULL);
              vert_to_edges[e1->v1()].erase(e1i);
              vert_to_edges[e1->v2()].erase(e1i);
              vert_to_edges[e2->v1()].erase(e2i);
              vert_to_edges[e2->v2()].erase(e2i);
              removeFromEdgeMergeHeap(edge_heap, e1i, merger);
              removeFromEdgeMergeHeap(edge_heap, e2i, merger);
              edge_info.erase(e1);
              edge_info.erase(e2);
              f1->clearEdges();
              tree->remove(f1, aabb);

              delete e1i;
              delete e2i;
            }
            delete e;
          }

          tree->updateExtents(aabb);
        }

        delete tree;

        return n_mods;
      }



      size_t mergeCoplanarFaces(mesh_t *mesh, double min_normal_angle) {
        std::unordered_set<edge_t *> coplanar_face_edges;
        double min_dp = cos(min_normal_angle);
        size_t n_merge = 0;

        for (size_t i = 0; i < mesh->closed_edges.size(); ++i) {
          edge_t *e = mesh->closed_edges[i];
          face_t *f1 = e->face;
          face_t *f2 = e->rev->face;

          if (carve::geom::dot(f1->plane.N, f2->plane.N) < min_dp) {
            continue;
          }

          coplanar_face_edges.insert(std::min(e, e->rev));
        }

        while (coplanar_face_edges.size()) {
          edge_t *edge = *coplanar_face_edges.begin();
          if (edge->face == edge->rev->face) {
            coplanar_face_edges.erase(edge);
            continue;
          }

          edge_t *removed = edge->mergeFaces();
          if (removed == NULL) {
            coplanar_face_edges.erase(edge);
            ++n_merge;
          } else {
            edge_t *e = removed;
            do {
              edge_t *n = e->next;
              coplanar_face_edges.erase(std::min(e, e->rev));
              delete e->rev;
              delete e;
              e = n;
            } while (e != removed);
          }
        }
        return n_merge;
      }



      uint8_t affected_axes(const face_t *face) {
        uint8_t r = 0;
        if (fabs(carve::geom::dot(face->plane.N, carve::geom::VECTOR(1,0,0))) > 0.001) r |= 1;
        if (fabs(carve::geom::dot(face->plane.N, carve::geom::VECTOR(0,1,0))) > 0.001) r |= 2;
        if (fabs(carve::geom::dot(face->plane.N, carve::geom::VECTOR(0,0,1))) > 0.001) r |= 4;
        return r;
      }



      double median(std::vector<double> &v) {
        if (v.size() & 1) {
          size_t N = v.size() / 2 + 1;
          std::nth_element(v.begin(), v.begin() + N, v.end());
          return v[N];
        } else {
          size_t N = v.size() / 2;
          std::nth_element(v.begin(), v.begin() + N, v.end());
          return (v[N] + *std::min_element(v.begin() + N + 1, v.end())) / 2.0;
        }
      }



      double harmonicmean(const std::vector<double> &v) {
        double m = 0.0;
        for (size_t i = 0; i < v.size(); ++i) {
          m *= v[i];
        }
        return pow(m, 1.0 / v.size());
      }



      double mean(const std::vector<double> &v) {
        double m = 0.0;
        for (size_t i = 0; i < v.size(); ++i) {
          m += v[i];
        }
        return m / v.size();
      }



      template<typename iter_t>
      void snapFaces(iter_t begin, iter_t end, double grid, int axis) {
        std::set<vertex_t *> vertices;
        for (iter_t i = begin; i != end; ++i) {
          face_t *face = *i;
          edge_t *edge = face->edge;
          do {
            vertices.insert(edge->vert);
            edge = edge->next;
          } while (edge != face->edge);
        }

        std::vector<double> pos;
        pos.reserve(vertices.size());
        for (std::set<vertex_t *>::iterator i = vertices.begin(); i != vertices.end(); ++i) {
          pos.push_back((*i)->v.v[axis]);
        }

        double med = median(pos);

        double snap_pos = med;
        if (grid) snap_pos = round(snap_pos / grid) * grid;

        for (std::set<vertex_t *>::iterator i = vertices.begin(); i != vertices.end(); ++i) {
          (*i)->v.v[axis] = snap_pos;
        }

        for (iter_t i = begin; i != end; ++i) {
          face_t *face = *i;
          face->recalc();
          edge_t *edge = face->edge;
          do {
            if (edge->rev && edge->rev->face) edge->rev->face->recalc();
            edge = edge->next;
          } while (edge != face->edge);
        }
      }

      carve::geom::plane<3> quantizePlane(const face_t *face,
                                          int angle_xy_quantization,
                                          int angle_z_quantization) {
        if (!angle_xy_quantization && !angle_z_quantization) {
          return face->plane;
        }
        carve::geom::vector<3> normal = face->plane.N;

        if (angle_z_quantization) {
          if (normal.x || normal.y) {
            double a = asin(std::min(std::max(normal.z, 0.0), 1.0));
            a = round(a * angle_z_quantization / (M_PI * 2)) * (M_PI * 2) / angle_z_quantization;
            normal.z = sin(a);
            double s = sqrt((1 - normal.z * normal.z) / (normal.x * normal.x + normal.y * normal.y));
            normal.x = normal.x * s;
            normal.y = normal.y * s;
          }
        }
        if (angle_xy_quantization) {
          if (normal.x || normal.y) {
            double a = atan2(normal.y, normal.x);
            a = round(a * angle_xy_quantization / (M_PI * 2)) * (M_PI * 2) / angle_xy_quantization;
            double s = sqrt(1 - normal.z * normal.z);
            s = std::min(std::max(s, 0.0), 1.0);
            normal.x = cos(a) * s;
            normal.y = sin(a) * s;
          }
        }

        std::cerr << "normal = " << normal << std::endl;

        std::vector<double> d_vec;
        d_vec.reserve(face->nVertices());
        edge_t *e = face->edge;
        do {
          d_vec.push_back(-carve::geom::dot(normal, e->vert->v));
          e = e->next;
        } while (e != face->edge);

        return carve::geom::plane<3>(normal, mean(d_vec));
      }



      double summedError(const carve::geom::vector<3> &vert, const std::list<carve::geom::plane<3> > &planes) {
        double d = 0;
        for (std::list<carve::geom::plane<3> >::const_iterator i = planes.begin(); i != planes.end(); ++i) {
          d += fabs(carve::geom::distance2(*i, vert));
        }
        return d;
      }



      double minimize(carve::geom::vector<3> &vert, const std::list<carve::geom::plane<3> > &planes, int axis) {
        double num = 0.0;
        double den = 0.0;
        int a1 = (axis + 1) % 3;
        int a2 = (axis + 2) % 3;
        for (std::list<carve::geom::plane<3> >::const_iterator i = planes.begin(); i != planes.end(); ++i) {
          const carve::geom::vector<3> &N = (*i).N;
          const double d = (*i).d;
          den += N.v[axis] * N.v[axis];
          num -= N.v[axis] * (N.v[a1] * vert.v[a1] + N.v[a2] * vert.v[a2] + d);
        }
        if (fabs(den) < 1e-5) return vert.v[axis];
        return num / den;
      }



      size_t cleanFaceEdges(mesh_t *mesh) {
        size_t n_removed = 0;
        for (size_t i = 0; i < mesh->faces.size(); ++i) {
          face_t *face = mesh->faces[i];
          edge_t *start = face->edge;
          edge_t *edge = start;
          do {
            if (edge->next == edge->rev || edge->prev == edge->rev) {
              edge = edge->removeEdge();
              ++n_removed;
              start = edge->prev;
            } else {
              edge = edge->next;
            }
          } while (edge != start);
        }
        return n_removed;
      }



      size_t cleanFaceEdges(meshset_t *mesh) {
        size_t n_removed = 0;
        for (size_t i = 0; i < mesh->meshes.size(); ++i) {
          n_removed += cleanFaceEdges(mesh->meshes[i]);
        }
        return n_removed;
      }



      void removeRemnantFaces(mesh_t *mesh) {
        size_t n = 0;
        for (size_t i = 0; i < mesh->faces.size(); ++i) {
          if (mesh->faces[i]->nEdges() == 0) {
            delete mesh->faces[i];
          } else {
            mesh->faces[n++] = mesh->faces[i];
          }
        }
        mesh->faces.resize(n);
      }



      void removeRemnantFaces(meshset_t *mesh) {
        for (size_t i = 0; i < mesh->meshes.size(); ++i) {
          removeRemnantFaces(mesh->meshes[i]);
        }
      }



      edge_t *removeFin(edge_t *e) {
        // e and e->next are shared with the same reverse triangle.
        edge_t *e1 = e->prev;
        edge_t *e2 = e->rev->next;
        CARVE_ASSERT(e1->v2() == e2->v1());
        CARVE_ASSERT(e2->v2() == e1->v1());

        CARVE_ASSERT(e1->rev != e2 && e2->rev != e1);

        edge_t *e1r = e1->rev;
        edge_t *e2r = e2->rev;
        if (e1r) e1r->rev = e2r;
        if (e2r) e2r->rev = e1r;

        face_t *f1 = e1->face;
        face_t *f2 = e2->face;
        f1->clearEdges();
        f2->clearEdges();

        return e1r;
      }

      size_t removeFin(face_t *face) {
        if (face->edge == NULL || face->nEdges() != 3) return 0;
        edge_t *e = face->edge;
        do {
          if (e->rev != NULL) {
            face_t *revface = e->rev->face;
            if (revface->nEdges() == 3) {
              if (e->next->rev && e->next->rev->face == revface) {
                if (e->next->next->rev && e->next->next->rev->face == revface) {
                  // isolated tripair
                  face->clearEdges();
                  revface->clearEdges();
                  return 1;
                }
                // fin
                edge_t *spliced_edge = removeFin(e);
                return 1 + removeFin(spliced_edge->face);
              }
            }
          }
          e = e->next;
        } while (e != face->edge);
        return 0;
      }



    public:
      // Merge adjacent coplanar faces (where coplanar is determined
      // by dot-product >= cos(min_normal_angle)).
      size_t mergeCoplanarFaces(meshset_t *meshset, double min_normal_angle) {
        size_t n_removed = 0;
        for (size_t i = 0; i < meshset->meshes.size(); ++i) {
          n_removed += mergeCoplanarFaces(meshset->meshes[i], min_normal_angle);
          removeRemnantFaces(meshset->meshes[i]);
          cleanFaceEdges(meshset->meshes[i]);
          meshset->meshes[i]->cacheEdges();
        }
        return n_removed;
      }

      size_t improveMesh_conservative(meshset_t *meshset) {
        initEdgeInfo(meshset);
        size_t modifications = flipEdges(meshset, FlippableConservative());
        clearEdgeInfo();
        return modifications;
      }

      void dissolveMeshEdges(mesh_t *mesh, std::unordered_set<edge_t *> dissolve_edges) {
        while (dissolve_edges.size()) {
            MeshSet<3>::edge_t *edge = *dissolve_edges.begin();
            if (edge->face == edge->rev->face) {
                dissolve_edges.erase(edge);
                continue;
            }

            MeshSet<3>::edge_t *removed = edge->mergeFaces();
            if (removed == NULL) {
                dissolve_edges.erase(edge);
            } else {
                MeshSet<3>::edge_t *e = removed;
                do {
                    MeshSet<3>::edge_t *n = e->next;
                    dissolve_edges.erase(std::min(e, e->rev));
                    delete e->rev;
                    delete e;
                    e = n;
                } while (e != removed);
            }
        }

        removeRemnantFaces(mesh);
        cleanFaceEdges(mesh);
        mesh->cacheEdges();
     }


      size_t improveMesh(meshset_t *meshset,
                         double min_colinearity,
                         double min_delta_v,
                         double min_normal_angle) {
        initEdgeInfo(meshset);
        size_t modifications = flipEdges(meshset, Flippable(min_colinearity, min_delta_v, min_normal_angle));
        clearEdgeInfo();
        return modifications;
      }



      size_t eliminateShortEdges(meshset_t *meshset,
                                 double min_length) {
        initEdgeInfo(meshset);
        size_t modifications = collapseEdges(meshset, EdgeMerger(min_length));
        removeRemnantFaces(meshset);
        clearEdgeInfo();
        return modifications;
      }



      // Snap vertices to grid, aligning almost flat axis-aligned
      // faces to the axis, and flattening other faces as much as is
      // possible. Passing a number less than DBL_MIN_EXPONENT (-1021)
      // turns off snapping to grid (but face alignment is still
      // performed).
      void snap(meshset_t *meshset,
                int log2_grid,
                int angle_xy_quantization = 0,
                int angle_z_quantization = 0) {
        double grid = 0.0;
        if (log2_grid >= std::numeric_limits<double>::min_exponent) grid = pow(2.0, (double)log2_grid);

        typedef std::unordered_map<face_t *, uint8_t> axis_influence_map_t;
        axis_influence_map_t axis_influence;

        typedef std::unordered_map<face_t *, std::set<face_t *> > interaction_graph_t;
        interaction_graph_t interacting_faces;

        for (size_t m = 0; m < meshset->meshes.size(); ++m) {
          mesh_t *mesh = meshset->meshes[m];
          for (size_t f = 0; f < mesh->faces.size(); ++f) {
            face_t *face = mesh->faces[f];
            axis_influence[face] = affected_axes(face);
          }
        }

        std::map<vertex_t *, std::list<carve::geom::plane<3> > > non_axis_vertices;
        std::unordered_map<vertex_t *, uint8_t> vertex_constraints;

        for (axis_influence_map_t::iterator i = axis_influence.begin(); i != axis_influence.end(); ++i) {
          face_t *face = (*i).first;
          uint8_t face_axes = (*i).second;
          edge_t *edge = face->edge;
          if (face_axes != 1 && face_axes != 2 && face_axes != 4) {
            do {
              non_axis_vertices[edge->vert].push_back(quantizePlane(face,
                                                                    angle_xy_quantization,
                                                                    angle_z_quantization));
              edge = edge->next;
            } while (edge != face->edge);
          } else {
            interacting_faces[face].insert(face);
            do {
              vertex_constraints[edge->vert] |= face_axes;

              if (edge->rev && edge->rev->face) {
                face_t *face2 = edge->rev->face;
                uint8_t face2_axes = axis_influence[face2];
                if (face2_axes == face_axes) {
                  interacting_faces[face].insert(face2);
                }
              }
              edge = edge->next;
            } while (edge != face->edge);
          }
        }

        while (interacting_faces.size()) {
          std::set<face_t *> face_set;
          uint8_t axes = 0;

          std::set<face_t *> open;
          open.insert((*interacting_faces.begin()).first);
          while (open.size()) {
            face_t *curr = *open.begin();
            open.erase(open.begin());
            face_set.insert(curr);
            axes |= axis_influence[curr];
            for (interaction_graph_t::data_type::iterator i = interacting_faces[curr].begin(), e = interacting_faces[curr].end(); i != e; ++i) {
              face_t *f = *i;
              if (face_set.find(f) != face_set.end()) continue;
              open.insert(f);
            }
          }

          switch (axes) {
          case 1: snapFaces(face_set.begin(), face_set.end(), grid, 0); break;
          case 2: snapFaces(face_set.begin(), face_set.end(), grid, 1); break;
          case 4: snapFaces(face_set.begin(), face_set.end(), grid, 2); break;
          default: CARVE_FAIL("should not be reached");
          }

          for (std::set<face_t *>::iterator i = face_set.begin(); i != face_set.end(); ++i) {
            interacting_faces.erase((*i));
          }
        }

        for (std::map<vertex_t *, std::list<carve::geom::plane<3> > >::iterator i = non_axis_vertices.begin(); i != non_axis_vertices.end(); ++i) {
          vertex_t *vert = (*i).first;
          std::list<carve::geom::plane<3> > &planes = (*i).second;
          uint8_t constraint = vertex_constraints[vert];

          if (constraint == 7) continue;

          double d = summedError(vert->v, planes);
          for (size_t N = 0; ; N = (N+1) % 3) {
            if (constraint & (1 << N)) continue;
            vert->v[N] = minimize(vert->v, planes, N);
            double d_next = summedError(vert->v, planes);
            if (d - d_next < 1e-20) break;
            d = d_next;
          }

          if (grid) {
            carve::geom::vector<3> v_best = vert->v;
            double d_best = 0.0;
            
            for (int axes = 0; axes < 8; ++axes) {
              carve::geom::vector<3> v = vert->v;
              for (int N = 0; N < 3; ++N) {
                if (constraint & (1 << N)) continue;
                if (axes & (1<<N)) {
                  v.v[N] = ceil(v.v[N] / grid) * grid;
                } else {
                  v.v[N] = floor(v.v[N] / grid) * grid;
                }
              }
              double d = summedError(v, planes);
              if (axes == 0 || d < d_best) {
                v_best = v;
                d_best = d;
              }
            }

            vert->v = v_best;
          }
        }
      }



      size_t simplify(meshset_t *meshset,
                      double min_colinearity,
                      double min_delta_v,
                      double min_normal_angle,
                      double min_length) {
        size_t modifications = 0;
        size_t n, n_flip, n_merge;

        initEdgeInfo(meshset);

        std::cerr << "initial merge" << std::endl;
        modifications = collapseEdges(meshset, EdgeMerger(0.0));
        removeRemnantFaces(meshset);

        do {
          n_flip = n_merge = 0;
          // std::cerr << "flip colinear pairs";
          // n = flipEdges(meshset, FlippableColinearPair());
          // std::cerr << " " << n << std::endl;
          // n_flip = n;

          std::cerr << "flip conservative";
          n = flipEdges(meshset, FlippableConservative());
          std::cerr << " " << n << std::endl;
          n_flip += n;

          std::cerr << "flip";
          n = flipEdges(meshset, Flippable(min_colinearity, min_delta_v, min_normal_angle));
          std::cerr << " " << n << std::endl;
          n_flip += n;

          std::cerr << "merge";
          n = collapseEdges(meshset, EdgeMerger(min_length));
          removeRemnantFaces(meshset);
          std::cerr << " " << n << std::endl;
          n_merge = n;

          modifications += n_flip + n_merge;
          std::cerr << "stats:" << n_flip << " " << n_merge << std::endl;
        } while (n_flip || n_merge);

        clearEdgeInfo();

        for (size_t i = 0; i < meshset->meshes.size(); ++i) {
          meshset->meshes[i]->cacheEdges();
        }

        return modifications;
      }

      
      
      size_t removeFins(mesh_t *mesh) {
        size_t n_removed = 0;
        for (size_t i = 0; i < mesh->faces.size(); ++i) {
          n_removed += removeFin(mesh->faces[i]);
        }
        if (n_removed) removeRemnantFaces(mesh);
        return n_removed;
      }



      size_t removeFins(meshset_t *meshset) {
        size_t n_removed = 0;
        for (size_t i = 0; i < meshset->meshes.size(); ++i) {
          n_removed += removeFins(meshset->meshes[i]);
        }
        return n_removed;
      }



      size_t removeLowVolumeManifolds(meshset_t *meshset, double min_abs_volume) {
        size_t n_removed = 0;
        for (size_t i = 0; i < meshset->meshes.size(); ++i) {
          if (fabs(meshset->meshes[i]->volume()) < min_abs_volume) {
            delete meshset->meshes[i];
            meshset->meshes[i] = NULL;
            ++n_removed;
          }
        }
        meshset->meshes.erase(std::remove_if(meshset->meshes.begin(),
                                             meshset->meshes.end(),
                                             std::bind2nd(std::equal_to<mesh_t *>(), (mesh_t *)NULL)),
                              meshset->meshes.end());
        return n_removed;
      }

      struct point_enumerator_t {
        struct heapval_t {
          double dist;
          vector_t pt;
          heapval_t(double _dist, vector_t _pt) : dist(_dist), pt(_pt) {
          }
          heapval_t() {}
          bool operator==(const heapval_t &other) const { return dist == other.dist && pt == other.pt; }
          bool operator<(const heapval_t &other) const { return dist > other.dist || (dist == other.dist && pt > other.pt); }
        };

        vector_t origin;
        double rounding_fac;
        heapval_t last;
        std::vector<heapval_t> heap;

        point_enumerator_t(vector_t _origin, int _base, int _n_dp) : origin(_origin), rounding_fac(pow((double)_base, _n_dp)), last(-1.0, _origin), heap() {
          for (int i = 0; i < (1 << 3); ++i) {
            vector_t t = origin;
            for (int j = 0; j < 3; ++j) {
              if (i & (1U << j)) {
                t[j] = ceil(t[j] * rounding_fac) / rounding_fac;
              } else {
                t[j] = floor(t[j] * rounding_fac) / rounding_fac;
              }
            }
            heap.push_back(heapval_t(carve::geom::distance2(origin, t), t));
          }
          std::make_heap(heap.begin(), heap.end());
        }

        vector_t next() {
          heapval_t curr;
          do {
            CARVE_ASSERT(heap.size());
            std::pop_heap(heap.begin(), heap.end());
            curr = heap.back();
            heap.pop_back();
          } while (curr == last);

          vector_t t;

          for (int dx = -1; dx <= +1; ++dx) {
            t.x = floor(curr.pt.x * rounding_fac + dx) / rounding_fac;
            for (int dy = -1; dy <= +1; ++dy) {
              t.y = floor(curr.pt.y * rounding_fac + dy) / rounding_fac;
              for (int dz = -1; dz <= +1; ++dz) {
                t.z = floor(curr.pt.z * rounding_fac + dz) / rounding_fac;
                heapval_t h2(carve::geom::distance2(origin, t), t);
                if (h2 < curr) {
                  heap.push_back(h2);
                  std::push_heap(heap.begin(), heap.end());
                }
              }
            }
          }
          last = curr;
          return curr.pt;
        }
      };

      struct quantization_info_t {
        point_enumerator_t *pt;
        std::set<face_t *> faces;

        quantization_info_t() : pt(NULL), faces() {
        }

        ~quantization_info_t() {
          if (pt) delete pt;
        }

        aabb_t getAABB() const {
          std::set<face_t *>::const_iterator i = faces.begin();
          aabb_t aabb = (*i)->getAABB();
          while (++i != faces.end()) {
            aabb.unionAABB((*i)->getAABB());
          }
          return aabb;
        }
      };

      void selfIntersectionAwareQuantize(meshset_t *meshset, int base, int n_dp) {
        typedef std::unordered_map<vertex_t *, quantization_info_t> vfsmap_t;

        vfsmap_t vertex_qinfo;

        for (size_t m = 0; m < meshset->meshes.size(); ++m) {
          mesh_t *mesh = meshset->meshes[m];
          for (size_t f = 0; f < mesh->faces.size(); ++f) {
            face_t *face = mesh->faces[f];
            edge_t *e = face->edge;
            do {
              vertex_qinfo[e->vert].faces.insert(face);
              e = e->next;
            } while (e != face->edge);
          }
        }

        face_rtree_t *tree = face_rtree_t::construct_STR(meshset->faceBegin(), meshset->faceEnd(), 4, 4);

        for (vfsmap_t::iterator i = vertex_qinfo.begin(); i != vertex_qinfo.end(); ++i) {
          (*i).second.pt = new point_enumerator_t((*i).first->v, base, n_dp);
        }

        while (vertex_qinfo.size()) {
          std::vector<vertex_t *> quantized;

          std::cerr << "vertex_qinfo.size() == " << vertex_qinfo.size() << std::endl;

          for (vfsmap_t::iterator i = vertex_qinfo.begin(); i != vertex_qinfo.end(); ++i) {
            vertex_t *vert = (*i).first;
            quantization_info_t &qi = (*i).second;
            vector_t q_pt = qi.pt->next();
            aabb_t aabb = qi.getAABB();
            aabb.unionAABB(aabb_t(q_pt));

            std::vector<face_t *> overlapping;
            tree->search(aabb, std::back_inserter(overlapping));


            int n_intersections = countIntersectionPairs(qi.faces.begin(), qi.faces.end(),
                                                         overlapping.begin(), overlapping.end(),
                                                         vert, NULL, q_pt);

            if (n_intersections == 0) {
              vert->v = q_pt;
              quantized.push_back((*i).first);
              tree->updateExtents(aabb);
            }
          }
          for (size_t i = 0; i < quantized.size(); ++i) {
            vertex_qinfo.erase(quantized[i]);
          }

          if (!quantized.size()) break;
        }
      }


    };
  }
}
