diff --git a/Draw.cc b/Draw.cc new file mode 100644 index 0000000000000000000000000000000000000000..a8e21e6b59ae337dfa30836e18b2cb8cdc3ae906 --- /dev/null +++ b/Draw.cc @@ -0,0 +1,136 @@ +/* + * Author: pschmidt + */ +#include "Draw.hh" + +#include <ACG/Scenegraph/TransformNode.hh> +#include <ACG/Scenegraph/MaterialNode.hh> +#include <ACG/Scenegraph/PointNode.hh> +#include <ACG/Scenegraph/LineNode.hh> + +namespace Utils +{ + +Draw::Draw() + : translation_(0.0), + transform_node_(nullptr) +{ + +} + +Draw::~Draw() +{ + clear(); +} + +void Draw::set_translation(ACG::Vec3d _t) +{ + translation_ = _t; + apply_translation(); +} + +void Draw::reset_translation() +{ + translation_ = ACG::Vec3d(0.0); + apply_translation(); +} + +void Draw::apply_translation() +{ + transform_node()->loadIdentity(); + transform_node()->translate(translation_); +} + +void Draw::clear() +{ + point_nodes_.clear(); + line_nodes_.clear(); + + if (transform_node_) + transform_node_->delete_subtree(); + + transform_node_ = nullptr; +} + +void Draw::point(ACG::Vec2d _p, Color _color, float _width/* = 10.0f*/) +{ + point(ACG::Vec3d(_p[0], 0.0, -_p[1]), _color, _width); +} + +void Draw::point(ACG::Vec3d _p, Color _color, float _width/* = 10.0f*/) +{ + PointNode* node = point_node(_width); + + node->add_color(_color); + node->add_point(_p); + node->show(); +} + +void Draw::line(ACG::Vec2d _from, ACG::Vec2d _to, Color _color, float _width/* = 4.0f*/) +{ + return line(ACG::Vec3d(_from[0], 0.0, -_from[1]), ACG::Vec3d(_to[0], 0.0, -_to[1]), _color, _width); +} + +void Draw::line(ACG::Vec3d _from, ACG::Vec3d _to, Color _color, float _width/* = 4.0f*/) +{ + LineNode* node = line_node(_width); + + node->add_color(_color); + node->add_line(_from, _to); + node->show(); +} + +Draw::TransformNode* Draw::transform_node() +{ + if (!transform_node_) + { + transform_node_ = new TransformNode(); + PluginFunctions::addGlobalNode(transform_node_); + apply_translation(); + } + + return transform_node_; +} + +ACG::SceneGraph::PointNode* Draw::point_node(float _width) +{ + // Already there? + for (auto& node : point_nodes_) + { + auto* material_node = dynamic_cast<ACG::SceneGraph::MaterialNode*>(node->parent()); + if (material_node->point_size() == _width) + return node; + } + + // Add new node + auto* material_node = new ACG::SceneGraph::MaterialNode(transform_node()); + material_node->enable_blending(); + + auto* point_node = new ACG::SceneGraph::PointNode(material_node); + material_node->set_point_size(_width); + point_node->enablePicking(false); + point_node->drawMode(ACG::SceneGraph::DrawModes::POINTS_COLORED); + point_nodes_.push_back(point_node); + + return point_node; +} + +ACG::SceneGraph::LineNode* Draw::line_node(float _width) +{ + // Already there? + for (auto& node : line_nodes_) + { + if (node->line_width() == _width) + return node; + } + + // Add new node + LineNode* node = new ACG::SceneGraph::LineNode(ACG::SceneGraph::LineNode::LineSegmentsMode, transform_node()); + node->set_line_width(_width); + node->enablePicking(false); + line_nodes_.push_back(node); + + return node; +} + +} // namespace Utils diff --git a/Draw.hh b/Draw.hh new file mode 100644 index 0000000000000000000000000000000000000000..0ce38452fbf3ba6402203cdf4e2349a3aabf563b --- /dev/null +++ b/Draw.hh @@ -0,0 +1,57 @@ +/* + * Author: pschmidt + */ +#pragma once + +#include <memory> +#include <ObjectTypes/TriangleMesh/TriangleMesh.hh> +#include <ObjectTypes/TriangleMesh/TriangleMeshTypes.hh> + +#include "RWTHColors.hh" + +#include <memory> +#include <unordered_map> + +namespace ACG { namespace SceneGraph { class TransformNode; } } +namespace ACG { namespace SceneGraph { class PointNode; } } +namespace ACG { namespace SceneGraph { class LineNode; } } + +namespace Utils +{ + +class Draw +{ +public: + using TransformNode = ACG::SceneGraph::TransformNode; + using PointNode = ACG::SceneGraph::PointNode; + using LineNode = ACG::SceneGraph::LineNode; + + using Color = ACG::Vec4f; + +public: + Draw(); + ~Draw(); + + void set_translation(ACG::Vec3d _t); + void reset_translation(); + + void clear(); + void point(ACG::Vec2d _p, Color _color, float _width = 10.0f); + void point(ACG::Vec3d _p, Color _color, float _width = 10.0f); + void line(ACG::Vec2d _from, ACG::Vec2d _to, Color _color, float _width = 4.0f); + void line(ACG::Vec3d _from, ACG::Vec3d _to, Color _color, float _width = 4.0f); + +private: + void apply_translation(); + TransformNode* transform_node(); + PointNode* point_node(float _width); + LineNode* line_node(float _width); + +private: + ACG::Vec3d translation_; + TransformNode* transform_node_; + std::vector<PointNode*> point_nodes_; + std::vector<LineNode*> line_nodes_; +}; + +} // namespace Utils diff --git a/Embedding.cc b/Embedding.cc new file mode 100644 index 0000000000000000000000000000000000000000..19292e672ecf561ce8c01d897eb9b784c7301e8a --- /dev/null +++ b/Embedding.cc @@ -0,0 +1,4614 @@ +#include "Embedding.hh" +#include <math.h> +#include <QDebug> +#include <random> + +/*! + * \brief Embedding::Embedding + */ +Embedding::Embedding() { + qDebug() << "Start of MetaMesh ctor"; + base_mesh_ = nullptr; + meta_mesh_ = nullptr; + qDebug() << "Initialized MetaMesh"; +} + +/*! + * \brief Embedding::CopyInitialization initializes the meta mesh as a copy of the + * base mesh + * \param base_mesh + * \param meta_mesh + */ +void Embedding::CopyInitialization(TriMesh &base_mesh, PolyMesh &meta_mesh) { + qDebug() << "Copying the base mesh into the metamesh."; + boundaries_ = false; + initial_triangulation_ = true; + base_mesh_ = &base_mesh; + meta_mesh_ = &meta_mesh; + meta_mesh.clear(); + InitializeProperties(); + for (auto bvh : base_mesh_->vertices()) { + auto mvh = meta_mesh_->add_vertex(base_mesh_->point(bvh)); + base_mesh_->property(bv_connection_, bvh) = mvh; + meta_mesh_->property(mv_connection_, mvh) = bvh; + base_mesh_->property(bsplithandle_, bvh) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(voronoiID_, bvh) = + OpenMesh::PolyConnectivity::InvalidVertexHandle; + } + for (auto bfh : base_mesh_->faces()) { + auto bheh0 = base_mesh_->halfedge_handle(bfh); + auto bheh1 = base_mesh_->next_halfedge_handle(bheh0); + auto bheh2 = base_mesh_->next_halfedge_handle(bheh1); + auto bvh0 = base_mesh_->from_vertex_handle(bheh0); + auto bvh1 = base_mesh_->from_vertex_handle(bheh1); + auto bvh2 = base_mesh_->from_vertex_handle(bheh2); + auto mvh0 = base_mesh_->property(bv_connection_, bvh0); + auto mvh1 = base_mesh_->property(bv_connection_, bvh1); + auto mvh2 = base_mesh_->property(bv_connection_, bvh2); + auto mheh0 = base_mesh_->property(bhe_connection_, + base_mesh_->opposite_halfedge_handle(bheh0)); + auto mheh1 = base_mesh_->property(bhe_connection_, + base_mesh_->opposite_halfedge_handle(bheh1)); + auto mheh2 = base_mesh_->property(bhe_connection_, + base_mesh_->opposite_halfedge_handle(bheh2)); + if (meta_mesh_->is_valid_handle(mheh0)) { + mheh0 = meta_mesh_->opposite_halfedge_handle(mheh0); + } + if (meta_mesh_->is_valid_handle(mheh1)) { + mheh1 = meta_mesh_->opposite_halfedge_handle(mheh1); + } + if (meta_mesh_->is_valid_handle(mheh2)) { + mheh2 = meta_mesh_->opposite_halfedge_handle(mheh2); + } + auto mfh = AddFace({mvh0, mvh1, mvh2}, {mheh0, mheh1, mheh2}); + mheh0 = meta_mesh_->halfedge_handle(mfh); + mheh1 = meta_mesh_->next_halfedge_handle(mheh0); + mheh2 = meta_mesh_->next_halfedge_handle(mheh1); + base_mesh_->property(bhe_connection_, bheh0) = mheh0; + base_mesh_->property(bhe_connection_, bheh1) = mheh1; + base_mesh_->property(bhe_connection_, bheh2) = mheh2; + meta_mesh_->property(mhe_connection_, mheh0) = bheh0; + meta_mesh_->property(mhe_connection_, mheh1) = bheh1; + meta_mesh_->property(mhe_connection_, mheh2) = bheh2; + base_mesh_->property(next_heh_, bheh0) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, bheh1) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, bheh2) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(halfedge_weight_, bheh0) = 1.0; + base_mesh_->property(halfedge_weight_, bheh1) = 1.0; + base_mesh_->property(halfedge_weight_, bheh2) = 1.0; + } + for (auto bheh : base_mesh_->halfedges()) { + if (base_mesh_->is_boundary(bheh)) { + auto mheh = meta_mesh_->opposite_halfedge_handle( + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(bheh))); + base_mesh_->property(bhe_connection_, bheh) = mheh; + meta_mesh_->property(mhe_connection_, mheh) = bheh; + meta_mesh_->set_boundary(mheh); + meta_mesh_->set_halfedge_handle(meta_mesh_->from_vertex_handle(mheh), mheh); + } + } + for (auto mheh : meta_mesh_->halfedges()) { + if (meta_mesh_->is_boundary(mheh)) { + auto mhehn = meta_mesh_->halfedge_handle(meta_mesh_->to_vertex_handle(mheh)); + meta_mesh_->set_next_halfedge_handle(mheh, mhehn); + } + } + ColorizeMetaMesh(); + initial_triangulation_ = false; +} + +/*! + * \brief Embedding::SelectionTriangulation triangulate selected meta vertices + * \param base_mesh + * \param meta_mesh + * \param type + */ +void Embedding::SelectionTriangulation(TriMesh& base_mesh, PolyMesh& meta_mesh) { + qDebug() << "Entering InitialTriangulation function."; + boundaries_ = false; + initial_triangulation_ = true; + base_mesh_ = &base_mesh; + meta_mesh_ = &meta_mesh; + meta_mesh.clear(); + std::vector<OpenMesh::VertexHandle> meta_mesh_points; + for (auto vh : base_mesh_->vertices()) { + if (base_mesh_->status(vh).selected() == true) { + meta_mesh_points.push_back(vh); + } + } + if (meta_mesh_points.empty()) { + CopyInitialization(base_mesh, meta_mesh); + return; + } + TriangulationPipeline(meta_mesh_points); + initial_triangulation_ = false; +} + +/*! + * \brief Embedding::RandomTriangulation + * 1 / ratio chance of a vertex being randomly selected as a meta vertex + * \param base_mesh + * \param meta_mesh + * \param input + * \param rtype + * \param type + */ +void Embedding::RandomTriangulation(TriMesh &base_mesh, PolyMesh &meta_mesh, double input, + RandomType rtype) { + initial_triangulation_ = true; + boundaries_ = false; + base_mesh_ = &base_mesh; + meta_mesh_ = &meta_mesh; + meta_mesh.clear(); + std::random_device rd; //Will be used to obtain a seed for the random number engine + std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() + std::uniform_int_distribution<> dis0(0, static_cast<int>(1.0/input)); + std::uniform_int_distribution<> dis1(0, static_cast<int>(base_mesh_->n_vertices())-1); + std::vector<OpenMesh::VertexHandle> meta_mesh_points; + + // Find boundary vertices + OpenMesh::VPropHandleT<bool> traversed_boundary; + base_mesh_->add_property(traversed_boundary, "Mark boundaries that have been traversed"); + for (auto bvh : base_mesh_->vertices()) { + base_mesh_->property(traversed_boundary, bvh) = false; + } + for (auto bvh : base_mesh_->vertices()) { + // Go into each boundary once + if (base_mesh_->is_boundary(bvh) + && base_mesh_->property(traversed_boundary, bvh) == false) { + uint step = 0; + // Find the right stepsize to evenly distribute boundary vertices + if (rtype == RATIO) { + step = static_cast<uint>(1/input); + } else if (rtype == TOTAL) { + step = static_cast<uint>(base_mesh_->n_vertices()/input); + } + base_mesh_->property(traversed_boundary, bvh) = true; + meta_mesh_points.push_back(bvh); + uint currstep = 1; + auto bheh = base_mesh_->halfedge_handle(bvh); + while (!base_mesh_->property(traversed_boundary, + base_mesh_->to_vertex_handle(bheh))) { + base_mesh_->property(traversed_boundary, + base_mesh_->to_vertex_handle(bheh)) = true; + if (currstep == step) { + meta_mesh_points.push_back(base_mesh_->to_vertex_handle(bheh)); + currstep = 0; + } + ++currstep; + bheh = base_mesh_->next_halfedge_handle(bheh); + } + } + } + base_mesh_->remove_property(traversed_boundary); + + if (rtype == RATIO) { + for (auto bvh : base_mesh_->vertices()) { + if (dis0(gen) == 1 && !base_mesh_->is_boundary(bvh)) { + meta_mesh_points.push_back(bvh); + } + } + } else if (rtype == TOTAL) { + while (meta_mesh_points.size()<input*10000) { + meta_mesh_points.push_back(base_mesh_->vertex_handle(static_cast<uint>(dis1(gen)))); + } + } + while (!TriangulationPipeline(meta_mesh_points)) { + meta_mesh_points.clear(); + qDebug() << "Automatically remeshing"; + meta_mesh_->clear(); + if (rtype == RATIO) { + for (auto vh : base_mesh_->vertices()) { + if (dis0(gen) == 1) + meta_mesh_points.push_back(vh); + } + } else if (rtype == TOTAL) { + for (unsigned long i=0; i<input*10000; ++i) { + meta_mesh_points.push_back(base_mesh_->vertex_handle(static_cast<uint>(dis1(gen)))); + } + } + } + initial_triangulation_ = false; +} + +/*! + * \brief Embedding::TriangulationPipeline + * \param meta_mesh_points + * \param type + * \return + */ +bool Embedding::TriangulationPipeline( + std::vector<OpenMesh::VertexHandle> meta_mesh_points) { + debug_hard_stop_ = false; + InitializeProperties(); + CreateMetaMeshVertices(meta_mesh_points); + if (!TriangulateMetaMesh()) + return false; + CleanUpBaseMesh(); + TestHalfedgeConsistency(); + ColorizeMetaMesh(); + return true; +} + +/*! + * \brief Embedding::InitializeProperties + */ +void Embedding::InitializeProperties() { + base_mesh_->add_property(voronoiID_, "Voronoi area"); + base_mesh_->add_property(bsplithandle_, "Vertex to collapse into, undefined if not introduced" + "by a splitting operation"); + base_mesh_->add_property(bv_connection_, "BVV_Connection"); + meta_mesh_->add_property(mv_connection_, "MVV_Connection"); + base_mesh_->add_property(bhe_connection_, "Pointer from base he to meta heh"); + meta_mesh_->add_property(mhe_connection_, "Pointer from meta he to first base heh"); + base_mesh_->add_property(bhe_border_, "Pointer from border base he to meta heh"); + meta_mesh_->add_property(mhe_border_, "Pointer from meta he to first border base heh"); + base_mesh_->add_property(next_heh_, "Pointer from base he to next heh on meta edge"); + base_mesh_->add_property(halfedge_weight_, "Pointer from base he to meta heh"); +} + +/*! + * \brief Embedding::CreateMetaMeshVertices + * \param meta_mesh_points + */ +void Embedding::CreateMetaMeshVertices(std::vector<OpenMesh::VertexHandle> meta_mesh_points) { + qDebug() << "Start appointing BaseMesh properties:"; + qDebug() << "is bv_connection_ valid? " << bv_connection_.is_valid(); + qDebug() << "is bsplithandle_ valid? " << bsplithandle_.is_valid(); + for (auto bvh : base_mesh_->vertices()) { + base_mesh_->property(bv_connection_, bvh) = OpenMesh::PolyConnectivity::InvalidVertexHandle; + base_mesh_->property(bsplithandle_, bvh) = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + for (auto bheh : base_mesh_->halfedges()) { + base_mesh_->property(next_heh_, bheh) = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, bheh) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(halfedge_weight_, bheh) = 1.0; + if (base_mesh_->is_boundary(bheh)) { + boundaries_ = true; + } + } + + qDebug() << "Start appointing MetaMesh properties:"; + for (auto mpoint : meta_mesh_points) { + auto mvhnew = meta_mesh_->add_vertex(base_mesh_->point(mpoint)); + meta_mesh_->property(mv_connection_, mvhnew) = mpoint; + base_mesh_->property(bv_connection_, mpoint) = mvhnew; + } + qDebug() << "Finish appointing MetaMesh properties:"; +} + +/*! + * \brief Embedding::TriangulateMetaMesh + * \param type + * \return + */ +bool Embedding::TriangulateMetaMesh() { + OpenMesh::VPropHandleT<double> voronoidistance; + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> to_heh; + OpenMesh::HPropHandleT<int> multiplicity_heh; + base_mesh_->add_property(voronoidistance, "Voronoi distance from center"); + base_mesh_->add_property(to_heh, "Incoming edge from the shortest path"); + base_mesh_->add_property(multiplicity_heh, "Mark duplicate paths for elimination"); + qDebug() << "Entering Delaunay:"; + if (!Delaunay(voronoidistance, to_heh, multiplicity_heh)) { + return false; + } + + A_StarTriangulation(); + + base_mesh_->remove_property(voronoidistance); + base_mesh_->remove_property(to_heh); + base_mesh_->remove_property(multiplicity_heh); + return true; +} + +/*! + * \brief Embedding::PreProcessEdges global preprocessing + */ +void Embedding::PreProcessEdges() { + for (auto beh : base_mesh_->edges()) { + auto bheh = base_mesh_->halfedge_handle(beh, 0); + ConditionalSplit(bheh); + } + base_mesh_->update_normals(); +} + +/*! + * \brief Embedding::ProcessEdge local preprocessing + * \param meh + */ +void Embedding::ProcessEdge(OpenMesh::EdgeHandle meh) { + ProcessHalfedge(meta_mesh_->halfedge_handle(meh, 0)); + ProcessHalfedge(meta_mesh_->halfedge_handle(meh, 1)); +} + +/*! + * \brief Embedding::ProcessVertex local preprocessing + * \param bvh + */ +void Embedding::ProcessVertex(OpenMesh::VertexHandle bvh) { + std::list<OpenMesh::HalfedgeHandle> splits; + for (auto bvoheh : base_mesh_->voh_range(bvh)) { + if (IsSectorBorder(base_mesh_->to_vertex_handle(bvoheh))) { + splits.push_back(bvoheh); + } + } + while (!splits.empty()) { + ConditionalSplit(splits.front()); + splits.pop_front(); + } +} + +/*! + * \brief Embedding::CleanupVertex perform legal base collapses around vertex bvh + * \param bvh + */ +void Embedding::CleanupVertex(OpenMesh::VertexHandle bvh) { + std::list<OpenMesh::VertexHandle> collapses; + for (auto bvhit : base_mesh_->vv_range(bvh)) { + if (base_mesh_->is_valid_handle(bvhit) + && !base_mesh_->status(bvhit).deleted() + && base_mesh_->is_valid_handle(base_mesh_->property(bsplithandle_, bvhit)) + && !base_mesh_->status(base_mesh_->property(bsplithandle_, bvhit)).deleted()) { + collapses.push_back(bvhit); + } + } + while (!collapses.empty()) { + ConditionalCollapse(collapses.front()); + collapses.pop_front(); + } +} + +/*! + * \brief Embedding::CleanupFace cleans up the faces around mheh + * \param mheh + */ +void Embedding::CleanupFace(OpenMesh::HalfedgeHandle mheh) { + auto mhehiter = mheh; + do { + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehiter))) { + CleanupHalfedge(mhehiter); + } + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } while (mhehiter != mheh); +} + +/*! + * \brief Embedding::CleanupHalfedge cleans up mheh + * \param mheh + */ +void Embedding::CleanupHalfedge(OpenMesh::HalfedgeHandle mheh) { + auto linehalfedges = GetBaseHalfedges(mheh); + auto mhehnext = meta_mesh_->next_halfedge_handle(mheh); + while (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehnext))) { + mhehnext = meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mhehnext)); + } + linehalfedges.push_back(meta_mesh_->property(mhe_connection_, mhehnext)); + for (auto blheh : linehalfedges) { + auto bvhfrom = base_mesh_->from_vertex_handle(blheh); + if (base_mesh_->is_valid_handle(bvhfrom) + && base_mesh_->is_valid_handle(base_mesh_->property(bsplithandle_, bvhfrom)) + && !base_mesh_->status(bvhfrom).deleted() + && !base_mesh_->status(base_mesh_->property(bsplithandle_, bvhfrom)).deleted()) { + ConditionalCollapse(bvhfrom); + } + for (auto bheh : LeftHalfCircle(blheh)) { + auto bvhto = base_mesh_->to_vertex_handle(bheh); + if (base_mesh_->is_valid_handle(bvhto) + && base_mesh_->is_valid_handle(base_mesh_->property(bsplithandle_, bvhto)) + && !base_mesh_->status(bvhto).deleted() + && !base_mesh_->status(base_mesh_->property(bsplithandle_, bvhto)).deleted()) { + ConditionalCollapse(bvhto); + } + } + } + BaseGarbageCollection(); +} + +/*! + * \brief Embedding::ProcessNeighbors pre process the neighboring edges around mheh + * this is called before tracing + * \param meh + */ +void Embedding::ProcessNeighbors(OpenMesh::EdgeHandle meh) { + auto mheh0 = meta_mesh_->halfedge_handle(meh, 0); + auto mheh1 = meta_mesh_->halfedge_handle(meh, 1); + + // Find the ACTUAL neighbors that are already traced, not the neighbors in the meta mesh + auto neighbor0 = meta_mesh_->opposite_halfedge_handle( + meta_mesh_->prev_halfedge_handle(mheh0)); + while (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, neighbor0)) + && neighbor0 != mheh0) { + neighbor0 = meta_mesh_->opposite_halfedge_handle( + meta_mesh_->prev_halfedge_handle(neighbor0)); + } + auto neighbor1 = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh0)); + while (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, neighbor1)) + && neighbor1 != mheh0) { + neighbor1 = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(neighbor1)); + } + auto neighbor2 = meta_mesh_->opposite_halfedge_handle( + meta_mesh_->prev_halfedge_handle(mheh1)); + while (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, neighbor2)) + && neighbor2 != mheh1) { + neighbor2 = meta_mesh_->opposite_halfedge_handle( + meta_mesh_->prev_halfedge_handle(neighbor2)); + } + auto neighbor3 = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh1)); + while (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, neighbor3)) + && neighbor3 != mheh1) { + neighbor3 = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(neighbor3)); + } + + // neighbor0 and neighbor 2 were found searching towards the left but + // ProcessHalfedge is left-facing, so process the opposite halfedges. + if (neighbor0 != mheh0) { + ProcessHalfedge(meta_mesh_->opposite_halfedge_handle(neighbor0)); + ProcessHalfedge(neighbor1); + } + if (neighbor2 != mheh1) { + ProcessHalfedge(meta_mesh_->opposite_halfedge_handle(neighbor2)); + ProcessHalfedge(neighbor3); + } +} + +/*! + * \brief Embedding::ProcessFace + * Iterate over the currently traced patch that mheh is part of and call pre-processing + * on all traversed meta halfedges + * \param mheh + */ +void Embedding::ProcessFace(OpenMesh::HalfedgeHandle mheh) { + // Edge case: Trying to process the face of an mheh which has an adjacent valence 1 vertex + // If mheh points towards that vertex there is no problem in the iteration + // If mheh points away from that vertex it immediately iterates into itself and terminates + // -> no processing + // Fix this by changing mheh to its opposite halfedge if it points away from a valence 1 vertex + // This cannot go wrong since only one of the two vertices of mheh can be valence 1 in any + // scenario where pre-processing is required (both valence 1 can only happen in + // initial triangulation) + // The previous solution works for edges next to valence 1 being traced but breaks face splits + // so instead just find an adjacent traced edge and iterate from there; this works in both + // cases. + auto mhehf = meta_mesh_->next_halfedge_handle(mheh); + auto mhehb = meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mheh)); + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehf))) { + mheh = mhehf; + } else if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehb))) { + mheh = mhehb; + } else { + // In this case BOTH vertices have to be empty, so the state has to be initial triangulation + assert(initial_triangulation_); + return; + } + auto mhehtemp = mheh; + do { + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehtemp)) + && base_mesh_->property(bhe_connection_, meta_mesh_->property(mhe_connection_, mhehtemp)) + == mhehtemp) { + ProcessHalfedge(mhehtemp); + mhehtemp = meta_mesh_->next_halfedge_handle(mhehtemp); + } else { + mhehtemp = meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mhehtemp)); + } + } while (mhehtemp != mheh); +} + +/*! + * \brief Embedding::ProcessHalfedge pre-process base halfedges around meta halfedge mheh + * \param mheh + */ +void Embedding::ProcessHalfedge(OpenMesh::HalfedgeHandle mheh) { + auto linehalfedges = GetBaseHalfedges(mheh); + auto mhehnext = meta_mesh_->next_halfedge_handle(mheh); + while (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehnext))) { + mhehnext = meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mhehnext)); + } + linehalfedges.push_back(meta_mesh_->property(mhe_connection_, mhehnext)); + for (auto blheh : linehalfedges) { + for (auto bheh : LeftHalfCircle(blheh)) { + ConditionalSplit(bheh); + } + } +} + +/*! + * \brief Embedding::ConditionalSplit split bheh if permissible + * \param bheh + * \return + */ +bool Embedding::ConditionalSplit(OpenMesh::HalfedgeHandle bheh) { + // Don't split boundary halfedges for now since it is leading to crashes. + // This could be enabled (I was in the middle of adding boundary split functionality) + // But there is not enough time + // TODO: remove this check and fix boundary splits in CollapseBaseHe and SplitBaseHe + // After testing quite a bit without splitting boundary halfedges this seems to be working quite + // well. Splitting boundary halfedges may not be necessary at all. + if (base_mesh_->is_boundary(bheh) + || base_mesh_->is_boundary(base_mesh_->opposite_halfedge_handle(bheh))) { + return false; + } + + if (!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))) { + bool split1 = false; + bool split2 = false; + bool split3 = false; + for (auto bheh : base_mesh_->voh_range(base_mesh_->from_vertex_handle(bheh))) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))) { + split1 = true; + auto mvh0 = base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bheh)); + if (meta_mesh_->is_valid_handle(mvh0) + && meta_mesh_->to_vertex_handle(base_mesh_->property( + bhe_connection_, bheh)) != mvh0 + && meta_mesh_->from_vertex_handle(base_mesh_->property( + bhe_connection_, bheh)) != mvh0) { + split2 = true; + } + } + } + for (auto bheh : base_mesh_->voh_range(base_mesh_->to_vertex_handle(bheh))) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))) { + split2 = true; + auto mvh0 = base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(bheh)); + if (meta_mesh_->is_valid_handle(mvh0) + && meta_mesh_->to_vertex_handle(base_mesh_->property( + bhe_connection_, bheh)) != mvh0 + && meta_mesh_->from_vertex_handle(base_mesh_->property( + bhe_connection_, bheh)) != mvh0) { + split1 = true; + } + } + } + auto bvid0 = base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bheh)); + auto bvid1 = base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bheh)); + if (bvid0 != bvid1 && initial_triangulation_) { + auto meh = GetMetaEdge(base_mesh_->from_vertex_handle(bheh)); + if (!meta_mesh_->is_valid_handle(meh)) { + split3 = true; + } else { + auto mvid0 = meta_mesh_->from_vertex_handle(meta_mesh_->halfedge_handle(meh, 0)); + auto mvid1 = meta_mesh_->to_vertex_handle(meta_mesh_->halfedge_handle(meh, 0)); + if (!((mvid0 == bvid0 && mvid1 == bvid1) || (mvid0 == bvid1 && mvid1 == bvid0))) { + split3 = true; + } + } + } + + if ((split1 && split2) || (split3 && (split1 || split2))) { + SplitBaseHe(bheh); + return true; + } + } else if ((meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(bheh)))) + && (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bheh))))) { + SplitBaseHe(bheh); + return true; + } + return false; +} + +/*! + * \brief Embedding::LeftHalfCircle + * \param bheh + * \return the halfedges surrounding bheh in a left halfcircle and + * stopping when reaching a meta halfedge (used for preprocessing) + */ +std::vector<OpenMesh::HalfedgeHandle> Embedding::LeftHalfCircle(OpenMesh::HalfedgeHandle bheh) { + std::vector<OpenMesh::HalfedgeHandle> retval; + retval.push_back(bheh); + auto curr = base_mesh_->opposite_halfedge_handle(base_mesh_->prev_halfedge_handle(bheh)); + retval.push_back(curr); + while (curr != bheh && !meta_mesh_->is_valid_handle( + base_mesh_->property(bhe_connection_, curr))) { + curr = base_mesh_->opposite_halfedge_handle(base_mesh_->prev_halfedge_handle(curr)); + retval.push_back(curr); + } + return retval; +} + +/*! + * \brief Embedding::GetBaseHalfedges + * \param mheh + * \return the base halfedges of meta halfedge mheh + */ +std::vector<OpenMesh::HalfedgeHandle> Embedding::GetBaseHalfedges( + OpenMesh::HalfedgeHandle mheh) { + std::vector<OpenMesh::HalfedgeHandle> retval; + auto bheh = meta_mesh_->property(mhe_connection_, mheh); + assert(base_mesh_->is_valid_handle(bheh)); + retval.push_back(bheh); + auto curr = base_mesh_->property(next_heh_, bheh); + while (base_mesh_->is_valid_handle(curr)) { + retval.push_back(curr); + curr = base_mesh_->property(next_heh_, curr); + } + return retval; +} + +/*! + * \brief Embedding::CleanUpBaseMesh collapses new bhehs where allowed and collects garbage it + * \param garbagecollection collect garbage if set to true + */ +void Embedding::CleanUpBaseMesh(bool garbagecollection) { + std::queue<OpenMesh::VertexHandle> hehqueue; + for (auto bvh : base_mesh_->vertices()) { + if (base_mesh_->is_valid_handle(bvh) + && !base_mesh_->status(bvh).deleted() + && base_mesh_->is_valid_handle(base_mesh_->property(bsplithandle_, bvh)) + && !base_mesh_->status(base_mesh_->property(bsplithandle_, bvh)).deleted()) { + auto bvhnew = ConditionalCollapse(bvh); + if (base_mesh_->is_valid_handle(bvhnew)) { + hehqueue.push(bvhnew); + } + } + if (debug_hard_stop_) { + break; + } + } + // More passes to clear up topologically blocked halfedges whose merges depend on order + // this is tricky because there may be a few that _can't_ be merged no matter what + unsigned long ctr = hehqueue.size()*2; + while (!hehqueue.empty() && ctr > 0) { + auto bvh = hehqueue.front(); + hehqueue.pop(); + auto bvhnew = ConditionalCollapse(bvh); + if (base_mesh_->is_valid_handle(bvhnew)) { + hehqueue.push(bvhnew); + } + --ctr; + } + + if (garbagecollection) { + BaseGarbageCollection(); + } +} + +/*! + * \brief Embedding::ConditionalCollapse collapse bvh into its bsplithandle_ halfedge + * if permissible + * \param bvh + * \return + */ +OpenMesh::VertexHandle Embedding::ConditionalCollapse(OpenMesh::VertexHandle bvh) { + auto bheh = base_mesh_->property(bsplithandle_, bvh); + auto bheho = base_mesh_->opposite_halfedge_handle(bheh); + if (base_mesh_->status(bheh).deleted() || base_mesh_->status(bvh).deleted()) { + return OpenMesh::PolyConnectivity::InvalidVertexHandle; + } + auto bvh0 = base_mesh_->from_vertex_handle(bheh); + auto bvh1 = base_mesh_->to_vertex_handle(bheh); + + // Sanity check + assert(base_mesh_->from_vertex_handle(bheh) == bvh); + + bool merge = true; + + auto meh0 = GetMetaEdge(bvh0); + auto meh1 = GetMetaEdge(bvh1); + // Collapsing into a deleted edge + if (base_mesh_->status(bheh).deleted()) { + merge = false; + qDebug() << "Vertex " << bvh0.idx() << " points towards deleted halfedge " + << bheh.idx() << " so it cannot be merged. Presumably something is wrong."; + } + + // Collapsing from one meta halfedge into another + if (meta_mesh_->is_valid_handle(meh0) && meta_mesh_->is_valid_handle(meh1) + && !(meh0 == meh1)) { + merge = false; + } + + // Collapsing from a meta halfedge into itself but not along the edge + if (meta_mesh_->is_valid_handle(meh0) && meh0 == meh1 && meta_mesh_->edge_handle( + base_mesh_->property(bhe_connection_, bheh)) != meh0) { + merge = false; + } + + // Collapsing from a meta vertex into a meta halfedge that doesn't end + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh0))) { + for (auto bih : base_mesh_->vih_range(bvh1)) { + if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bih)) + && !(base_mesh_->property(next_heh_, bih) == bheho + || bih == bheh)) { + merge = false; + } + } + } + + // Collapsing a meta vertex + if (base_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh0))) { + merge = false; + } + + // Collapsing into a meta vertex but not along a meta edge when the collapsed vertex lies on a meta edge + auto mhehx = meta_mesh_->edge_handle(base_mesh_->property(bhe_connection_, bheh)); + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh1)) + && meta_mesh_->is_valid_handle(meh0) + && mhehx != meh0) { + merge = false; + } + + // Collapsing of inhabited edges onto boundary edges to avoid blocking + if (meta_mesh_->is_valid_handle(meh0) && base_mesh_->is_boundary(bvh1)) { + merge = false; + } + + // Collapsing of a 2-loop can lead to a scenario where two neighboring base vertices are meta vertices + // and also doubly connected. eg: A---B + // \ / + // C + // with A and B being meta vertices and C not a meta vertex. The rules so far would allow collapsing + // the CB halfedge, but this destroys the loop and should not be allowed before first removing the loop + // in the meta mesh. Avoid this. + // Conditions: 1. Collapsing into a meta vertex + // 2: Collapsing from a meta edge + // 3: The meta edge is also in one of the triangles of the collapsed edge eg. AC + // 4: The other edge of that triangle belongs to a meta edge as well eg. AB + // This specifically does not have to be a DIFFERENT meta edge but just ANY meta edge since otherwise a + // self-edge of B going BA->AC->CB would be illegally collapsed. + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh1)) // C1 + && meta_mesh_->is_valid_handle(meh0) // C2 + && ((meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, + base_mesh_->prev_halfedge_handle(bheh))) // C3+4 left + && meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, + base_mesh_->next_halfedge_handle(bheh)))) + || (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, // C3+4 right + base_mesh_->next_halfedge_handle(bheho))) + && meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, + base_mesh_->prev_halfedge_handle(bheho)))))) { + merge = false; + } + + + // Other illegal collapses provided by OpenMesh; retry later in this case since these may be blocked by + // collapsing order and collapseable later. + if (!base_mesh_->is_collapse_ok(bheh)) { + return bvh; + } + + // This one should not be necessary, delete later and fix. + /* + if (base_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))) { + merge = false; + } + */ + if (merge) { + CollapseBaseHe(bheh); + } + return OpenMesh::PolyConnectivity::InvalidVertexHandle; +} + +/*! + * \brief Embedding::SplitBaseHe + * Retain properties correctly while splitting halfedges and return the first half of the split + * edge, also marks new vertices introduced by the split with a handle to the vertex to collapse + * them into to undo the split. + * \param bheh + * \return the halfedge starting at the from_vertex of bheh and pointing towards the new vertex + * resulting from the split. + */ +OpenMesh::HalfedgeHandle Embedding::SplitBaseHe(OpenMesh::HalfedgeHandle bheh) { + auto opp = base_mesh_->opposite_halfedge_handle(bheh); + auto vertex_colors_pph = base_mesh_->vertex_colors_pph(); + auto vertex_color = base_mesh_->property(vertex_colors_pph, + base_mesh_->from_vertex_handle(bheh)); + + auto weight = base_mesh_->property(halfedge_weight_, bheh); + bool lbound = base_mesh_->is_boundary(bheh); + bool rbound = base_mesh_->is_boundary(base_mesh_->opposite_halfedge_handle(bheh)); + + OpenMesh::HalfedgeHandle border0, border1, mborder0, mborder1; + if (initial_triangulation_) { + border0 = base_mesh_->property(bhe_border_, bheh); + border1 = base_mesh_->property(bhe_border_, opp); + mborder0 = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + if (meta_mesh_->is_valid_handle(border0)) { + if (meta_mesh_->property(mhe_border_, border0) == bheh) { + mborder0 = bheh; + } + } + mborder1 = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + if (meta_mesh_->is_valid_handle(border1)) { + if (meta_mesh_->property(mhe_border_, border1) == opp) { + mborder1 = opp; + } + } + } + + OpenMesh::HalfedgeHandle bhe_connection0 = base_mesh_->property(bhe_connection_, bheh); + OpenMesh::HalfedgeHandle bhe_connection1 = base_mesh_->property(bhe_connection_, opp); + OpenMesh::HalfedgeHandle next_heh0 = base_mesh_->property(next_heh_, bheh); + OpenMesh::HalfedgeHandle next_heh1 = base_mesh_->property(next_heh_, opp); + + OpenMesh::HalfedgeHandle mhe_connection0 = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + if (meta_mesh_->is_valid_handle(bhe_connection0)){ + if (meta_mesh_->property(mhe_connection_, bhe_connection0) == bheh) { + mhe_connection0 = bheh; + } + } + OpenMesh::HalfedgeHandle mhe_connection1 = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + if (meta_mesh_->is_valid_handle(bhe_connection1)){ + if (meta_mesh_->property(mhe_connection_, bhe_connection1) == opp) { + mhe_connection1 = opp; + } + } + auto prev = base_mesh_->prev_halfedge_handle(bheh); + + bool backsplitadjust = (base_mesh_->property(bsplithandle_, + base_mesh_->from_vertex_handle(bheh)) + == bheh); + bool frontsplitadjust = (base_mesh_->property(bsplithandle_, + base_mesh_->to_vertex_handle(bheh)) + == base_mesh_->opposite_halfedge_handle(bheh)); + + + TriMesh::Point splitpoint = (base_mesh_->point(base_mesh_->from_vertex_handle(bheh)) + + base_mesh_->point(base_mesh_->to_vertex_handle(bheh)) )/2.0; + auto bvh = base_mesh_->split(base_mesh_->edge_handle(bheh), splitpoint); + + auto back = base_mesh_->next_halfedge_handle(prev); + auto left = base_mesh_->next_halfedge_handle(back); + auto front = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle(left)); + auto right = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle(front)); + assert (bvh == base_mesh_->to_vertex_handle(back)); + + if (lbound) { + front = base_mesh_->next_halfedge_handle(back); + right = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle(front)); + base_mesh_->set_boundary(back); + base_mesh_->set_boundary(front); + base_mesh_->set_halfedge_handle(bvh, front); + } + if (rbound) { + base_mesh_->set_boundary(base_mesh_->opposite_halfedge_handle(back)); + base_mesh_->set_boundary(base_mesh_->opposite_halfedge_handle(front)); + base_mesh_->set_halfedge_handle(bvh, base_mesh_->opposite_halfedge_handle(back)); + } + + + base_mesh_->property(bhe_connection_, back) = bhe_connection0; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(back)) + = bhe_connection1; + base_mesh_->property(bhe_connection_, front) = bhe_connection0; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(front)) + = bhe_connection1; + if (!lbound) { + base_mesh_->property(bhe_connection_, left) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(left)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + if (!rbound) { + base_mesh_->property(bhe_connection_, right) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(right)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + if (!lbound) { + base_mesh_->property(next_heh_, left) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(left)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + if (!rbound) { + base_mesh_->property(next_heh_, right) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(right)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + base_mesh_->property(halfedge_weight_, back) + = weight; + base_mesh_->property(halfedge_weight_, base_mesh_->opposite_halfedge_handle(back)) + = weight; + base_mesh_->property(halfedge_weight_, front) + = weight; + base_mesh_->property(halfedge_weight_, base_mesh_->opposite_halfedge_handle(front)) + = weight; + if (!lbound) { + base_mesh_->property(halfedge_weight_, left) + = 2.0*weight; + base_mesh_->property(halfedge_weight_, base_mesh_->opposite_halfedge_handle(left)) + = 2.0*weight; + } + if (!rbound) { + base_mesh_->property(halfedge_weight_, right) + = 2.0*weight; + base_mesh_->property(halfedge_weight_, base_mesh_->opposite_halfedge_handle(right)) + = 2.0*weight; + } + base_mesh_->property(bv_connection_, bvh) + = OpenMesh::PolyConnectivity::InvalidVertexHandle; + assert (!meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))); + base_mesh_->property(vertex_colors_pph, bvh) = vertex_color; + + if (backsplitadjust) { + base_mesh_->property(bsplithandle_, base_mesh_->from_vertex_handle(back)) = back; + base_mesh_->property(bsplithandle_, bvh) = front; + } else if (frontsplitadjust) { + base_mesh_->property(bsplithandle_, base_mesh_->to_vertex_handle(front)) + = base_mesh_->opposite_halfedge_handle(front); + base_mesh_->property(bsplithandle_, bvh) = base_mesh_->opposite_halfedge_handle(back); + } else { + base_mesh_->property(bsplithandle_, bvh) = base_mesh_->opposite_halfedge_handle(back); + } + + if (base_mesh_->is_valid_handle(mhe_connection0)) { + meta_mesh_->property(mhe_connection_, bhe_connection0) = back; + } + if (base_mesh_->is_valid_handle(mhe_connection1)) { + meta_mesh_->property(mhe_connection_, bhe_connection1) + = base_mesh_->opposite_halfedge_handle(front); + } + if (base_mesh_->is_valid_handle(next_heh0) || + base_mesh_->is_valid_handle(next_heh1)) { + base_mesh_->property(next_heh_, front) = next_heh0; + base_mesh_->property(next_heh_, back) = front; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(back)) = next_heh1; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(front)) + = base_mesh_->opposite_halfedge_handle(back); + if (base_mesh_->is_valid_handle(next_heh0)) { + auto next = base_mesh_->opposite_halfedge_handle(next_heh0); + base_mesh_->property(next_heh_, next) = base_mesh_->opposite_halfedge_handle(front); + } + if (base_mesh_->is_valid_handle(next_heh1)) { + auto prev = base_mesh_->opposite_halfedge_handle(next_heh1); + base_mesh_->property(next_heh_, prev) = back; + } + } else if (meta_mesh_->is_valid_handle(bhe_connection0)) { + base_mesh_->property(next_heh_, back) = front; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(front)) + = base_mesh_->opposite_halfedge_handle(back); + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(back)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, front) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } else { + base_mesh_->property(next_heh_, back) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(back)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, front) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(front)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + + if (!lbound) { + auto v_left = base_mesh_->opposite_halfedge_handle( + base_mesh_->next_halfedge_handle(left)); + base_mesh_->property(bhe_border_, left) = base_mesh_->property(bhe_border_, v_left); + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(left)) = + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(v_left)); + } + + if (!rbound) { + auto v_right = base_mesh_->prev_halfedge_handle( + base_mesh_->opposite_halfedge_handle(right)); + base_mesh_->property(bhe_border_, right) = base_mesh_->property(bhe_border_, v_right); + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(right)) = + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(v_right)); + } + + base_mesh_->property(bhe_border_, back) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(back)) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + + if (!meta_mesh_->is_valid_handle(bhe_connection0)) { + assert(!meta_mesh_->is_valid_handle(bhe_connection1)); + assert(!IsSectorBorder(bvh)); + assert(ValidA_StarEdge(back, + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle)); + assert(ValidA_StarEdge(base_mesh_->opposite_halfedge_handle(back), + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle)); + } + + if (initial_triangulation_) { + base_mesh_->property(bhe_border_, front) = border0; + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(front)) = border1; + if (base_mesh_->is_valid_handle(mborder0)) { + meta_mesh_->property(mhe_border_, border0) = front; + } + if (base_mesh_->is_valid_handle(mborder1)) { + meta_mesh_->property(mhe_border_, border1) = base_mesh_->opposite_halfedge_handle(front); + } + + base_mesh_->property(voronoiID_, bvh) = base_mesh_->property(voronoiID_, + base_mesh_->from_vertex_handle(back)); + if (!meta_mesh_->is_valid_handle(bhe_connection0)) { + assert(ValidA_StarEdge(front, border0)); + assert(ValidA_StarEdge(base_mesh_->opposite_halfedge_handle(front), border1)); + } + } + return back; +} + +void Embedding::CollapseBaseHe(OpenMesh::HalfedgeHandle bheh) { + // Ensure bheh is valid + assert(base_mesh_->is_valid_handle(bheh)); + + auto bvh0 = base_mesh_->from_vertex_handle(bheh); + auto bheho = base_mesh_->opposite_halfedge_handle(bheh); + + bool lbound = base_mesh_->is_boundary(bheh); + bool rbound = base_mesh_->is_boundary(bheho); + + // Ensure only vertices not from the original mesh are collapsed. + assert (base_mesh_->property(bsplithandle_, bvh0) == bheh); + + // Adjust property pointers towards bheh if it lies on a meta edge + AdjustPointersForBheCollapse(bheh); + + // Transfer properties and pointers over from redundant edges + assert(base_mesh_->is_valid_handle(base_mesh_->prev_halfedge_handle(bheh))); + assert(base_mesh_->is_valid_handle(base_mesh_->opposite_halfedge_handle( + base_mesh_->next_halfedge_handle(bheh)))); + if (!lbound) { + MergeProperties(base_mesh_->prev_halfedge_handle(bheh), + base_mesh_->opposite_halfedge_handle(base_mesh_->next_halfedge_handle(bheh))); + } + if (debug_hard_stop_) return; + assert(base_mesh_->is_valid_handle(base_mesh_->next_halfedge_handle(bheho))); + assert(base_mesh_->is_valid_handle(base_mesh_->opposite_halfedge_handle( + base_mesh_->prev_halfedge_handle(bheho)))); + if (!rbound){ + MergeProperties(base_mesh_->next_halfedge_handle(bheho), + base_mesh_->opposite_halfedge_handle(base_mesh_->prev_halfedge_handle(bheho))); + } + if (debug_hard_stop_) return; + + base_mesh_->collapse(bheh); +} + +/*! + * \brief Embedding::LowLevelBaseCollapse reimplemented collapse method on the base mesh, not sure + * if this is used at all? TODO: check if this needs deletion. + * \param bheh + */ +void Embedding::LowLevelBaseCollapse(OpenMesh::HalfedgeHandle bheh) { + // See PolyConnectivity::collapse and PolyConnectivity::collapse_edge + OpenMesh::HalfedgeHandle h = bheh; + OpenMesh::HalfedgeHandle hn = base_mesh_->next_halfedge_handle(h); + OpenMesh::HalfedgeHandle hp = base_mesh_->prev_halfedge_handle(h); + + OpenMesh::HalfedgeHandle o = base_mesh_->opposite_halfedge_handle(h); + OpenMesh::HalfedgeHandle on = base_mesh_->next_halfedge_handle(o); + OpenMesh::HalfedgeHandle op = base_mesh_->prev_halfedge_handle(o); + + OpenMesh::FaceHandle fh = base_mesh_->face_handle(h); + OpenMesh::FaceHandle fo = base_mesh_->face_handle(o); + + OpenMesh::VertexHandle vh = base_mesh_->to_vertex_handle(h); + OpenMesh::VertexHandle vo = base_mesh_->to_vertex_handle(o); + + // halfedge -> vertex + for (auto vih_it : base_mesh_->vih_range(vo)) { + base_mesh_->set_vertex_handle(vih_it, vh); + } + + // halfedge -> halfedge + base_mesh_->set_next_halfedge_handle(hp, hn); + base_mesh_->set_next_halfedge_handle(op, on); + + + // face -> halfedge + if (base_mesh_->is_valid_handle(fh)) { + base_mesh_->set_halfedge_handle(fh, hn); + } + if (base_mesh_->is_valid_handle(fo)) { + base_mesh_->set_halfedge_handle(fo, on); + } + + + // vertex -> halfedge + if (base_mesh_->halfedge_handle(vh) == o) { + base_mesh_->set_halfedge_handle(vh, hn); + } + base_mesh_->adjust_outgoing_halfedge(vh); + base_mesh_->set_isolated(vo); + + // delete stuff + base_mesh_->status(base_mesh_->edge_handle(h)).set_deleted(true); + base_mesh_->status(vo).set_deleted(true); + if (base_mesh_->has_halfedge_status()) + { + base_mesh_->status(h).set_deleted(true); + base_mesh_->status(o).set_deleted(true); + } + + // How to handle loops then? +} + +/*! + * \brief Embedding::AdjustPointersForBheCollapse lots of pointer operations to ensure a + * base collapse operation does not disrupt the embedding + * \param bheh + */ +void Embedding::AdjustPointersForBheCollapse(OpenMesh::HalfedgeHandle bheh) { + auto bheho = base_mesh_->opposite_halfedge_handle(bheh); + + // move next_heh_ pointers and meta->base pointers if applicable + if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh)) + && !base_mesh_->status(base_mesh_->property(next_heh_, bheh)).deleted() + && base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheho)) + && !base_mesh_->status(base_mesh_->property(next_heh_, bheho)).deleted()) { + auto bhehn = base_mesh_->property(next_heh_, bheh); + auto bhehno = base_mesh_->opposite_halfedge_handle(bhehn); + auto bhehpo = base_mesh_->property(next_heh_, bheho); + auto bhehp = base_mesh_->opposite_halfedge_handle(bhehpo); + assert(base_mesh_->property(next_heh_, bhehp) == bheh); + assert(base_mesh_->property(next_heh_, bhehno) == bheho); + base_mesh_->property(next_heh_, bhehp) = bhehn; + base_mesh_->property(next_heh_, bhehno) = bhehpo; + assert(base_mesh_->property(bhe_connection_, bhehp) + == base_mesh_->property(bhe_connection_, bhehn)); + assert(base_mesh_->property(bhe_connection_, bhehno) + == base_mesh_->property(bhe_connection_, bhehpo)); + } else if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh)) + && !base_mesh_->status(base_mesh_->property(next_heh_, bheh)).deleted()) { + assert(meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(bheh)))); + meta_mesh_->property(mhe_connection_, base_mesh_->property(bhe_connection_, bheh)) + = base_mesh_->property(next_heh_, bheh); + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheh))) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } else if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheho)) + && !base_mesh_->status(base_mesh_->property(next_heh_, bheho)).deleted()) { + assert(meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bheh)))); + meta_mesh_->property(mhe_connection_, base_mesh_->property(bhe_connection_, bheho)) + = base_mesh_->property(next_heh_, bheho); + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheho))) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } else { + // Sanity check; don't collapse 1 edge long meta edges. + assert(!base_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))); + } + + // bheh property deletion for visual debugging + base_mesh_->property(next_heh_, bheh) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, bheho) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, bheh) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, bheho) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + + // TESTS + // Traversal tests + if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh)) + && base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheho))) { + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheh))) + == base_mesh_->property(next_heh_, bheho)); + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheho))) + == base_mesh_->property(next_heh_, bheh)); + } else if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh))) { + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheh))) != bheho); + } else if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheho))) { + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheho))) != bheh); + } + // Connection tests + if (base_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh)) + && !base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheho))) { + // Ensure new connectivity + assert(meta_mesh_->property(mhe_connection_, base_mesh_->property(bhe_connection_, bheh)) + == base_mesh_->property(next_heh_, bheh)); + assert(!base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, + base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheh))))); + } + if (base_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheho)) + && !base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh))) { + // Ensure new connectivity + assert(meta_mesh_->property(mhe_connection_, base_mesh_->property(bhe_connection_, bheho)) + == base_mesh_->property(next_heh_, bheho)); + assert(!base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, + base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheho))))); + } + // bsplithandle tests + assert(base_mesh_->property(bsplithandle_, base_mesh_->to_vertex_handle(bheh)) != bheho); +} + +/*! + * \brief Embedding::MergeProperties + * Takes two edges and transfers the properties of and pointers + * pointing to the first to the second if they're not empty. + * \param bheh0 + * \param bheh1 + */ +void Embedding::MergeProperties(OpenMesh::HalfedgeHandle bheh0, + OpenMesh::HalfedgeHandle bheh1) { + auto bheh0o = base_mesh_->opposite_halfedge_handle(bheh0); + auto bheh1o = base_mesh_->opposite_halfedge_handle(bheh1); + auto nprop0 = base_mesh_->property(next_heh_, bheh0); + auto nprop0o = base_mesh_->property(next_heh_, bheh0o); + auto nprop1 = base_mesh_->property(next_heh_, bheh1); + auto nprop1o = base_mesh_->property(next_heh_, bheh1o); + auto bhcprop0 = base_mesh_->property(bhe_connection_, bheh0); + auto bhcprop0o = base_mesh_->property(bhe_connection_, bheh0o); + auto bhcprop1 = base_mesh_->property(bhe_connection_, bheh1); + auto bhcprop1o = base_mesh_->property(bhe_connection_, bheh1o); + auto wprop0 = base_mesh_->property(halfedge_weight_, bheh0); + auto wprop0o = base_mesh_->property(halfedge_weight_, bheh0o); + auto wprop1 = base_mesh_->property(halfedge_weight_, bheh1); + auto wprop1o = base_mesh_->property(halfedge_weight_, bheh1o); + auto mhcprop0 = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + auto mhcprop0o = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + auto mhcprop1 = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + auto mhcprop1o = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + if (meta_mesh_->is_valid_handle(bhcprop0)) { + mhcprop0 = meta_mesh_->property(mhe_connection_, bhcprop0); + } + if (meta_mesh_->is_valid_handle(bhcprop0o)) { + mhcprop0o = meta_mesh_->property(mhe_connection_, bhcprop0o); + } + if (meta_mesh_->is_valid_handle(bhcprop1)) { + mhcprop1 = meta_mesh_->property(mhe_connection_, bhcprop1); + } + if (meta_mesh_->is_valid_handle(bhcprop1o)) { + mhcprop1o = meta_mesh_->property(mhe_connection_, bhcprop1o); + } + auto bsprop0 = base_mesh_->property(bsplithandle_, base_mesh_->from_vertex_handle(bheh0)); + auto bsprop0o = base_mesh_->property(bsplithandle_, base_mesh_->to_vertex_handle(bheh0)); + // Sanity check + assert((!(mhcprop0 == bheh0) && !(mhcprop0o == bheh0o)) + || (!(mhcprop1 == bheh1) && !(mhcprop1o == bheh1o))); + if (meta_mesh_->is_valid_handle(bhcprop0)) { + // more checks + assert(meta_mesh_->is_valid_handle(bhcprop0o) && !meta_mesh_->is_valid_handle(bhcprop1) + && !meta_mesh_->is_valid_handle(bhcprop1o)); + assert(!base_mesh_->is_valid_handle(nprop1) && !base_mesh_->is_valid_handle(nprop1o)); + // property merging + // transfer bhe_connection properties + base_mesh_->property(bhe_connection_, bheh1) = bhcprop0; + base_mesh_->property(bhe_connection_, bheh1o) = bhcprop0o; + + // check the sides + if (!base_mesh_->is_valid_handle(nprop0o)) { + assert(mhcprop0 == bheh0); + meta_mesh_->property(mhe_connection_, bhcprop0) = bheh1; + // if this is not the end of the edge fix the next_heh pointers + } else { + assert(base_mesh_->is_valid_handle(nprop0o)); + base_mesh_->property(next_heh_, bheh1o) = nprop0o; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(nprop0o)) + = bheh1; + assert(base_mesh_->property(bhe_connection_, + base_mesh_->opposite_halfedge_handle(nprop0o)) + == base_mesh_->property(bhe_connection_, bheh1)); + } + // repeat for the other side + if (!base_mesh_->is_valid_handle(nprop0)) { + assert(mhcprop0o == bheh0o); + meta_mesh_->property(mhe_connection_, bhcprop0o) = bheh1o; + } else { + assert(base_mesh_->is_valid_handle(nprop0)); + base_mesh_->property(next_heh_, bheh1) = nprop0; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(nprop0)) + = bheh1o; + assert(base_mesh_->property(bhe_connection_, + base_mesh_->opposite_halfedge_handle(nprop0)) + == base_mesh_->property(bhe_connection_, bheh1o)); + } + } + // transfer bsplithandle properties + if (bsprop0 == bheh0) { + base_mesh_->property(bsplithandle_, base_mesh_->from_vertex_handle(bheh0)) = bheh1; + } + if (bsprop0o == bheh0o) { + base_mesh_->property(bsplithandle_, base_mesh_->to_vertex_handle(bheh0)) = bheh1o; + } + + assert(static_cast<int>(wprop0) == static_cast<int>(wprop0o)); + assert(static_cast<int>(wprop1) == static_cast<int>(wprop1o)); + base_mesh_->property(halfedge_weight_, bheh1) = std::min(wprop0, wprop1); + base_mesh_->property(halfedge_weight_, bheh1o) = std::min(wprop0, wprop1); + + // bheh0 property deletion for visual debugging + base_mesh_->property(next_heh_, bheh0) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, bheh0o) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, bheh0) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, bheh0o) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + + // TESTS: + // Traversal tests + if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh0))) { + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheh0))) == bheh1o); + assert(base_mesh_->property(next_heh_, bheh0) + == base_mesh_->property(next_heh_, bheh1)); + } + if (base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh0o))) { + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle( + base_mesh_->property(next_heh_, bheh0o))) == bheh1); + assert(base_mesh_->property(next_heh_, bheh0o) + == base_mesh_->property(next_heh_, bheh1o)); + } + // Connection tests + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh0))) { + assert(base_mesh_->property(bhe_connection_, bheh0) + == base_mesh_->property(bhe_connection_, bheh1)); + assert(base_mesh_->property(bhe_connection_, bheh0o) + == base_mesh_->property(bhe_connection_, bheh1o)); + } + if (!meta_mesh_->is_valid_handle(bhcprop0) + && !meta_mesh_->is_valid_handle(bhcprop1)) { + assert(!base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh1))); + if (meta_mesh_->is_valid_handle(bhcprop0o) + || meta_mesh_->is_valid_handle(bhcprop1o)) { + assert(meta_mesh_->property(mhe_connection_, + base_mesh_->property(bhe_connection_, bheh1o)) == bheh1o); + } + } + if (!meta_mesh_->is_valid_handle(bhcprop0o) + && !meta_mesh_->is_valid_handle(bhcprop1o)) { + assert(!base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh1o))); + if (meta_mesh_->is_valid_handle(bhcprop0) + || meta_mesh_->is_valid_handle(bhcprop1)) { + assert(meta_mesh_->property(mhe_connection_, + base_mesh_->property(bhe_connection_, bheh1)) == bheh1); + } + } + // bsplithandle tests + assert(base_mesh_->property(bsplithandle_, base_mesh_->from_vertex_handle(bheh0)) != bheh0); + assert(base_mesh_->property(bsplithandle_, base_mesh_->to_vertex_handle(bheh0)) != bheh0o); + // existence tests + assert(!base_mesh_->status(bheh0).deleted()); + assert(!base_mesh_->status(bheh0o).deleted()); + assert(!base_mesh_->status(bheh1).deleted()); + assert(!base_mesh_->status(bheh1o).deleted()); +} + +/*! + * \brief Embedding::Delaunay delaunay triangulation + * \param voronoidistance + * \param to_heh + * \param multiplicity_heh + * \param type + * \return true for success, false for failure + */ +bool Embedding::Delaunay(OpenMesh::VPropHandleT<double> voronoidistance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> to_heh, + OpenMesh::HPropHandleT<int> multiplicity_heh) { + + for (auto bvh : base_mesh_->vertices()) { + base_mesh_->property(voronoiID_, bvh) = OpenMesh::PolyConnectivity::InvalidVertexHandle; + base_mesh_->property(to_heh, bvh) = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(voronoidistance, bvh)=0.0; + } + + for (auto bheh : base_mesh_->halfedges()) { + base_mesh_->property(multiplicity_heh, bheh) = 0; + } + + std::queue<OpenMesh::VertexHandle> metavhqueue; + for (auto mvh : meta_mesh_->vertices()){ + //qDebug() << "Meta Vertex: " << mvh.idx(); + auto bvh = meta_mesh_->property(mv_connection_, mvh); + base_mesh_->property(voronoiID_, bvh)=mvh; + metavhqueue.push(bvh); + } + qDebug() << "Creating Voronoi Regions"; + NaiveVoronoi(metavhqueue, voronoidistance, to_heh); + qDebug() << "Finished Voronoi Regions"; + + for (auto fh : base_mesh_->faces()) { + auto bheh0 = base_mesh_->halfedge_handle(fh); + auto bheh1 = base_mesh_->next_halfedge_handle(bheh0); + auto bheh2 = base_mesh_->next_halfedge_handle(bheh1); + auto vh0 = base_mesh_->from_vertex_handle(bheh0); + auto vh1 = base_mesh_->to_vertex_handle(bheh0); + auto vh2 = base_mesh_->to_vertex_handle(bheh1); + auto metavh0 = base_mesh_->property(voronoiID_, vh0); + auto metavh1 = base_mesh_->property(voronoiID_, vh1); + auto metavh2 = base_mesh_->property(voronoiID_, vh2); + if (metavh0 != metavh1 && metavh0 != metavh2 && metavh1 != metavh2) { + auto mheh0 = FindHalfedge(bheh0); + auto mheh1 = FindHalfedge(bheh1); + auto mheh2 = FindHalfedge(bheh2); + auto mf = AddFace({metavh0, metavh1, metavh2}, {mheh0, mheh1, mheh2}); + SetFaceProperties(fh, mf); + mheh0 = meta_mesh_->halfedge_handle(mf); + mheh1 = meta_mesh_->next_halfedge_handle(mheh0); + mheh2 = meta_mesh_->next_halfedge_handle(mheh1); + + SetBorderProperties(meta_mesh_->property(mhe_border_, mheh0), mheh0); + SetBorderProperties(meta_mesh_->property(mhe_border_, mheh1), mheh1); + SetBorderProperties(meta_mesh_->property(mhe_border_, mheh2), mheh2); + } + } + qDebug() << "Done adding all basic faces."; + + qDebug() << "Applying mesh fixes."; + if (meta_mesh_->n_vertices()>0) { + std::vector<int> genus = VoronoiGenus(); + for (auto mvh : meta_mesh_->vertices()) { + if (genus.at(static_cast<unsigned long>(mvh.idx())) != 1) { + qDebug() << "Voronoi Region " << mvh.idx() << " has a euler characteristic of " + << genus.at(static_cast<unsigned long>(mvh.idx())) + << " perhaps you should choose different seed points."; + return false; + } + } + } + + if (boundaries_) { + qDebug() << "Collecting boundary faces."; + for (auto mvh : meta_mesh_->vertices()) { + if (base_mesh_->is_boundary(meta_mesh_->property(mv_connection_, mvh))) { + auto mheh = meta_mesh_->halfedge_handle(mvh); + if (!meta_mesh_->is_valid_handle(mheh)) { + TraverseBoundary(mvh); + } else { + auto mvhnext = meta_mesh_->to_vertex_handle(mheh); + // Traverse the boundary if there is still an undetected boundary halfedge + if (!base_mesh_->is_boundary(meta_mesh_->property(mv_connection_, mvhnext))) { + TraverseBoundary(mvh); + } + } + } + } + } + + if (boundaries_) { + qDebug() << "Collecting Voronoi borders near boundaries."; + for (auto bfh : base_mesh_->faces()) { + std::list<int> neighbors; + for (auto bfhiter : base_mesh_->ff_range(bfh)) { + if (!neighbors.empty() + && std::binary_search(neighbors.begin(), neighbors.end(), bfhiter.idx())) { + qDebug() << "Found a pair of Voronoi regions with more than one border," + " try triangulating again with a different configuration."; + return false; + } + neighbors.push_back(bfhiter.idx()); + } + } + VoronoiBorders(); + } + + bool correct_voronoi_borders = true; + for (auto bheh : base_mesh_->halfedges()) { + if ((base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bheh)) + != base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bheh))) + && !meta_mesh_->is_valid_handle(base_mesh_->property(bhe_border_, bheh))) { + qDebug() << "Invalid border mheh " + << base_mesh_->property(bhe_border_, bheh).idx(); + base_mesh_->status(bheh).set_selected(true); + correct_voronoi_borders = false; + debug_hard_stop_ = true; + } + } + if (!correct_voronoi_borders) { + qDebug() << "border detection failed, exiting triangulation."; + return false; + } + if (boundaries_) { + AddBoundaries(); + } + return true; +} + +/*! + * \brief Embedding::TraverseBoundary traverse the boundary that mvh lies on and add faces + * along the boundary that were missed by the voronoi triangulation + * \param mvh + */ +void Embedding::TraverseBoundary(OpenMesh::VertexHandle mvh) { + assert(base_mesh_->is_boundary(meta_mesh_->property(mv_connection_, mvh))); + OpenMesh::HalfedgeHandle bhehstart = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + // Exactly one outgoing halfedge should be a boundary + for (auto bvoheh : base_mesh_->voh_range(meta_mesh_->property(mv_connection_, mvh))) { + if (base_mesh_->is_boundary(bvoheh)) { + assert(!base_mesh_->is_valid_handle(bhehstart)); + bhehstart = bvoheh; + } + } + assert(base_mesh_->is_valid_handle(bhehstart)); + auto bhehcurr = bhehstart; + std::vector<OpenMesh::VertexHandle> newfacev = {mvh}; + std::vector<OpenMesh::HalfedgeHandle> newfaceh = {}; + std::vector<OpenMesh::HalfedgeHandle> boundaryborders = {}; + // Traverse the boundary + do { + assert(base_mesh_->is_boundary(bhehcurr)); + auto currVID = newfacev.back(); + auto newVID = base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bhehcurr)); + // Stepping over a voronoi region border: Register the new region as part of the face + if (newVID != currVID) { + newfacev.push_back(newVID); + auto mhehnew = FindHalfedge(bhehcurr); + newfaceh.push_back(mhehnew); + boundaryborders.push_back(bhehcurr); + } + // Stepping into a meta vertex: Complete the face + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bhehcurr)))) { + assert(newVID == base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bhehcurr))); + // Less than 3 means no new face was found + if (newfacev.size() > 2) { + newfaceh.push_back(OpenMesh::PolyConnectivity::InvalidHalfedgeHandle); + auto mfh = AddFace(newfacev, newfaceh); + CopyFaceProperties(mfh, boundaryborders); + auto mheh = meta_mesh_->halfedge_handle(mfh); + auto mhehiter = mheh; + do { + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_border_, mhehiter))) { + SetBorderProperties(meta_mesh_->property(mhe_border_, mhehiter), mhehiter); + } + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } while (mhehiter != mheh); + } + // Start collecting the next face, starting with the current mvh + newfacev.clear(); + newfaceh.clear(); + boundaryborders.clear(); + newfacev.push_back(newVID); + } + + bhehcurr = base_mesh_->next_halfedge_handle(bhehcurr); + } while (bhehcurr != bhehstart); +} + +/*! + * \brief Embedding::fact + * \param n + * \return n! + */ +int Embedding::fact(int n) +{ + return (n == 1 || n == 0) ? 1 : fact(n - 1) * n; +} + +/*! + * \brief Embedding::nCk + * \param n + * \param k + * \return nCk(n,k) + */ +int Embedding::nCk(int n, int k) { + return (fact(n)/(fact(k)*fact(n-k))); +} + +/*! + * \brief Embedding::VoronoiGenus + * \return the vector with the geni (or euler characteristics? one of those) + * of the voronoi regions + */ +std::vector<int> Embedding::VoronoiGenus() { + // Initialize faces as 1 to make this work, disk topology + std::vector<int> faces(meta_mesh_->n_vertices(), 0); + std::vector<int> edges(meta_mesh_->n_vertices(), 0); + std::vector<int> vertices(meta_mesh_->n_vertices(), 0); + std::vector<int> genus; + + for (auto fh : base_mesh_->faces()) { + auto heh0 = base_mesh_->halfedge_handle(fh); + auto heh1 = base_mesh_->next_halfedge_handle(heh0); + auto id0 = base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(heh0)); + auto id1 = base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(heh0)); + auto id2 = base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(heh1)); + if (id0 == id1 && id1 == id2) { + faces.at(static_cast<unsigned long>(id0.idx())) += 1; + } + } + for (auto eh : base_mesh_->edges()) { + auto vh0 = base_mesh_->from_vertex_handle(base_mesh_->halfedge_handle(eh, 0)); + auto vh1 = base_mesh_->to_vertex_handle(base_mesh_->halfedge_handle(eh, 0)); + auto id0 = base_mesh_->property(voronoiID_, vh0); + auto id1 = base_mesh_->property(voronoiID_, vh1); + if (id0 == id1) { + edges.at(static_cast<unsigned long>(id0.idx())) += 1; + } + } + for (auto vh : base_mesh_->vertices()) { + auto id = base_mesh_->property(voronoiID_, vh); + vertices.at(static_cast<unsigned long>(id.idx())) += 1; + } + for (unsigned long i=0; i<meta_mesh_->n_vertices(); ++i) { + int g = vertices.at(i) - edges.at(i) + faces.at(i); + genus.push_back(g); + } + return genus; +} + +/*! + * \brief Embedding::SetFaceProperties link a base face with a meta face (connect their + * property handles etc.) + * \param bf + * \param mf + * \param voronoidistance + * \param to_heh + * \param type + */ +void Embedding::SetFaceProperties(OpenMesh::FaceHandle bf, + OpenMesh::FaceHandle mf) { + auto bheh0 = base_mesh_->halfedge_handle(bf); + auto bheh1 = base_mesh_->next_halfedge_handle(bheh0); + auto bheh2 = base_mesh_->next_halfedge_handle(bheh1); + auto mheh0 = meta_mesh_->halfedge_handle(mf); + auto mheh1 = meta_mesh_->next_halfedge_handle(mheh0); + auto mheh2 = meta_mesh_->next_halfedge_handle(mheh1); + meta_mesh_->property(mhe_connection_, mheh0) = bheh0; + meta_mesh_->property(mhe_connection_, mheh1) = bheh1; + meta_mesh_->property(mhe_connection_, mheh2) = bheh2; + meta_mesh_->property(mhe_border_, mheh0) = bheh0; + meta_mesh_->property(mhe_border_, mheh1) = bheh1; + meta_mesh_->property(mhe_border_, mheh2) = bheh2; +} + +/*! + * \brief Embedding::CopyFaceProperties + * \param mfh + * \param boundaryborders + */ +void Embedding::CopyFaceProperties(OpenMesh::FaceHandle mfh, + std::vector<OpenMesh::HalfedgeHandle> boundaryborders) { + auto mheh = meta_mesh_->halfedge_handle(mfh); + auto mhehiter = mheh; + uint borderctr = 0; + uint mfval = meta_mesh_->valence(mfh); + for (uint i=0; i<boundaryborders.size(); ++i) { + auto bhehb = boundaryborders.at(i); + assert(meta_mesh_->from_vertex_handle(mhehiter) + == base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bhehb))); + assert(meta_mesh_->to_vertex_handle(mhehiter) + == base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bhehb))); + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } + mhehiter = mheh; + do { + if (meta_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, + meta_mesh_->opposite_halfedge_handle(mhehiter)))) { + meta_mesh_->property(mhe_connection_, mhehiter) = base_mesh_->opposite_halfedge_handle( + meta_mesh_->property(mhe_connection_, meta_mesh_->opposite_halfedge_handle(mhehiter))); + } else if (borderctr < mfval-1) { + auto bhehb = boundaryborders.at(borderctr); + assert(base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bhehb)) + == meta_mesh_->from_vertex_handle(mhehiter)); + assert(base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bhehb)) + == meta_mesh_->to_vertex_handle(mhehiter)); + meta_mesh_->property(mhe_connection_, mhehiter) = boundaryborders.at(borderctr); + } + if (meta_mesh_->is_valid_handle(meta_mesh_->property(mhe_border_, + meta_mesh_->opposite_halfedge_handle(mhehiter)))) { + meta_mesh_->property(mhe_border_, mhehiter) = base_mesh_->opposite_halfedge_handle( + meta_mesh_->property(mhe_border_, meta_mesh_->opposite_halfedge_handle(mhehiter))); + } else if (borderctr < mfval-1) { + meta_mesh_->property(mhe_border_, mhehiter) = boundaryborders.at(borderctr); + } + assert(base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehiter)) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mhehiter))); + ++borderctr; + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } while (mheh != mhehiter); +} + +/*! + * \brief Embedding::AddFace adds a meta face + * \param mvh vertices + * \param mheh halfedges, going mvh(i) -> mheh(i) -> mvh(i+1) + * Use the opposite halfedges of adjacent faces for these, or an invalid handle if they don't exist + * yet and in that case AddFace will make them. + * \return the facehandle + */ +OpenMesh::FaceHandle Embedding::AddFace(std::vector<OpenMesh::VertexHandle> mvh, + std::vector<OpenMesh::HalfedgeHandle> mheh) { + assert(mvh.size() == mheh.size()); + for (uint i = 0; i<mheh.size(); ++i) { + if (!meta_mesh_->is_valid_handle(mheh.at(i))) { + mheh.at(i) = meta_mesh_->new_edge(mvh.at(i), mvh.at((i+1)%mvh.size())); + } + } + for (uint i = 0; i<mheh.size(); ++i) { + assert(meta_mesh_->from_vertex_handle(mheh.at(i)) == mvh.at(i)); + assert(meta_mesh_->to_vertex_handle(mheh.at(i)) == mvh.at((i+1)%mvh.size())); + } + + // consistent with mesh_->add_face() + auto fnew(meta_mesh_->new_face()); + + // Face->Halfedge pointer + meta_mesh_->set_halfedge_handle(fnew, mheh.front()); + + // Halfedge->Face Pointers + for (uint i = 0; i<mheh.size(); ++i) { + meta_mesh_->set_face_handle(mheh.at(i), fnew); + } + + // Halfedge->Next Halfedge pointers + for (uint i = 0; i<mheh.size(); ++i) { + meta_mesh_->set_next_halfedge_handle(mheh.at(i), mheh.at((i+1)%mvh.size())); + } + + // Halfedge->Vertex Pointers + for (uint i = 0; i<mheh.size(); ++i) { + meta_mesh_->set_vertex_handle(mheh.at(i), mvh.at((i+1)%mvh.size())); + } + + // Vertex->Halfedge Pointers + //meta_mesh_->set_isolated(mvh0); + for (uint i = 0; i<mheh.size(); ++i) { + auto mheho = meta_mesh_->opposite_halfedge_handle(mheh.at(i)); + if (!meta_mesh_->is_valid_handle(meta_mesh_->halfedge_handle(mvh.at(i))) + || meta_mesh_->is_boundary(mheho)) { + meta_mesh_->set_halfedge_handle(mvh.at((i+1)%mvh.size()), mheho); + } + } + + assert(meta_mesh_->is_valid_handle(meta_mesh_->halfedge_handle(fnew))); + for (uint i = 0; i<mheh.size(); ++i) { + assert(meta_mesh_->is_valid_handle(meta_mesh_->next_halfedge_handle(mheh.at(i)))); + assert(meta_mesh_->is_valid_handle((meta_mesh_->halfedge_handle(mvh.at(i))))); + } + return fnew; +} + +/*! + * \brief Embedding::AddBoundaries set meta properties and pointers for boundaries. + */ +void Embedding::AddBoundaries() { + for (auto mheh : meta_mesh_->halfedges()) { + if (!meta_mesh_->is_valid_handle(meta_mesh_->next_halfedge_handle(mheh)) + && base_mesh_->is_boundary(base_mesh_->halfedge_handle( + meta_mesh_->property(mv_connection_, meta_mesh_->from_vertex_handle(mheh))))) { + meta_mesh_->set_boundary(mheh); + meta_mesh_->set_next_halfedge_handle(mheh, meta_mesh_->halfedge_handle( + meta_mesh_->to_vertex_handle(mheh))); + meta_mesh_->set_halfedge_handle(meta_mesh_->from_vertex_handle(mheh), mheh); + } + } +} + +/*! + * \brief Embedding::FindHalfedge + * \param bheh bheh lying on a mheh + * \return the opposite meta halfedge of the meta halfedge bheh lies on, or nothing + * if this is invalid + */ +OpenMesh::HalfedgeHandle Embedding::FindHalfedge(OpenMesh::HalfedgeHandle bheh) { + auto oppmheh = base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(bheh)); + if (!meta_mesh_->is_valid_handle(oppmheh)) { + return OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } else { + return meta_mesh_->opposite_halfedge_handle(oppmheh); + } +} + +/*! + * \brief Embedding::VoronoiBorders iteration over all border regions + */ +void Embedding::VoronoiBorders() { + // Considering the inclusion of boundaries in the meta mesh a local border traversal + // becomes more complicated since it would involve iterating around boundaries. + // Considering that, iterating over all base halfedges should be faster. + // However this leaves us unable to determine which border between regions a halfedge + // lies on if those two regions have two borders with each other. + // So keep in mind that using a global solution requires forbidding initial triangulations + // where two faces have more than 1 boundary to each other. + for (auto mheh : meta_mesh_->halfedges()) { + if (!meta_mesh_->is_boundary(mheh) + && meta_mesh_->is_valid_handle(meta_mesh_->property(mhe_border_, mheh))) { + SetBorderProperties(meta_mesh_->property(mhe_border_, mheh), mheh); + } + } + if (boundaries_) { + for (auto beh : base_mesh_->edges()) { + auto bheh0 = base_mesh_->halfedge_handle(beh, 0); + if (!base_mesh_->is_valid_handle(base_mesh_->property(bhe_border_, bheh0))) { + auto bheh1 = base_mesh_->halfedge_handle(beh, 1); + auto mvh0 = base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bheh0)); + auto mvh1 = base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bheh0)); + if (mvh0 != mvh1) { + auto mheh0 = meta_mesh_->find_halfedge(mvh0, mvh1); + if (!meta_mesh_->is_valid_handle(mheh0)) { + qDebug() << "Didn't find a halfedge between mvh " << mvh0.idx() + << "and " << mvh1.idx(); + } + auto mheh1 = meta_mesh_->opposite_halfedge_handle(mheh0); + base_mesh_->property(bhe_border_, bheh0) = mheh0; + base_mesh_->property(bhe_border_, bheh1) = mheh1; + } + } + } + } +} + +/*! + * \brief Embedding::SetBorderProperties iteration over one specific border region + * TODO: improve this for complex borders, those may break this function at the current state. + * \param bheh + * \param mheh + */ +void Embedding::SetBorderProperties(OpenMesh::HalfedgeHandle bheh, + OpenMesh::HalfedgeHandle mheh) { + assert(base_mesh_->is_valid_handle(bheh)); + if (base_mesh_->property(bhe_border_, bheh) == mheh) { + return; + } + base_mesh_->property(bhe_border_, bheh) = mheh; + base_mesh_->property(bhe_border_, base_mesh_->opposite_halfedge_handle(bheh)) + = meta_mesh_->opposite_halfedge_handle(mheh); + auto bheh0 = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle(bheh)); + auto bheh1 = base_mesh_->next_halfedge_handle(bheh0); + auto mv0 = meta_mesh_->from_vertex_handle(mheh); + auto mv1 = meta_mesh_->to_vertex_handle(mheh); + auto bvIDprop0 = base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bheh)); + auto bvIDprop1 = base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bheh)); + assert(mv0 == bvIDprop0); + assert(mv1 == bvIDprop1); + if (base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bheh0)) == mv0 + && base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bheh0)) == mv1) { + SetBorderProperties(bheh0, mheh); + } else if (base_mesh_->property(voronoiID_, base_mesh_->from_vertex_handle(bheh1)) == mv0 + && base_mesh_->property(voronoiID_, base_mesh_->to_vertex_handle(bheh1)) == mv1) { + SetBorderProperties(bheh1, mheh); + } +} + +/*! + * \brief Embedding::A_StarTriangulation + * \param type + */ +void Embedding::A_StarTriangulation() { + for (auto mheh : meta_mesh_->halfedges()) { + if (meta_mesh_->is_boundary(mheh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh))) { + if (debug_hard_stop_) + return; + Trace(mheh, true, false); + } + } + for (auto mheh : meta_mesh_->halfedges()) { + if (!(meta_mesh_->is_boundary(mheh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh)))) { + if (base_mesh_->property(bhe_connection_, meta_mesh_->property(mhe_connection_, mheh)) + != mheh) { + if (!meta_mesh_->is_boundary(meta_mesh_->from_vertex_handle(mheh)) + && !meta_mesh_->is_boundary(meta_mesh_->to_vertex_handle(mheh))) { + if (debug_hard_stop_) + return; + Trace(mheh, true, false); + assert(base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh))); + } + } + } + } + // Triangulate non-triangular faces + if (boundaries_) { + // switch initial_triangulation_ off temporarily or correct paths can't be found + initial_triangulation_ = false; + for (auto mheh : meta_mesh_->halfedges()) { + if (!(meta_mesh_->is_boundary(mheh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh)))) { + if (base_mesh_->property(bhe_connection_, meta_mesh_->property(mhe_connection_, mheh)) + != mheh) { + if (meta_mesh_->is_boundary(meta_mesh_->from_vertex_handle(mheh)) + || meta_mesh_->is_boundary(meta_mesh_->to_vertex_handle(mheh))) { + if (debug_hard_stop_) + return; + Trace(mheh, false, false); + } + } + } + } + for (auto mfh : meta_mesh_->faces()) { + if (meta_mesh_->valence(mfh) != 3) { + auto mhehstart = meta_mesh_->halfedge_handle(mfh); + auto mheh0 = meta_mesh_->next_halfedge_handle( + meta_mesh_->next_halfedge_handle(mhehstart)); + auto mheh1 = meta_mesh_->next_halfedge_handle(mheh0); + while (mheh1 != mhehstart) { + auto mhehnew = AddMetaEdge(mhehstart, mheh0); + Trace(mhehnew, false, false); + mheh0 = mheh1; + mheh1 = meta_mesh_->next_halfedge_handle(mheh1); + } + } + } + initial_triangulation_ = true; + } + + base_mesh_->update_normals(); +} + +/*! + * \brief Embedding::NaiveVoronoi build voronoi regions + * \param queue + * \param voronoidistance + * \param to_heh + */ +void Embedding::NaiveVoronoi(std::queue<OpenMesh::VertexHandle> queue, + OpenMesh::VPropHandleT<double> voronoidistance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> to_heh) { + while (!queue.empty()) { + auto vh = queue.front(); + for (auto vvh : base_mesh_->vv_range(vh)){ + if (!meta_mesh_->is_valid_handle(base_mesh_->property(voronoiID_, vvh))) { + base_mesh_->property(voronoiID_, vvh) = base_mesh_->property(voronoiID_, vh); + base_mesh_->property(voronoidistance, vvh) = base_mesh_->property(voronoidistance, vh) + + base_mesh_->calc_edge_length(base_mesh_->find_halfedge(vh,vvh)); + base_mesh_->property(to_heh, vvh) = base_mesh_->find_halfedge(vh,vvh); + queue.push(vvh); + } else { + auto newdist = base_mesh_->property(voronoidistance, vh) + + base_mesh_->calc_edge_length(base_mesh_->find_halfedge(vh,vvh)); + if (newdist < base_mesh_->property(voronoidistance, vvh)) { + base_mesh_->property(voronoidistance, vvh) = newdist; + base_mesh_->property(voronoiID_, vvh) = base_mesh_->property(voronoiID_, vh); + base_mesh_->property(to_heh, vvh) = base_mesh_->find_halfedge(vh,vvh); + queue.push(vvh); + if (base_mesh_->property(voronoiID_, vvh) != base_mesh_->property(voronoiID_, vh)) { + base_mesh_->property(voronoiID_, vvh) = base_mesh_->property(voronoiID_, vh); + } + } + } + } + queue.pop(); + } + + ColorizeVoronoiRegions(); +} + +/*! + * \brief Embedding::GetMetaEdge + * \param bvh + * \return the meta edge bvh lies on or InvalidHandle if it lies on none + */ +OpenMesh::EdgeHandle Embedding::GetMetaEdge(OpenMesh::VertexHandle bvh) { + if(meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))) { + //qDebug() << "Called GetMetaEdge on a Meta Vertex"; + return OpenMesh::PolyConnectivity::InvalidEdgeHandle; + } + for (auto bheh : base_mesh_->voh_range(bvh)) { + auto mheh = base_mesh_->property(bhe_connection_, bheh); + if (meta_mesh_->is_valid_handle(mheh)) { + return meta_mesh_->edge_handle(mheh); + } + } + return OpenMesh::PolyConnectivity::InvalidEdgeHandle; +} + +/*! + * \brief Embedding::MetaHalfedgeWeight + * \param mheh + * \return the log of the product of edge weights of the base halfedges in mheh + */ +double Embedding::MetaHalfedgeWeight(OpenMesh::HalfedgeHandle mheh) { + double weight = 0.0; + std::vector<OpenMesh::HalfedgeHandle> hes = GetBaseHalfedges(mheh); + for (auto bheh : hes) { + weight += std::log(base_mesh_->property(halfedge_weight_, bheh)); + } + return weight; +} + +/*! + * \brief Embedding::MetaVertexWeight + * \param mvh + * \return Vertex weight based on halfedge weight of outgoing base halfedges + */ +double Embedding::MetaVertexWeight(OpenMesh::VertexHandle mvh) { + double weight = 0.0; + for (auto moh : meta_mesh_->voh_range(mvh)) { + auto bheh = meta_mesh_->property(mhe_connection_, moh); + weight += std::log(base_mesh_->property(halfedge_weight_, bheh)); + } + return weight; +} + +/*! + * \brief Embedding::CalculateEdgeLength + * \param mheh + * \return edge length of mheh + */ +double Embedding::CalculateEdgeLength(OpenMesh::HalfedgeHandle mheh) { + assert(meta_mesh_->is_valid_handle(mheh)); + auto bheh = meta_mesh_->property(mhe_connection_, mheh); + assert(base_mesh_->is_valid_handle(bheh)); + double length = base_mesh_->calc_edge_length(bheh); + bheh = base_mesh_->property(next_heh_, bheh); + while (base_mesh_->is_valid_handle(bheh)) { + length += base_mesh_->calc_edge_length(bheh); + bheh = base_mesh_->property(next_heh_, bheh); + } + return length; +} + +/*! + * \brief Embedding::CalculateEdgeLength + * \param meh + * \return edge length of meh + */ +double Embedding::CalculateEdgeLength(OpenMesh::EdgeHandle meh) { + return CalculateEdgeLength(meta_mesh_->halfedge_handle(meh, 0)); +} + +/*! + * \brief Embedding::CalculateFlippedEdgeLength + * Calculates the length mheh has after rotation. + * This method is only safe if the incident faces of mheh are triangles + * mheh will also be retraced in the process (avoiding this would be difficult + * without changing a lot of code and removing base mesh cleanup operations) + * \param mheh + * \return length of flipped mheh + */ +double Embedding::CalculateFlippedEdgeLength(OpenMesh::HalfedgeHandle mheh) { + Rotate(meta_mesh_->edge_handle(mheh)); + double length = CalculateEdgeLength(mheh); + Rotate(meta_mesh_->edge_handle(mheh)); + + return length; +} + +/*! + * \brief Embedding::CalculateFlippedEdgeLength + * \param meh + * \return length of flipped mehs + */ +double Embedding::CalculateFlippedEdgeLength(OpenMesh::EdgeHandle meh) { + return CalculateFlippedEdgeLength(meta_mesh_->halfedge_handle(meh, 0)); +} + +/*! + * \brief Embedding::CleanMetaMesh + * Delete all meta mesh face handles with valence <3 so that OpenFlipper can display it + * Not recommended outside of debugging since behavior afterwards is undefined. This only + * serves for visualization of strange edge cases. + */ +void Embedding::CleanMetaMesh() { + for (auto mfh : meta_mesh_->faces()) { + auto mheh = meta_mesh_->halfedge_handle(mfh); + auto mvh0 = meta_mesh_->from_vertex_handle(mheh); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh); + auto mvh2 = meta_mesh_->to_vertex_handle(meta_mesh_->next_halfedge_handle(mheh)); + if (mvh0 == mvh1) { + meta_mesh_->status(mfh).set_deleted(true); + meta_mesh_->set_face_handle(mheh, OpenMesh::PolyConnectivity::InvalidFaceHandle); + } else if (mvh0 == mvh2) { + meta_mesh_->status(mfh).set_deleted(true); + meta_mesh_->set_face_handle(mheh, OpenMesh::PolyConnectivity::InvalidFaceHandle); + meta_mesh_->set_face_handle(meta_mesh_->next_halfedge_handle(mheh) + , OpenMesh::PolyConnectivity::InvalidFaceHandle); + } + } + for (auto mheh : meta_mesh_->halfedges()) { + if (!meta_mesh_->is_valid_handle(meta_mesh_->face_handle(mheh))) { + meta_mesh_->set_boundary(mheh); + } + } +} + +/*! + * \brief Embedding::MetaGarbageCollection + * \param vh_update + * \param heh_update + * \param fh_update + */ +void Embedding::MetaGarbageCollection(std::vector<OpenMesh::VertexHandle*> vh_update, + std::vector<OpenMesh::HalfedgeHandle*> heh_update, + std::vector<OpenMesh::FaceHandle*> fh_update) +{ + for (const auto& bheh : base_mesh_->halfedges()) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))) { + heh_update.push_back(&base_mesh_->property(bhe_connection_, bheh)); + } + } + for (const auto& bvh : base_mesh_->vertices()) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))) { + vh_update.push_back(&base_mesh_->property(bv_connection_, bvh)); + } + } + + meta_mesh_->garbage_collection(vh_update, heh_update, fh_update); +} + +/*! + * \brief Embedding::BaseGarbageCollection + * \param vh_update + * \param heh_update + * \param fh_update + */ +void Embedding::BaseGarbageCollection(std::vector<OpenMesh::VertexHandle*> vh_update, + std::vector<OpenMesh::HalfedgeHandle*> heh_update, + std::vector<OpenMesh::FaceHandle*> fh_update) +{ + OpenMesh::VPropHandleT<unsigned long> bsplithandle_temp; + OpenMesh::HPropHandleT<unsigned long> next_heh_temp; + std::vector<OpenMesh::HalfedgeHandle> bsplithandles(base_mesh_->n_vertices()); + std::vector<OpenMesh::HalfedgeHandle> next_hehs(base_mesh_->n_halfedges()); + base_mesh_->add_property(bsplithandle_temp, "Temporary property for storing vertex " + "indices for the bsplithandle_ property since the garbage collection is useless"); + base_mesh_->add_property(next_heh_temp, "Temporary property for storing vertex " + "indices for the next_heh_ property since the garbage collection is useless"); + for (const auto& bvh : base_mesh_->vertices()) { + auto bsprop = base_mesh_->property(bsplithandle_, bvh); + //assert(base_mesh_->is_valid_handle(bsprop) + // || !bsprop.is_valid()); + unsigned long index = static_cast<unsigned long>(bvh.idx()); + bsplithandles.at(index) = bsprop; + base_mesh_->property(bsplithandle_temp, bvh) = index; + if (bsprop != OpenMesh::PolyConnectivity::InvalidHalfedgeHandle) { + heh_update.push_back(&bsplithandles.at(index)); + } + } + for (const auto& bheh : base_mesh_->halfedges()) { + auto nhprop = base_mesh_->property(next_heh_, bheh); + //assert(base_mesh_->is_valid_handle(nhprop) + // || !nhprop.is_valid()); + unsigned long index = static_cast<unsigned long>(bheh.idx()); + next_hehs.at(index) = nhprop; + base_mesh_->property(next_heh_temp, bheh) = index; + if (nhprop != OpenMesh::PolyConnectivity::InvalidHalfedgeHandle) { + heh_update.push_back(&next_hehs.at(index)); + } + } + + for (const auto& mheh : meta_mesh_->halfedges()) { + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh))) { + heh_update.push_back(&meta_mesh_->property(mhe_connection_, mheh)); + } + } + for (const auto& mvh : meta_mesh_->vertices()) { + if (base_mesh_->is_valid_handle(meta_mesh_->property(mv_connection_, mvh))) { + vh_update.push_back(&meta_mesh_->property(mv_connection_, mvh)); + } + } + + base_mesh_->garbage_collection(vh_update, heh_update, fh_update); + + // Write bsplithandles and next_hehs back into their properties. + for (const auto& bvh : base_mesh_->vertices()) { + base_mesh_->property(bsplithandle_, bvh) = bsplithandles.at( + base_mesh_->property(bsplithandle_temp, bvh)); + //assert(base_mesh_->is_valid_handle(base_mesh_->property(bsplithandle_, bvh)) + // || !base_mesh_->property(bsplithandle_, bvh).is_valid()); + } + for (const auto& bheh : base_mesh_->halfedges()) { + base_mesh_->property(next_heh_, bheh) = next_hehs.at( + base_mesh_->property(next_heh_temp, bheh)); + //assert(base_mesh_->is_valid_handle(base_mesh_->property(next_heh_, bheh)) + // || !base_mesh_->property(next_heh_, bheh).is_valid()); + } + base_mesh_->remove_property(bsplithandle_temp); + base_mesh_->remove_property(next_heh_temp); +} + +/*! + * \brief Embedding::ColorizeMetaMesh + */ +void Embedding::ColorizeMetaMesh() { + /*/ enable this to mark all meta mesh vertices in the base mesh + ColorizeMetaMeshVertices(); + //*/ + ColorizeMetaEdges(); + /*/ enable this to mark all voronoi borders in the base mesh + ColorizeBorders(); + //*/ +} + +/*! + * \brief Embedding::ColorizeMetaMeshVertices + */ +void Embedding::ColorizeMetaMeshVertices() { + if (!debug_hard_stop_) { + for (auto bvh : base_mesh_->vertices()) { + base_mesh_->status(bvh).set_selected(false); + } + } + for (auto mvh : meta_mesh_->vertices()) { + auto bvh = meta_mesh_->property(mv_connection_, mvh); + base_mesh_->status(bvh).set_selected(true); + } +} + +/*! + * \brief Embedding::ColorizeMetaEdges + */ +void Embedding::ColorizeMetaEdges() { + //* + draw_.clear(); + auto colorgenerator = new ACG::HaltonColors(); + auto numcolors = meta_mesh_->n_edges(); + if (numcolors != 0) { + std::vector<ACG::Vec4f> colors; + colorgenerator->generateNextNColors(static_cast<int>(numcolors) + , std::back_inserter(colors)); + for (auto meh : meta_mesh_->edges()) { + //qDebug() << meh.idx() << " < " << numcolors; + auto mheh = meta_mesh_->halfedge_handle(meh, 0); + auto frompoint = meta_mesh_->point(meta_mesh_->from_vertex_handle(mheh)); + auto topoint = meta_mesh_->point(meta_mesh_->to_vertex_handle(mheh)); + draw_.line(frompoint, topoint, colors.at( + static_cast<unsigned long>(meh.idx()))); + } + for (auto beh : base_mesh_->edges()) { + auto bheh = base_mesh_->halfedge_handle(beh, 0); + auto mheh = base_mesh_->property(bhe_connection_, bheh); + if (meta_mesh_->is_valid_handle(mheh)) { + auto meh = meta_mesh_->edge_handle(mheh); + //qDebug() << meh.idx() << " < " << numcolors; + assert(base_mesh_->is_valid_handle(base_mesh_->from_vertex_handle(bheh))); + assert(base_mesh_->is_valid_handle(base_mesh_->to_vertex_handle(bheh))); + auto frompoint = base_mesh_->point(base_mesh_->from_vertex_handle(bheh)); + auto topoint = base_mesh_->point(base_mesh_->to_vertex_handle(bheh)); + draw_.line(frompoint, topoint, colors.at( + static_cast<unsigned long>(meh.idx()))); + } + } + } + /*/ Alternative: selection instead of colorizing. + for (auto bheh : base_mesh_->halfedges()) { + base_mesh_->status(bheh).set_selected(false); + } + for (auto bheh : base_mesh_->halfedges()) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh)) + base_mesh_->status(bheh).set_selected(true); + } + //*/ +} + +/*! + * \brief Embedding::ColorizeVoronoiRegions + */ +void Embedding::ColorizeVoronoiRegions() { + auto vertex_colors_pph = base_mesh_->vertex_colors_pph(); + auto colorgenerator = new ACG::HaltonColors(); + auto numcolors = meta_mesh_->n_vertices(); + if (numcolors != 0) { + std::vector<ACG::Vec4f> colors; + colorgenerator->generateNextNColors(static_cast<int>(numcolors) + , std::back_inserter(colors)); + for (auto vh : base_mesh_->vertices()) { + auto voronoiregion = base_mesh_->property(voronoiID_, vh).idx(); + base_mesh_->property(vertex_colors_pph, vh) = + colors.at(static_cast<unsigned long>(voronoiregion)); + } + } +} + +/*! + * \brief Embedding::ColorizeBorders + */ +void Embedding::ColorizeBorders() { + /* Comments: Switch the code to these to colorize with haltoncolors instead of selecting. + auto halfedge_colors_pph = base_mesh_->halfedge_colors_pph(); + auto colorgenerator = new ACG::HaltonColors(); + */ + auto numcolors = meta_mesh_->n_halfedges(); + if (numcolors != 0) { + /* + std::vector<ACG::Vec4f> colors; + colorgenerator->generateNextNColors(static_cast<int>(numcolors) + , std::back_inserter(colors)); + */ + for (auto heh : base_mesh_->halfedges()) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_border_, heh))) { + /* + auto border = base_mesh_->property(bhe_border_, heh).idx(); + base_mesh_->property(halfedge_colors_pph, heh) = + colors.at(static_cast<unsigned long>(border)); + */ + base_mesh_->status(heh).set_selected(true); + } else { + /* + base_mesh_->property(halfedge_colors_pph, heh) = + ACG::Vec4f(0.0f, 0.0f, 0.0f, 1.0f); + */ + //base_mesh_->status(heh).set_selected(false); + } + } + } +} + +/*! + * \brief Embedding::TestHalfedgeConsistency expensive test, will also only work in debug mode. + * Could probably be deleted but I'll leave it here just in case. + */ +void Embedding::TestHalfedgeConsistency() { + for (auto eh : base_mesh_->edges()) { + auto heh0 = base_mesh_->halfedge_handle(eh, 0); + auto heh1 = base_mesh_->halfedge_handle(eh, 1); + bool halfedgeconnectedprop0 = + (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, heh0))); + bool halfedgeconnectedprop1 = + (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, heh1))); + assert(halfedgeconnectedprop0 == halfedgeconnectedprop1); + } +} + +/*! + * \brief Embedding::Trace Most important function: Traces a meta edge into the base mesh + * \param mheh the meta edge to be traced + * \param cleanup switch: cleanup after tracing or not + * \param mark_trace switch: mark the trace or not + * \param facetype preprocessedface or not? Current implementation calls the pre-processing + * in Trace itself. UNPROCESSEDFACE should call a trace method that does its own base edge + * splits where needed: this is not implemented yet. + * TODO: Implement Advanced trace or remove the switch. + */ +void Embedding::Trace(OpenMesh::HalfedgeHandle mheh, bool cleanup, bool mark_trace, + TraceFaceAttr facetype) { + auto bheh = meta_mesh_->property(mhe_connection_, mheh); + if (base_mesh_->is_valid_handle(bheh) + && base_mesh_->property(bhe_connection_, bheh) == mheh) { + return; + } + + if (meta_mesh_->is_boundary(mheh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh))) { + BoundaryTrace(mheh); + } else { + switch (facetype) { + case UNPROCESSEDFACE : + AdvancedTrace(mheh); + break; + case PREPROCESSEDFACE : + SimpleTrace(mheh, cleanup, mark_trace); + break; + } + } +} + +/*! + * \brief Embedding::Retrace + * Retraces the given meta halfedge + * \param mheh + * \param cleanup + * \param mark_trace + * \param facetype + */ +void Embedding::Retrace(OpenMesh::HalfedgeHandle mheh, bool cleanup, bool mark_trace, + TraceFaceAttr facetype) { + if (!meta_mesh_->is_valid_handle(mheh) + || meta_mesh_->status(mheh).deleted()) { + qDebug() << "Invalid or deleted halfedge mheh cannot be retraced"; + return; + } + RemoveMetaEdgeFromBaseMesh(mheh); + Trace(mheh, cleanup, mark_trace, facetype); +} + +/*! + * \brief Embedding::BoundaryTrace Trace a boundary edge. This is a special case but also + * much easier since the meta edge must then also lie on the boundary by definition. + * \param mheh + * \param mark_trace + */ +void Embedding::BoundaryTrace(OpenMesh::HalfedgeHandle mheh, bool mark_trace) { + //qDebug() << "Tracing boundary halfedge mheh " << mheh.idx(); + // align the edge along the boundary + if (!meta_mesh_->is_boundary(mheh)) { + mheh = meta_mesh_->opposite_halfedge_handle(mheh); + } + assert(meta_mesh_->is_boundary(mheh)); + + auto mvh0 = meta_mesh_->from_vertex_handle(mheh); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh); + auto bvh0 = meta_mesh_->property(mv_connection_, mvh0); + auto bvh1 = meta_mesh_->property(mv_connection_, mvh1); + + auto bheh = base_mesh_->halfedge_handle(bvh0); + assert(base_mesh_->is_boundary(bheh)); + + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))) { + qDebug() << "Boundary mheh" << mheh.idx() << " is already traced or blocked by " + " another edge."; + return; + } + + if (mark_trace) { + base_mesh_->status(bheh).set_selected(true); + } + std::vector<OpenMesh::HalfedgeHandle> path = {bheh}; + while (!meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bheh)))) { + bheh = base_mesh_->next_halfedge_handle(bheh); + if (mark_trace) { + base_mesh_->status(bheh).set_selected(true); + } + assert(base_mesh_->is_boundary(bheh)); + path.push_back(bheh); + //base_mesh_->status(bheh).set_selected(true); + } + auto bvhe = base_mesh_->to_vertex_handle(bheh); + assert(bvhe == bvh1); + + InsertPath(mheh, path); + + if (initial_triangulation_) { + ProcessEdge(meta_mesh_->edge_handle(mheh)); + } +} + +/*! + * \brief Embedding::SimpleTrace Trace function, traces mheh. + * \param mheh + * \param cleanup + * \param mark_trace + */ +void Embedding::SimpleTrace(OpenMesh::HalfedgeHandle mheh, bool cleanup, bool mark_trace) { + //qDebug() << "Entering A*"; + ProcessFace(mheh); + + auto bheh = FindA_StarStartingHalfedges(mheh); + auto path = A_StarBidirectional(bheh.first, bheh.second, mark_trace, mheh); + if (debug_hard_stop_) { + return; + } + //qDebug() << "Entering Insertpath"; + if (!path.empty()) { + InsertPath(mheh, path); + } + + //qDebug() << "Entering ProcessEdge"; + // post processing during initial triangulation + if (initial_triangulation_) { + ProcessEdge(meta_mesh_->edge_handle(mheh)); + } else if (cleanup) { + CleanupFace(mheh); + CleanupFace(meta_mesh_->opposite_halfedge_handle(mheh)); + } + //base_mesh_->update_normals(); +} + +/*! + * \brief Embedding::AdvancedTrace + * Unimplemented method. This Trace method should handle all neccessary split operations + * inside the sector it traces in by itself. I will leave this here since implementing it + * used to be an idea, but there was no time left. + * TODO: Implement this + * \param mheh + */ +void Embedding::AdvancedTrace(OpenMesh::HalfedgeHandle mheh) { + meta_mesh_->status(mheh).deleted(); +} + +/*! + * \brief Embedding::A_StarBidirectional Bidirectional A* Algorithm to find the + * best path quickly, see these: + * https://en.wikipedia.org/wiki/A*_search_algorithm + * https://www.cs.princeton.edu/courses/archive/spr06/cos423/Handouts/EPP%20shortest%20path%20algorithms.pdf + * \param bheh0 Starting halfedge 0 found by FindA_StarStartingHalfedges + * \param bheh1 Starting halfedge 1 found by FindA_StarStartingHalfedges + * \param mark_trace mark the trace y/n? + * \param mheh meta halfedge to be traced + * \return a vector of ordered base halfedges representing the mheh. these still + * need to be input and their pointers adjusted afterwards. + */ +std::vector<OpenMesh::HalfedgeHandle> Embedding::A_StarBidirectional( + OpenMesh::HalfedgeHandle bheh0, OpenMesh::HalfedgeHandle bheh1, + bool mark_trace, OpenMesh::HalfedgeHandle mheh) { + if (mark_trace) { + for (auto bheh : base_mesh_->halfedges()) { + base_mesh_->status(bheh).set_selected(false); + } + } + auto bvh0 = base_mesh_->from_vertex_handle(bheh0); + auto bvh1 = base_mesh_->from_vertex_handle(bheh1); + + const double inf = std::numeric_limits<double>::infinity(); + double shortest = inf; + OpenMesh::VertexHandle middle; + + // Heaps that always display the current vertex with lowest value from direction 0 and 1 + // Heap data structure allows easy access for the next element needed in the algorithm + struct cmp { + bool operator()(const std::pair<OpenMesh::VertexHandle, double> &a, + const std::pair<OpenMesh::VertexHandle, double> &b) { + return a.second > b.second; + } + }; + std::priority_queue<std::pair<OpenMesh::VertexHandle, double>, + std::vector<std::pair<OpenMesh::VertexHandle, double>>, cmp> minheap0; + std::priority_queue<std::pair<OpenMesh::VertexHandle, double>, + std::vector<std::pair<OpenMesh::VertexHandle, double>>, cmp> minheap1; + + OpenMesh::VPropHandleT<double> gscore0; + OpenMesh::VPropHandleT<double> gscore1; + base_mesh_->add_property(gscore0, "Distance from bvh0"); + base_mesh_->add_property(gscore1, "Distance from bvh1"); + + minheap0.push(std::make_pair(bvh0, A_StarHeuristic(bvh0, bvh1, bvh0))); + assert(minheap0.top().second == 0.0); + minheap1.push(std::make_pair(bvh1, A_StarHeuristic(bvh1, bvh0, bvh1))); + assert(minheap1.top().second == 0.0); + + // Saving the previous vh for each vh operated through; used for path reconstruction + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> predecessor0; + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> predecessor1; + base_mesh_->add_property(predecessor0, "Previous vertex on shortest path"); + base_mesh_->add_property(predecessor1, "Previous vertex on shortest path"); + + OpenMesh::VPropHandleT<bool> closed_set0; + base_mesh_->add_property(closed_set0, "Vertices in the first closed set"); + OpenMesh::VPropHandleT<bool> closed_set1; + base_mesh_->add_property(closed_set1, "Vertices in the second closed set"); + for (auto vh : base_mesh_->vertices()) { + base_mesh_->property(closed_set0, vh) = false; + base_mesh_->property(closed_set1, vh) = false; + base_mesh_->property(predecessor0, vh) = OpenMesh::PolyConnectivity::InvalidVertexHandle; + base_mesh_->property(predecessor1, vh) = OpenMesh::PolyConnectivity::InvalidVertexHandle; + base_mesh_->property(gscore0, vh) = inf; + base_mesh_->property(gscore1, vh) = inf; + } + base_mesh_->property(gscore0, bvh0) = 0.0; + base_mesh_->property(gscore1, bvh1) = 0.0; + + + // Search while a shortest path hasnt been found and the heaps are not empty + while (!minheap0.empty() + && !minheap1.empty() + && shortest + A_StarHeuristic(bvh0, bvh1, bvh1) + > base_mesh_->property(gscore0, minheap0.top().first) + + base_mesh_->property(gscore1, minheap1.top().first)) { + //qDebug() << "Start of A* Loop"; + OpenMesh::VertexHandle bvhc; + // One side of the search + if (minheap0.top().second < minheap1.top().second) { + bvhc = minheap0.top().first; + //qDebug() << "-Heap Value0: " << minheap0.top().second << " -A_Star_Heuristic0: " + // << A_StarHeuristic(bvh0, bvh1, curr) << " -gScore0: " << gscore0.at(curr); + minheap0.pop(); + // Only deal with vertices not in the closed set yet + if (base_mesh_->property(closed_set0, bvhc) == false) { + base_mesh_->property(closed_set0, bvhc) = true; + // Iterate over the neighboring vertices + auto neighbors0 = A_StarNeighbors(bvhc, mheh, closed_set0, bvh1, bheh0); + //if (neighbors0.empty()) + // qDebug() << "Empty A* Neighborhood0."; + for (auto bvhn : neighbors0) { + // If a better gscore is found set it for neighbors + auto tentative_gScore = inf; + if (!base_mesh_->is_valid_handle(base_mesh_->find_halfedge(bvhc, bvhn))) { + qDebug() << "Invalid seed halfedge bheh0"; + } else { + //base_mesh_->status(base_mesh_->find_halfedge(curr, vh)).set_selected(true); + auto bhehw = base_mesh_->find_halfedge(bvhc, bvhn); + // use reduced cost (modified graph) + double edge_weight = base_mesh_->calc_edge_length(bhehw); + // Increase the weight of non-original halfedges + edge_weight *= base_mesh_->property(halfedge_weight_, bhehw); + tentative_gScore = base_mesh_->property(gscore0, bvhc) + edge_weight; + } + //qDebug() << "n_vertices: " << base_mesh_->n_vertices() << " - vh.idx(): " + // << vh.idx(); + if (base_mesh_->property(gscore0, bvhn) > tentative_gScore) { + base_mesh_->property(gscore0, bvhn) = tentative_gScore; + base_mesh_->property(predecessor0, bvhn) = bvhc; + + // Push the new neighbors into the heap adding the heuristic value for speed + // The same value may be added twice but since a minheap is used and only the + // first time it comes through it will not be in the closed set yet this is ok + minheap0.push(std::make_pair(bvhn, tentative_gScore + A_StarHeuristic(bvh0, bvh1, bvhn))); + if (mark_trace) { + base_mesh_->status(base_mesh_->find_halfedge(bvhc, bvhn)).set_selected(true); + } + } + } + // Check if a connection with the other side has been made, if so check if it is minimal + if (bvhc != base_mesh_->from_vertex_handle(bheh0) && + base_mesh_->property(gscore1, bvhc) < inf) { + auto newdist = base_mesh_->property(gscore1, bvhc) + + base_mesh_->property(gscore0, bvhc); + if (shortest > newdist) { + shortest = newdist; + middle = bvhc; + //qDebug() << "found new shortest path of length: " << shortest; + } + } + } + // Other side of the search + } else { + bvhc = minheap1.top().first; + //qDebug() << "-Heap Value1: " << minheap1.top().second << " -A_Star_Heuristic1: " + // << A_StarHeuristic(bvh1, bvh0, curr) << " -gScore1: " << gscore1.at(curr); + minheap1.pop(); + // Only deal with vertices not in the closed set yet + if (base_mesh_->property(closed_set1, bvhc) == false) { + base_mesh_->property(closed_set1, bvhc) = true; + // Iterate over the neighboring vertices + auto neighbors1 = A_StarNeighbors(bvhc, mheh, closed_set1, bvh0, bheh1); + //if (neighbors1.empty()) + // qDebug() << "Empty A* Neighborhood1."; + for (auto bvhn : neighbors1) { + // If a better gscore is found set it for neighbors + auto tentative_gScore = inf; + if (!base_mesh_->is_valid_handle(base_mesh_->find_halfedge(bvhc, bvhn))) { + qDebug() << "Invalid seed halfedge bheh1"; + } else { + //base_mesh_->status(base_mesh_->find_halfedge(curr, vh)).set_selected(true); + auto bhehw = base_mesh_->find_halfedge(bvhc, bvhn); + // use reduced cost (modified graph) + double edge_weight = base_mesh_->calc_edge_length(bhehw); + // Increase the weight of non-original halfedges + edge_weight *= base_mesh_->property(halfedge_weight_, bhehw); + tentative_gScore = base_mesh_->property(gscore1, bvhc) + edge_weight; + } + //qDebug() << "n_vertices: " << base_mesh_->n_vertices() << " - vh.idx(): " + // << vh.idx(); + if (base_mesh_->property(gscore1, bvhn) > tentative_gScore) { + base_mesh_->property(gscore1, bvhn) = tentative_gScore; + base_mesh_->property(predecessor1, bvhn) = bvhc; + + // Push the new neighbors into the heap adding the heuristic value for speed + // The same value may be added twice but since a minheap is used and only the + // first time it comes through it will not be in the closed set yet this is ok + minheap1.push(std::make_pair(bvhn, tentative_gScore + A_StarHeuristic(bvh1, bvh0, bvhn))); + if (mark_trace) { + base_mesh_->status(base_mesh_->find_halfedge(bvhc, bvhn)).set_selected(true); + } + } + } + // Check if a connection with the other side has been made, if so check if it is minimal + if (bvhc != base_mesh_->from_vertex_handle(bheh1) && + base_mesh_->property(gscore0, bvhc) < inf) { + auto newdist = base_mesh_->property(gscore1, bvhc) + + base_mesh_->property(gscore0, bvhc); + if (shortest > newdist) { + shortest = newdist; + middle = bvhc; + //qDebug() << "found new shortest path of length: " << shortest; + } + } + } + } + } + //qDebug() << "Finished A* Loop"; + if (!base_mesh_->is_valid_handle(middle)) { + debug_hard_stop_=true; + if (!mark_trace) { + qWarning() << "A* Tracing failed for vertices " << bvh0.idx() << " and " + << bvh1.idx() << " retracing to mark."; + A_StarBidirectional(bheh0, bheh1, true, mheh); + qDebug() << "Marking the closed set"; + for (auto bvh : base_mesh_->vertices()) { + if (base_mesh_->property(closed_set0, bvh) || + base_mesh_->property(closed_set1, bvh)) { + base_mesh_->status(bvh).set_selected(true); + } + } + qDebug() << "Marking bheh0 " << bheh0.idx() << "and bheh1 " << bheh1.idx() + << "they are opposites: " + << (bheh0 == base_mesh_->opposite_halfedge_handle(bheh1)); + base_mesh_->status(bheh0).set_selected(true); + base_mesh_->status(bheh1).set_selected(true); + } else { + MarkVertex(bvh0); + MarkVertex(bvh1); + } + } + base_mesh_->remove_property(closed_set0); + base_mesh_->remove_property(closed_set1); + base_mesh_->remove_property(gscore0); + base_mesh_->remove_property(gscore1); + return A_StarReconstructPath(middle, predecessor0, predecessor1); +} + +/*! + * \brief Embedding::Distance + * \param bvhf + * \param bvhv + * \return direct spatial distance between vertices bvhf and bvhv + */ +double Embedding::Distance(OpenMesh::VertexHandle bvhf, OpenMesh::VertexHandle bvhv) { + auto pf = base_mesh_->point(bvhf); + auto pv = base_mesh_->point(bvhv); + double distfv = std::sqrt((pf[0]-pv[0])*(pf[0]-pv[0]) + +(pf[1]-pv[1])*(pf[1]-pv[1]) + +(pf[2]-pv[2])*(pf[2]-pv[2])); + return distfv; +} + +/*! + * \brief Embedding::A_StarHeuristic The heuristic used in A* here, detailed explanation: + * https://www.cs.princeton.edu/courses/archive/spr06/cos423/Handouts/EPP%20shortest%20path%20algorithms.pdf + * Averaging the distance heuristic from both sides makes it admissible and feasible + * Variables corresponding to page 4, Bidirectional A* Search + * p_f(v): 1/2(pi_f(v)-pi_r(v)) + (pi_r(t)/2) + * Distance(bvhf, bvhv) : pi_f(v) + * Distance(bvhr, bvhv) : pi_r(v) + * Distance(bvhr, bvhf) : pi_r(t) + * Then call it with flipped bvhf and bvhr for the opposite direction p_r(v). + * p_r(v): 1/2(pi_r(v)-pi_f(v)) + (pi_r(s)/2) + * \param bvhf + * \param bvhr + * \param bvhv + * \return heuristical values to sort the heaps by + */ +double Embedding::A_StarHeuristic(OpenMesh::VertexHandle bvhf, + OpenMesh::VertexHandle bvhr, + OpenMesh::VertexHandle bvhv) { + return 0.5 * (Distance(bvhf, bvhv) - Distance(bvhr, bvhv)) + + (Distance(bvhr, bvhf) / 2); +} + +/*! + * \brief Embedding::A_StarNeighbors + * \param bvh + * \param mheh + * \param closed_set + * \param towardsbvh + * \param bheh + * \return the VV_Neighborhood excluding vertices already closed by A* and border vertices + */ +std::vector<OpenMesh::VertexHandle> Embedding::A_StarNeighbors(OpenMesh::VertexHandle bvh, + OpenMesh::HalfedgeHandle mheh, + OpenMesh::VPropHandleT<bool> closed_set, + OpenMesh::VertexHandle towardsbvh, + OpenMesh::HalfedgeHandle bheh) { + std::vector<OpenMesh::VertexHandle> neighbors; + if (bvh == towardsbvh && base_mesh_->from_vertex_handle(bheh) != bvh) { + return neighbors; + } + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))) { + // Ensure that the seed halfedge originates from the initial vertex + assert(base_mesh_->from_vertex_handle(bheh) == bvh); + // Ensure that the seed halfedge is empty + assert(!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bheh))); + auto bhehiter = bheh; + do { + if (!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bhehiter)) + && (!IsSectorBorder(base_mesh_->to_vertex_handle(bhehiter)) + || base_mesh_->to_vertex_handle(bhehiter) == towardsbvh)) { + neighbors.push_back(base_mesh_->to_vertex_handle(bhehiter)); + } + bhehiter = base_mesh_->next_halfedge_handle( + base_mesh_->opposite_halfedge_handle(bhehiter)); + } while (!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bhehiter)) + && bhehiter != bheh); + } else { + for (auto voh_it: base_mesh_->voh_range(bvh)) { + auto vv_it = base_mesh_->to_vertex_handle(voh_it); + if ((((base_mesh_->property(closed_set, vv_it) == false) + && !IsSectorBorder(vv_it))) + && ValidA_StarEdge(voh_it, mheh)) { + neighbors.push_back(vv_it); + } + } + } + + return neighbors; +} + +/*! + * \brief Embedding::ValidA_StarEdge + * \param bheh + * \param mheh + * \return whether bheh is a Valid edge for A* traversal based on the bhe_border_ + * properties. These properties describe voronoi regions, so this method is only + * really used during initial triangulation to ensure traces don't block each other. + */ +bool Embedding::ValidA_StarEdge(OpenMesh::HalfedgeHandle bheh, + OpenMesh::HalfedgeHandle mheh) { + if (!initial_triangulation_) { + return true; + } + return (!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_border_, bheh)) + || (base_mesh_->property(bhe_border_, bheh) == mheh) + || (base_mesh_->property(bhe_border_, bheh) == + meta_mesh_->opposite_halfedge_handle(mheh))); +} + +/*! + * \brief Embedding::A_StarReconstructPath reconstructs a meta halfedge based on the output + * of the A_StarBidirectional function + * \param middle the middle halfedge of mheh + * \param predecessor0 predecessor property in one direction from the middle + * \param predecessor1 predecessor property in the other direction from the middle + * \return An ordered vector of halfedges representing the meta edge that was traced + */ +std::vector<OpenMesh::HalfedgeHandle> Embedding::A_StarReconstructPath( + OpenMesh::VertexHandle middle, + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> predecessor0, + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> predecessor1) { + std::list<OpenMesh::VertexHandle> total_path; + std::vector<OpenMesh::HalfedgeHandle> returnpath; + if (!base_mesh_->is_valid_handle(middle)) { + // qDebug() << "A_StarReconstructPath returning empty path"; + return returnpath; + } + total_path.push_back(middle); + auto curr = base_mesh_->property(predecessor0, middle); + while (base_mesh_->is_valid_handle(curr)) { + total_path.push_front(curr); + curr = base_mesh_->property(predecessor0, curr); + } + curr = middle; + curr = base_mesh_->property(predecessor1, middle); + while (base_mesh_->is_valid_handle(curr)) { + total_path.push_back(curr); + curr = base_mesh_->property(predecessor1, curr); + } + OpenMesh::VertexHandle vh0 = total_path.front(); + OpenMesh::VertexHandle vh1; + total_path.pop_front(); + while (!total_path.empty()) { + vh1 = vh0; + vh0 = total_path.front(); + auto heh = base_mesh_->find_halfedge(vh1, vh0); + returnpath.push_back(heh); + total_path.pop_front(); + } + base_mesh_->remove_property(predecessor0); + base_mesh_->remove_property(predecessor1); + return returnpath; +} + +/*! + * \brief Embedding::IsSectorBorder + * \param bvh + * \param exceptions + * \return true if bvh lies on a meta_mesh element (vertex or edge) which is NOT + * included in the list of exception + */ +bool Embedding::IsSectorBorder(OpenMesh::VertexHandle bvh, + std::list<OpenMesh::HalfedgeHandle> exceptions) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))) { + if (!exceptions.empty() && meta_mesh_->to_vertex_handle(exceptions.front()) == + base_mesh_->property(bv_connection_, bvh)) { + return false; + } + return true; + } + for (auto voh_it : base_mesh_->voh_range(bvh)) { + auto mhehprop = base_mesh_->property(bhe_connection_, voh_it); + if (meta_mesh_->is_valid_handle(mhehprop)) { + for (auto mheh : exceptions) { + if (mheh == mhehprop) { + return false; + } + if (meta_mesh_->opposite_halfedge_handle(mheh) == mhehprop) { + return false; + } + } + return true; + } + } + return false; +} + +/*! + * \brief Embedding::InsertPath Inserts a list of base halfedges saved in path into the mesh + * as meta halfedge mheh (called after A* triangulation) + * \param mheh + * \param path + */ +void Embedding::InsertPath(OpenMesh::HalfedgeHandle mheh, + std::vector<OpenMesh::HalfedgeHandle> path) { + if (path.empty()) { + qDebug() << "Couldn't insert empty path"; + } else { + auto opp = meta_mesh_->opposite_halfedge_handle(mheh); + meta_mesh_->property(mhe_connection_, mheh) = path.front(); + meta_mesh_->property(mhe_connection_, opp) = + base_mesh_->opposite_halfedge_handle(path.back()); + auto prev = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + while (!path.empty()) { + auto curr = path.back(); + base_mesh_->property(next_heh_, curr) = prev; + base_mesh_->property(bhe_connection_, curr) = mheh; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(curr)) = opp; + path.pop_back(); + if (base_mesh_->is_valid_handle(prev)) { + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(prev)) + = base_mesh_->opposite_halfedge_handle(curr); + } + prev = curr; + } + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(prev)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } +} + +/*! + * \brief Embedding::FindA_StarStartingHalfedges Find A* Starting halfedges through a series + * of halfedge pointer operations. This is more complicated than it first sounds because it has + * to handle an initial triangulation where edges are traced in an arbitrary order and some edges + * may already be traced while others arent. Traces starting in the wrong sectors will fail so + * the starting halfedges need to be right. + * \param mheh the halfedge to be traced. + * \param verbose + * \return Two starting halfedges for use in A* tracing. + */ +std::pair<OpenMesh::HalfedgeHandle, OpenMesh::HalfedgeHandle> +Embedding::FindA_StarStartingHalfedges(OpenMesh::HalfedgeHandle mheh, bool verbose) { + std::pair<OpenMesh::HalfedgeHandle, OpenMesh::HalfedgeHandle> output; + //ProcessNeighbors(meta_mesh_->edge_handle(mheh)); + auto mvh0 = meta_mesh_->from_vertex_handle(mheh); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh); + output.first = base_mesh_->halfedge_handle(meta_mesh_->property(mv_connection_, mvh0)); + output.second = base_mesh_->halfedge_handle(meta_mesh_->property(mv_connection_, mvh1)); + assert(mvh0 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.first))); + assert(mvh1 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.second))); + auto iter = meta_mesh_->opposite_halfedge_handle(meta_mesh_->prev_halfedge_handle(mheh)); + while (iter != mheh) { + assert(meta_mesh_->from_vertex_handle(iter) == mvh0); + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, iter))) { + if (base_mesh_->property(bhe_connection_, (meta_mesh_->property(mhe_connection_, iter))) + == iter) { + output.first = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle( + meta_mesh_->property(mhe_connection_, iter))); + assert(mvh0 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.first))); + goto endloop1; + } + } + auto bvhx = meta_mesh_->property(mv_connection_, meta_mesh_->to_vertex_handle(iter)); + for (auto bhehiter : base_mesh_->voh_range(meta_mesh_->property(mv_connection_, + meta_mesh_->from_vertex_handle(iter)))) { + if (base_mesh_->to_vertex_handle(bhehiter) == bvhx) { + output.first = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle( + bhehiter)); + assert(mvh0 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.first))); + goto endloop1; + } + } + iter = meta_mesh_->opposite_halfedge_handle(meta_mesh_->prev_halfedge_handle(iter)); + } + endloop1: + + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, output.first)) || + (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(output.first))) + && base_mesh_->property(bv_connection_, base_mesh_->to_vertex_handle(output.first)) + != mvh1)) { + SplitBaseHe(base_mesh_->next_halfedge_handle(output.first)); + output.first = base_mesh_->opposite_halfedge_handle( + base_mesh_->prev_halfedge_handle(output.first)); + assert(mvh0 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.first))); + } + + auto opp = meta_mesh_->opposite_halfedge_handle(mheh); + iter = meta_mesh_->opposite_halfedge_handle(meta_mesh_->prev_halfedge_handle(opp)); + while (iter != opp) { + assert(meta_mesh_->from_vertex_handle(iter) == mvh1); + if (base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, iter))) { + if (base_mesh_->property(bhe_connection_, (meta_mesh_->property(mhe_connection_, iter))) + == iter) { + output.second = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle( + meta_mesh_->property(mhe_connection_, iter))); + goto endloop2; + } + } + auto bvhx = meta_mesh_->property(mv_connection_, meta_mesh_->to_vertex_handle(iter)); + for (auto bhehiter : base_mesh_->voh_range(meta_mesh_->property(mv_connection_, + meta_mesh_->from_vertex_handle(iter)))) { + if (base_mesh_->to_vertex_handle(bhehiter) == bvhx) { + output.second = base_mesh_->next_halfedge_handle(base_mesh_->opposite_halfedge_handle( + bhehiter)); + goto endloop2; + } + } + iter = meta_mesh_->opposite_halfedge_handle(meta_mesh_->prev_halfedge_handle(iter)); + } + endloop2: + + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, output.second)) || + (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(output.second))) + && base_mesh_->property(bv_connection_, base_mesh_->to_vertex_handle(output.second)) + != mvh0)) { + assert(mvh0 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.first))); + SplitBaseHe(base_mesh_->next_halfedge_handle(output.second)); + assert(mvh0 == base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(output.first))); + output.second = base_mesh_->opposite_halfedge_handle( + base_mesh_->prev_halfedge_handle(output.second)); + } + + // Make sure the chosen starting halfedges start at the given vertices and are still empty. + assert(base_mesh_->is_valid_handle(output.first)); + assert(base_mesh_->is_valid_handle(output.second)); + assert(!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, output.first))); + assert(!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, output.second))); + auto bvcp1 = base_mesh_->property(bv_connection_, base_mesh_->from_vertex_handle(output.first)); + auto bvcp2 = base_mesh_->property(bv_connection_, base_mesh_->from_vertex_handle(output.second)); + assert(bvcp1 == mvh0); + assert(bvcp2 == mvh1); + + if (verbose + && meta_mesh_->from_vertex_handle(mheh) == meta_mesh_->to_vertex_handle(mheh)) { + qDebug() << "Trace from mvertex " << meta_mesh_->from_vertex_handle(mheh).idx() + << " to itself using starting halfedges " << output.first.idx() + << " and " << output.second.idx(); + } + + return output; +} + +/*! + * \brief Embedding::MarkVertex Marks a meta vertex in black while coloring its + * vv_neighborhood white, making it easy to see for visual debugging. + * \param bvh + */ +void Embedding::MarkVertex(OpenMesh::VertexHandle bvh) { + qDebug() << "Marking vertex: " << bvh.idx() << "in white."; + auto vertex_colors_pph = base_mesh_->vertex_colors_pph(); + base_mesh_->property(vertex_colors_pph, bvh) = ACG::Vec4f(0.0f, 0.0f, 0.0f, 1.0f); + for (auto vvh : base_mesh_->vv_range(bvh)) { + if (base_mesh_->property(vertex_colors_pph, vvh) != ACG::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)) { + base_mesh_->property(vertex_colors_pph, vvh) = ACG::Vec4f(1.0f, 1.0f, 1.0f, 1.0f); + } + } +} + +/*! + * \brief Embedding::AddMetaEdge Adds a meta edge (it will still need to be traced + * into the base mesh) + * \param mheh0 the previous edge of mheh + * \param mheh1 the previous edge of the opposite edge of mheh + * \return a halfedge of the new edge, specifically the next halfedge of mheh0 + */ +OpenMesh::HalfedgeHandle Embedding::AddMetaEdge(OpenMesh::HalfedgeHandle mheh0, + OpenMesh::HalfedgeHandle mheh1) { + auto mhehnew0 = meta_mesh_->new_edge(meta_mesh_->to_vertex_handle(mheh0), + meta_mesh_->to_vertex_handle(mheh1)); + auto mhehnew1 = meta_mesh_->opposite_halfedge_handle(mhehnew0); + auto fnew0 = meta_mesh_->face_handle(mheh0); + auto fnew1(meta_mesh_->new_face()); + auto next0 = meta_mesh_->next_halfedge_handle(mheh0); + auto next1 = meta_mesh_->next_halfedge_handle(mheh1); + auto mvh0 = meta_mesh_->to_vertex_handle(mheh0); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh1); + + meta_mesh_->set_next_halfedge_handle(mheh0, mhehnew0); + meta_mesh_->set_next_halfedge_handle(mheh1, mhehnew1); + meta_mesh_->set_next_halfedge_handle(mhehnew0, next1); + meta_mesh_->set_next_halfedge_handle(mhehnew1, next0); + + meta_mesh_->set_vertex_handle(mhehnew0, mvh1); + meta_mesh_->set_vertex_handle(mhehnew1, mvh0); + + meta_mesh_->set_halfedge_handle(fnew0, mhehnew0); + meta_mesh_->set_halfedge_handle(fnew1, mhehnew1); + + meta_mesh_->set_face_handle(mhehnew0, fnew0); + meta_mesh_->set_face_handle(mhehnew1, fnew1); + + auto iter = meta_mesh_->next_halfedge_handle(mhehnew0); + while (iter != mhehnew0) { + meta_mesh_->set_face_handle(iter, fnew0); + iter = meta_mesh_->next_halfedge_handle(iter); + } + iter = meta_mesh_->next_halfedge_handle(mhehnew1); + while (iter != mhehnew1) { + meta_mesh_->set_face_handle(iter, fnew1); + iter = meta_mesh_->next_halfedge_handle(iter); + } + + // Properties + meta_mesh_->property(mhe_connection_, mhehnew0) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + meta_mesh_->property(mhe_connection_, mhehnew1) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + meta_mesh_->property(mhe_border_, mhehnew0) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + meta_mesh_->property(mhe_border_, mhehnew1) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + + return mhehnew0; +} + +/*! + * \brief Embedding::Rotate + * \param meh + */ +void Embedding::Rotate(OpenMesh::EdgeHandle meh) { + if (!IsRotationOkay(meh)) { + qDebug() << "Meta edge " << meh.idx() << " cannot be rotated"; + } else { + MetaRotation(meh); + auto mheh = meta_mesh_->halfedge_handle(meh, 0); + RemoveMetaEdgeFromBaseMesh(mheh); + Trace(mheh, true, false); + } +} + +/*! + * \brief Embedding::IsRotationOkay + * \param meh + * \return whether meh can be rotateds + */ +bool Embedding::IsRotationOkay(OpenMesh::EdgeHandle meh) { + auto mheh0 = meta_mesh_->halfedge_handle(meh, 0); + auto mheh1 = meta_mesh_->halfedge_handle(meh, 1); + + // existence check + if (!meta_mesh_->is_valid_handle(meh) + || !meta_mesh_->is_valid_handle(mheh0) + || !meta_mesh_->is_valid_handle(mheh1) + || !base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh0)) + || !base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh1))) { + return false; + } + + // boundary check + if (meta_mesh_->is_boundary(meta_mesh_->halfedge_handle(meh, 0)) + || meta_mesh_->is_boundary(meta_mesh_->halfedge_handle(meh, 1))) { + return false; + } + + // loop check + return (meta_mesh_->next_halfedge_handle(mheh0) != mheh1 + && meta_mesh_->next_halfedge_handle(mheh1) != mheh0); +} + +// Flips an edge in the meta mesh, but not in the underlying base mesh +void Embedding::MetaRotation(OpenMesh::EdgeHandle meh) { + //qDebug() << "MetaFlip function called"; + assert(IsRotationOkay(meh)); + auto mheh0 = meta_mesh_->halfedge_handle(meh, 0); + auto mheh1 = meta_mesh_->halfedge_handle(meh, 1); + auto mhehprev0 = meta_mesh_->prev_halfedge_handle(mheh0); + auto mhehprev1 = meta_mesh_->prev_halfedge_handle(mheh1); + auto mhehnext0 = meta_mesh_->next_halfedge_handle(mheh0); + auto mhehnext1 = meta_mesh_->next_halfedge_handle(mheh1); + auto mhehnextnext0 = meta_mesh_->next_halfedge_handle(mhehnext0); + auto mhehnextnext1 = meta_mesh_->next_halfedge_handle(mhehnext1); + auto fh0 = meta_mesh_->face_handle(mheh0); + auto fh1 = meta_mesh_->face_handle(mheh1); + auto mvh0 = meta_mesh_->to_vertex_handle(mheh0); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh1); + auto mvh01 = meta_mesh_->to_vertex_handle(mhehnext0); + auto mvh11 = meta_mesh_->to_vertex_handle(mhehnext1); + + // Flip next_halfedge connections + meta_mesh_->set_next_halfedge_handle(mhehprev0, mhehnext1); + meta_mesh_->set_next_halfedge_handle(mhehprev1, mhehnext0); + meta_mesh_->set_next_halfedge_handle(mhehnext0, mheh0); + meta_mesh_->set_next_halfedge_handle(mhehnext1, mheh1); + meta_mesh_->set_next_halfedge_handle(mheh0, mhehnextnext1); + meta_mesh_->set_next_halfedge_handle(mheh1, mhehnextnext0); + + // Ensure correct face->heh connections + meta_mesh_->set_halfedge_handle(fh0, mheh0); + meta_mesh_->set_halfedge_handle(fh1, mheh1); + + // Ensure correct heh->face connections + meta_mesh_->set_face_handle(mhehprev1, fh0); + meta_mesh_->set_face_handle(mhehprev0, fh1); + meta_mesh_->set_face_handle(mhehnext0, fh0); + meta_mesh_->set_face_handle(mhehnext1, fh1); + + // Ensure correct heh->vertex connections + meta_mesh_->set_vertex_handle(mheh0, mvh11); + meta_mesh_->set_vertex_handle(mheh1, mvh01); + + // Ensure correct vertex->heh connections while preserving boundary rule + if (meta_mesh_->halfedge_handle(mvh0) == mheh1) { + meta_mesh_->set_halfedge_handle(mvh0, mhehnext0); + } + if (meta_mesh_->halfedge_handle(mvh1) == mheh0) { + meta_mesh_->set_halfedge_handle(mvh1, mhehnext1); + } + + // Check for correctness + auto mheh03 = meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle( + meta_mesh_->next_halfedge_handle(mheh0))); + auto mheh13 = meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle( + meta_mesh_->next_halfedge_handle(mheh1))); + assert(mheh0 == mheh03); + assert(mheh1 == mheh13); + assert(meta_mesh_->from_vertex_handle(mheh0) == meta_mesh_->to_vertex_handle(mhehnext0)); + assert(meta_mesh_->from_vertex_handle(mheh1) == meta_mesh_->to_vertex_handle(mhehnext1)); + assert(meta_mesh_->to_vertex_handle(mheh0) == meta_mesh_->to_vertex_handle(mhehnext1)); + assert(meta_mesh_->to_vertex_handle(mheh1) == meta_mesh_->to_vertex_handle(mhehnext0)); +} + +/*! + * \brief Embedding::RemoveMetaEdgeFromBaseMesh + * \param mheh meta halfedge to be removed from the basemesh (it stays in the meta mesh) + * \param cleanup + */ +void Embedding::RemoveMetaEdgeFromBaseMesh(OpenMesh::HalfedgeHandle mheh, bool cleanup) { + assert(meta_mesh_->is_valid_handle(mheh)); + assert(base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh))); + auto halfedges = GetBaseHalfedges(mheh); + assert(base_mesh_->property(bhe_connection_, halfedges.front()) == mheh); + auto mheho = meta_mesh_->opposite_halfedge_handle(mheh); + assert(base_mesh_->property(bhe_connection_, + base_mesh_->opposite_halfedge_handle(halfedges.back())) == mheho); + meta_mesh_->property(mhe_connection_, mheh) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + meta_mesh_->property(mhe_connection_, mheho) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + + for (auto bheh : halfedges) { + base_mesh_->property(bhe_connection_, bheh) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(bheh)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, bheh) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(bheh)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + + // Cleanup + if (cleanup) { + for (auto bheh : halfedges) { + std::queue<OpenMesh::VertexHandle> cleanupqueue; + for (auto bvhit : base_mesh_->vv_range(base_mesh_->to_vertex_handle(bheh))) { + if (!base_mesh_->status(bvhit).deleted() + && base_mesh_->is_valid_handle(base_mesh_->property(bsplithandle_, bvhit))) { + cleanupqueue.push(bvhit); + } + } + while (!cleanupqueue.empty()) { + if (base_mesh_->is_valid_handle(cleanupqueue.front()) + && !base_mesh_->status(cleanupqueue.front()).deleted()) { + ConditionalCollapse(cleanupqueue.front()); + } + cleanupqueue.pop(); + } + } + } +} + +/*! + * \brief Embedding::RemoveMetaEdgeFromMetaMesh + * \param mheh meta halfedge to be removed, from the meta mesh only (stays in the base mesh + * if not removed from there first which it usually should be) + */ +void Embedding::RemoveMetaEdgeFromMetaMesh(OpenMesh::HalfedgeHandle mheh) { + auto mheho = meta_mesh_->opposite_halfedge_handle(mheh); + auto mhehn = meta_mesh_->next_halfedge_handle(mheh); + auto mhehp = meta_mesh_->prev_halfedge_handle(mheh); + auto mhehon = meta_mesh_->next_halfedge_handle(mheho); + auto mhehop = meta_mesh_->prev_halfedge_handle(mheho); + auto mvh0 = meta_mesh_->from_vertex_handle(mheh); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh); + auto mfh0 = meta_mesh_->face_handle(mheh); + auto mfh1 = meta_mesh_->face_handle(mheho); + + // back + if (mhehp != mheho) { + // he - he + meta_mesh_->set_next_halfedge_handle(mhehp, mhehon); + // v - he + if (meta_mesh_->halfedge_handle(mvh0) == mheh) { + meta_mesh_->set_halfedge_handle(mvh0, mhehon); + } + } else { + assert(base_mesh_->is_valid_handle(meta_mesh_->property(mv_connection_, mvh0))); + base_mesh_->property(bv_connection_, meta_mesh_->property(mv_connection_, mvh0)) + = OpenMesh::PolyConnectivity::InvalidVertexHandle; + meta_mesh_->set_isolated(mvh0); + meta_mesh_->status(mvh0).set_deleted(true); + } + // front + if (mhehn != mheho) { + // he - he + meta_mesh_->set_next_halfedge_handle(mhehop, mhehn); + // v - he + if (meta_mesh_->halfedge_handle(mvh1) == mheho) { + meta_mesh_->set_halfedge_handle(mvh1, mhehn); + } + } else { + assert(base_mesh_->is_valid_handle(meta_mesh_->property(mv_connection_, mvh1))); + base_mesh_->property(bv_connection_, meta_mesh_->property(mv_connection_, mvh1)) + = OpenMesh::PolyConnectivity::InvalidVertexHandle; + meta_mesh_->set_isolated(mvh1); + meta_mesh_->status(mvh1).set_deleted(true); + } + if (meta_mesh_->is_valid_handle(mfh0)) { + // f - he + meta_mesh_->set_halfedge_handle(mfh0, mhehn); + // he - f + auto mhehiter = mhehn; + do { + meta_mesh_->set_face_handle(mhehiter, mfh0); + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } while (mhehn != mhehiter); + } + // delete stuff + if (meta_mesh_->is_valid_handle(mfh1) && mfh1 != mfh0) { + meta_mesh_->status(mfh1).set_deleted(true); + } + meta_mesh_->status(meta_mesh_->edge_handle(mheh)).set_deleted(true); + if (meta_mesh_->has_halfedge_status()) { + meta_mesh_->status(mheh).set_deleted(true); + meta_mesh_->status(mheho).set_deleted(true); + } +} + +/*! + * \brief Embedding::MiddleBvh + * \param meh + * \return the base vertex at the middle of meh (roughly since the embedding is discrete) + */ +OpenMesh::VertexHandle Embedding::MiddleBvh(OpenMesh::EdgeHandle meh) { + auto bhehleft = meta_mesh_->property(mhe_connection_, meta_mesh_->halfedge_handle(meh, 0)); + auto bhehright = meta_mesh_->property(mhe_connection_, meta_mesh_->halfedge_handle(meh, 1)); + if (bhehleft == base_mesh_->opposite_halfedge_handle(bhehright)) { + return base_mesh_->to_vertex_handle(SplitBaseHe(bhehleft)); + } + double leftdist = base_mesh_->calc_edge_length(bhehleft); + double rightdist = base_mesh_->calc_edge_length(bhehright); + + while(base_mesh_->to_vertex_handle(bhehleft) + != base_mesh_->to_vertex_handle(bhehright)) { + if (leftdist < rightdist) { + bhehleft = base_mesh_->property(next_heh_, bhehleft); + leftdist += base_mesh_->calc_edge_length(bhehleft); + } else { + bhehright = base_mesh_->property(next_heh_, bhehright); + rightdist += base_mesh_->calc_edge_length(bhehright); + } + } + return base_mesh_->to_vertex_handle(bhehleft); +} + +/*! + * \brief Embedding::MiddleBvh + * \param mheh + * \return the base vertex at the middle of mheh (roughly since the embedding is discrete) + */ +OpenMesh::VertexHandle Embedding::MiddleBvh(OpenMesh::HalfedgeHandle mheh) { + return MiddleBvh(meta_mesh_->edge_handle(mheh)); +} + +/*! + * \brief Embedding::EdgeSplit splits meh at bvh + * \param meh + * \param bvh + * \param verbose + */ +void Embedding::EdgeSplit(OpenMesh::EdgeHandle meh, OpenMesh::VertexHandle bvh, bool verbose) { + assert(meta_mesh_->is_valid_handle(meh)); + if (!base_mesh_->is_valid_handle(bvh)) { + bvh = MiddleBvh(meh); + } + + if (verbose) { + qDebug() << "Split the edge in the meta mesh"; + } + MetaEdgeSplit(meh, bvh); + auto mvh = base_mesh_->property(bv_connection_, bvh); + auto start = meta_mesh_->halfedge_handle(mvh); + auto iter = start; + if (verbose) { + qDebug() << "Trace the new edges in the base mesh"; + } + do { + if (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, iter))) { + Trace(iter); + if (debug_hard_stop_) return; + } + iter = meta_mesh_->opposite_halfedge_handle(meta_mesh_->prev_halfedge_handle(iter)); + } while (iter != start); +} + +/*! + * \brief Embedding::MetaEdgeSplit the part of the split performed on the meta mesh + * \param meh + * \param bvh + */ +void Embedding::MetaEdgeSplit(OpenMesh::EdgeHandle meh, OpenMesh::VertexHandle bvh) { + OpenMesh::HalfedgeHandle mhehb; + auto mheha = meta_mesh_->halfedge_handle(meh, 0); + auto mvh1 = meta_mesh_->to_vertex_handle(mheha); + + // Add bvh into the meta mesh + auto mvhnew = meta_mesh_->add_vertex(base_mesh_->point(bvh)); + + // //////////////// + // Connect mhehb + mhehb = meta_mesh_->new_edge(mvhnew, mvh1); + + // Boundary properties: + if (meta_mesh_->is_boundary(mheha)) { + meta_mesh_->set_boundary(mhehb); + } + if (meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheha))) { + meta_mesh_->set_boundary(meta_mesh_->opposite_halfedge_handle(mhehb)); + } + + // he - he + if (meta_mesh_->next_halfedge_handle(mheha) + == meta_mesh_->opposite_halfedge_handle(mheha)) { + meta_mesh_->set_next_halfedge_handle(mhehb, meta_mesh_->opposite_halfedge_handle(mhehb)); + } else { + meta_mesh_->set_next_halfedge_handle(mhehb, meta_mesh_->next_halfedge_handle(mheha)); + meta_mesh_->set_next_halfedge_handle(meta_mesh_->prev_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheha)), + meta_mesh_->opposite_halfedge_handle(mhehb)); + } + // he - v + meta_mesh_->set_vertex_handle(mhehb, mvh1); + meta_mesh_->set_vertex_handle(meta_mesh_->opposite_halfedge_handle(mhehb), mvhnew); + // v - he + if (meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheha))) { + meta_mesh_->set_halfedge_handle(mvhnew, meta_mesh_->opposite_halfedge_handle(mheha)); + } else { + meta_mesh_->set_halfedge_handle(mvhnew, mhehb); + } + if (meta_mesh_->halfedge_handle(mvh1) == meta_mesh_->opposite_halfedge_handle(mheha)) { + meta_mesh_->set_halfedge_handle(mvh1, meta_mesh_->opposite_halfedge_handle(mhehb)); + } + // he - f + meta_mesh_->set_face_handle(mhehb, meta_mesh_->face_handle(mheha)); + meta_mesh_->set_face_handle(meta_mesh_->opposite_halfedge_handle(mhehb), + meta_mesh_->face_handle(meta_mesh_->opposite_halfedge_handle(mheha))); + // Rewire mheha + // he - he + meta_mesh_->set_next_halfedge_handle(mheha, mhehb); + meta_mesh_->set_next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mhehb), + meta_mesh_->opposite_halfedge_handle(mheha)); + // he - v + meta_mesh_->set_vertex_handle(mheha, mvhnew); + + // /////////////////// + // Properties + // vertex properties + meta_mesh_->property(mv_connection_, mvhnew) = bvh; + base_mesh_->property(bv_connection_, bvh) = mvhnew; + // halfedge properties + for (auto bvoheh : base_mesh_->voh_range(bvh)) { + if (base_mesh_->property(bhe_connection_, bvoheh) + == meta_mesh_->opposite_halfedge_handle(mheha)) { + meta_mesh_->property(mhe_connection_, meta_mesh_->opposite_halfedge_handle(mheha)) + = bvoheh; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(bvoheh)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + if (base_mesh_->property(bhe_connection_, bvoheh) == mheha) { + meta_mesh_->property(mhe_connection_, mhehb) = bvoheh; + base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(bvoheh)) + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + } + OverwriteHalfedgeConnection(mhehb, meta_mesh_->property(mhe_connection_, mhehb)); + + // Triangulate the two faces adjacent to the new vertex. + auto start = mheha; + auto iter = meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle( + meta_mesh_->next_halfedge_handle(start))); + while (iter != start) { + AddMetaEdge(start, meta_mesh_->prev_halfedge_handle(iter)); + iter = meta_mesh_->next_halfedge_handle(iter); + } + start = meta_mesh_->opposite_halfedge_handle(mhehb); + iter = meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle( + meta_mesh_->next_halfedge_handle(start))); + while (iter != start) { + if (meta_mesh_->is_valid_handle(meta_mesh_->face_handle(iter))) { + AddMetaEdge(start, meta_mesh_->prev_halfedge_handle(iter)); + } + iter = meta_mesh_->next_halfedge_handle(iter); + } +} + +/*! + * \brief Embedding::OverwriteHalfedgeConnection + * Overwrites the bhe_connection_ parameters of an embedded halfedge starting at bheh + * to correspond to meta halfedge mheh. + * This is useful for splits since it saves us two Trace calls. + * \param mheh + * \param bheh + */ +void Embedding::OverwriteHalfedgeConnection(OpenMesh::HalfedgeHandle mheh, + OpenMesh::HalfedgeHandle bheh) { + assert(base_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->from_vertex_handle(bheh)))); + assert(base_mesh_->property(bv_connection_, base_mesh_->from_vertex_handle(bheh)) + == meta_mesh_->from_vertex_handle(mheh)); + meta_mesh_->property(mhe_connection_, mheh) = bheh; + base_mesh_->property(bhe_connection_, bheh) = mheh; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(bheh)) + = meta_mesh_->opposite_halfedge_handle(mheh); + assert(base_mesh_->property(next_heh_, base_mesh_->opposite_halfedge_handle(bheh)) + == OpenMesh::PolyConnectivity::InvalidHalfedgeHandle); + while (!meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, + base_mesh_->to_vertex_handle(bheh)))) { + bheh = base_mesh_->property(next_heh_, bheh); + base_mesh_->property(bhe_connection_, bheh) = mheh; + base_mesh_->property(bhe_connection_, base_mesh_->opposite_halfedge_handle(bheh)) + = meta_mesh_->opposite_halfedge_handle(mheh); + } + assert(base_mesh_->property(next_heh_, bheh) + == OpenMesh::PolyConnectivity::InvalidHalfedgeHandle); + meta_mesh_->property(mhe_connection_, meta_mesh_->opposite_halfedge_handle(mheh)) + = base_mesh_->opposite_halfedge_handle(bheh); + // Ensure connectivity + assert(base_mesh_->property(bv_connection_, base_mesh_->from_vertex_handle( + meta_mesh_->property(mhe_connection_, mheh))) + == meta_mesh_->from_vertex_handle(mheh)); + assert(base_mesh_->property(bv_connection_, base_mesh_->from_vertex_handle( + meta_mesh_->property(mhe_connection_, meta_mesh_->opposite_halfedge_handle(mheh)))) + == meta_mesh_->from_vertex_handle(meta_mesh_->opposite_halfedge_handle(mheh))); +} + +/*! + * \brief Embedding::FaceSplit Splits the meta face of mheh at base vertex bvhs + * \param bvh + * \param mheh + * \param verbose + */ +void Embedding::FaceSplit(OpenMesh::VertexHandle bvh, OpenMesh::HalfedgeHandle mheh, + bool verbose) { + assert(!IsSectorBorder(bvh)); + if (!meta_mesh_->is_valid_handle(mheh)) { + mheh = FindFaceHalfedge(bvh); + } + if (verbose) { + qDebug() << "Meta Face split"; + } + MetaFaceSplit(mheh, bvh); + if (verbose) { + qDebug() << "Vertex processing"; + } + ProcessVertex(bvh); + auto mvh = base_mesh_->property(bv_connection_, bvh); + if (verbose) { + qDebug() << "Face split loop"; + } + for (auto mvoheh : meta_mesh_->voh_range(mvh)) { + if (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mvoheh))) { + Trace(mvoheh); + if (debug_hard_stop_) return; + } + } +} + +/*! + * \brief Embedding::FindFaceHalfedge + * \param bvh + * \return a halfedge of the meta face bvh lies on. or InvalidHandle if bvh does not + * lie on a meta face. + */ +OpenMesh::HalfedgeHandle Embedding::FindFaceHalfedge(OpenMesh::VertexHandle bvh) { + std::queue<OpenMesh::VertexHandle> queue; + OpenMesh::VPropHandleT<bool> marked; + base_mesh_->add_property(marked, "Marked search traversal."); + queue.push(bvh); + while (!queue.empty()) { + auto vh = queue.front(); + queue.pop(); + if (!base_mesh_->property(marked, vh)) { + base_mesh_->property(marked, vh) = true; + for (auto bvoheh : base_mesh_->voh_range(vh)) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, bvoheh))) { + auto iter = base_mesh_->opposite_halfedge_handle( + base_mesh_->prev_halfedge_handle(bvoheh)); + while (!meta_mesh_->is_valid_handle(base_mesh_->property(bhe_connection_, iter))) { + if (base_mesh_->property(marked, base_mesh_->to_vertex_handle(iter))) { + base_mesh_->remove_property(marked); + return base_mesh_->property(bhe_connection_, bvoheh); + } + iter = base_mesh_->opposite_halfedge_handle( + base_mesh_->prev_halfedge_handle(iter)); + } + } + if (!base_mesh_->property(marked, base_mesh_->to_vertex_handle(bvoheh))) { + queue.push(base_mesh_->to_vertex_handle(bvoheh)); + } + } + } + } + base_mesh_->remove_property(marked); + return OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; +} + +/*! + * \brief Embedding::MetaFaceSplit meta mesh part of the face split + * \param mheh + * \param bvh + */ +void Embedding::MetaFaceSplit(OpenMesh::HalfedgeHandle mheh, OpenMesh::VertexHandle bvh) { + // Add bvh into the meta mesh + // try highlevel method, implement low level if this does not work + meta_mesh_->split(meta_mesh_->face_handle(mheh), base_mesh_->point(bvh)); + + // add properties + auto mvh = meta_mesh_->to_vertex_handle(meta_mesh_->next_halfedge_handle(mheh)); + meta_mesh_->property(mv_connection_, mvh) = bvh; + base_mesh_->property(bv_connection_, bvh) = mvh; +} + +/*! + * \brief Embedding::Relocate + * Relocates mvh to bvh + * The lowlevel method is better for most purposes, but less robust to extreme edge cases + * If you are relocating a vertex with edges that cannot be traced properly regardless of order + * eg. a vertex with 3 self-edges in a genus 4 mesh, do not use the low level method + * the high level method is a concatenation of a split and a collapse and should thus always work + * but it doesnt clean the base mesh up nearly as well. + * \param mvh + * \param bvh + * \param verbose + * \param lowlevel enabled by default, but disable it where this fails. The low level method still + * needs a bit of work and I might not have enough time to make it handle all edge cases (some + * edge cases will remain impossible regardless) + */ +void Embedding::Relocate(OpenMesh::VertexHandle mvh, OpenMesh::VertexHandle bvh, bool verbose, + bool lowlevel) { + if (!base_mesh_->is_valid_handle(bvh)) { + return; + } + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh)) + && !lowlevel) { + return; + } + // Disallow relocation of boundary vertices away from their boundary. + if (meta_mesh_->is_boundary(mvh)) { + auto mehb = GetMetaEdge(bvh); + if (!base_mesh_->is_boundary(bvh)) { + if (verbose) { + qDebug() << "Cannot relocate a boundary vertex away from its boundary."; + } + return; + } else if (mehb != meta_mesh_->edge_handle(meta_mesh_->halfedge_handle(mvh)) + && mehb != meta_mesh_->edge_handle( + meta_mesh_->prev_halfedge_handle(meta_mesh_->halfedge_handle(mvh)))) { + return; + } + } + + if (lowlevel) { + LowLevelRelocate(mvh, bvh, verbose); + } else { + CompositeRelocate(mvh, bvh, verbose); + } +} + +/*! + * \brief Embedding::LowLevelRelocate + * Relocation as a lower level function than CompositeRelocate + * Fails if all edges around mvh are self-edges (eg. genus 4 object with 1 vertex) + * \param mvh + * \param bvh + * \param verbose + */ +void Embedding::LowLevelRelocate(OpenMesh::VertexHandle mvh, OpenMesh::VertexHandle bvh, + bool verbose) { + // This method does not do edge splits, relocate only inside the patch. + auto meh = GetMetaEdge(bvh); + if (IsSectorBorder(bvh) + && meta_mesh_->is_valid_handle(meh) + && !(meta_mesh_->to_vertex_handle(meta_mesh_->halfedge_handle(meh,0)) == mvh + || meta_mesh_->to_vertex_handle(meta_mesh_->halfedge_handle(meh,1)) == mvh)) { + if (verbose) { + qDebug() << "Cannot relocate meta vertex " << mvh.idx() << " to base vertex " + << bvh.idx() << " since it doesn't lie in an incident face"; + } + return; + } + + // Ensure that bvh lies in the patch around mvh + auto mheh = FindFaceHalfedge(bvh); + auto mhehiter = mheh; + bool correct = false; + do { + if (meta_mesh_->to_vertex_handle(mhehiter) == mvh) { + correct = true; + mheh = mhehiter; + } else { + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } + } while (mhehiter!=mheh); + if (!correct) { + if (verbose) { + qDebug() << "Cannot relocate meta vertex " << mvh.idx() << " to base vertex " + << bvh.idx() << " since it doesn't lie in an incident face"; + } + return; + } + + // Do the relocation + // Relocate mvh to bvh + base_mesh_->property(bv_connection_, meta_mesh_->property(mv_connection_, mvh)) + = OpenMesh::PolyConnectivity::InvalidVertexHandle; + meta_mesh_->property(mv_connection_, mvh) = bvh; + base_mesh_->property(bv_connection_, bvh) = mvh; + // Delete the edges inside the patch: + for (auto moh : meta_mesh_->voh_range(mvh)) { + if (!(meta_mesh_->from_vertex_handle(moh) == meta_mesh_->to_vertex_handle(moh)) + || base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, moh))) { + RemoveMetaEdgeFromBaseMesh(moh); + } + } + // Use this opportunity to clean up the mesh while the patch is empty + CleanUpBaseMesh(); + // Retrace the patch around mvh + // Order can be very important when doing this, as otherwise vertices can be blocked off + // by edges. Order of retracing: + // 1: Boundary edges + // 2: A representative of each connected component in the patch around mvh. + // In most cases there will only be one component + // 3: Self-edges. The representatives of components should wall those off and confine them + // to areas so that they cannot vanish + // 4: All other edges that are non-duplicate + // 5: duplicates + OpenMesh::HPropHandleT<bool> he_selected; + OpenMesh::VPropHandleT<bool> v_selected; + OpenMesh::VPropHandleT<bool> group_selected; + std::queue<OpenMesh::HalfedgeHandle> tracingqueue; + meta_mesh_->add_property(he_selected, "select edges for tracing order"); + meta_mesh_->add_property(group_selected, "select groups for tracing order"); + meta_mesh_->add_property(v_selected, "select vertices for tracing order"); + // 0: initialize properties + meta_mesh_->property(group_selected, mvh) = false; + for (auto moh : meta_mesh_->voh_range(mvh)) { + meta_mesh_->property(he_selected, moh) = false; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = false; + meta_mesh_->property(group_selected, meta_mesh_->to_vertex_handle(moh)) = false; + meta_mesh_->property(v_selected, meta_mesh_->to_vertex_handle(moh)) = false; + } + + // 1: find boundary edges. + for (auto moh : meta_mesh_->voh_range(mvh)) { + meta_mesh_->property(he_selected, moh) = false; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = false; + if (!meta_mesh_->property(he_selected, moh) + && (meta_mesh_->is_boundary(moh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(moh)))) { + tracingqueue.push(moh); + meta_mesh_->property(he_selected, moh) = true; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = true; + meta_mesh_->property(group_selected, meta_mesh_->to_vertex_handle(moh)) = true; + meta_mesh_->property(v_selected, meta_mesh_->to_vertex_handle(moh)) = true; + TraverseGroup(mvh, moh, group_selected); + } + } + + // 2: find group representatives + for (auto moh : meta_mesh_->voh_range(mvh)) { + // non-self-edges + if (meta_mesh_->from_vertex_handle(moh) + != meta_mesh_->to_vertex_handle(moh)) { + // unselected vertex -> new group found -> take the representative and select it + if (!meta_mesh_->property(he_selected, moh) + && !meta_mesh_->property(group_selected, meta_mesh_->to_vertex_handle(moh))) { + tracingqueue.push(moh); + meta_mesh_->property(he_selected, moh) = true; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = true; + meta_mesh_->property(group_selected, meta_mesh_->to_vertex_handle(moh)) = true; + meta_mesh_->property(v_selected, meta_mesh_->to_vertex_handle(moh)) = true; + TraverseGroup(mvh, moh, group_selected); + } + } + } + + // 3: find self-edges + for (auto moh : meta_mesh_->voh_range(mvh)) { + // Also make sure the self-edge is not selected (can happen if boundary self-edge) + if (meta_mesh_->from_vertex_handle(moh) != meta_mesh_->to_vertex_handle(moh) + && !meta_mesh_->property(he_selected, moh)) { + tracingqueue.push(moh); + meta_mesh_->property(he_selected, moh) = true; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = true; + meta_mesh_->property(v_selected, meta_mesh_->to_vertex_handle(moh)) = true; + } + } + + // 4: add the remaining non-duplicate edges to the qeuue + for (auto moh : meta_mesh_->voh_range(mvh)) { + if (!meta_mesh_->property(he_selected, moh) && !meta_mesh_->property(v_selected, + meta_mesh_->to_vertex_handle(moh))) { + tracingqueue.push(moh); + meta_mesh_->property(he_selected, moh) = true; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = true; + meta_mesh_->property(v_selected, meta_mesh_->to_vertex_handle(moh)) = true; + } + } + + // 5: add duplicate edges + for (auto moh : meta_mesh_->voh_range(mvh)) { + if (!meta_mesh_->property(he_selected, moh)) { + tracingqueue.push(moh); + meta_mesh_->property(he_selected, moh) = true; + meta_mesh_->property(he_selected, meta_mesh_->opposite_halfedge_handle(moh)) = true; + } + } + + // Now that the order is set: finally do the tracing + while (!tracingqueue.empty()) { + auto mhehc = tracingqueue.front(); + Trace(mhehc); + tracingqueue.pop(); + if (debug_hard_stop_) { + qDebug() << "Relocation failed"; + base_mesh_->status(bvh).set_selected(true); + return; + } + } + + meta_mesh_->remove_property(he_selected); + meta_mesh_->remove_property(v_selected); + meta_mesh_->remove_property(group_selected); +} + +/*! + * \brief Embedding::TraverseGroup + * Traverses a patch border group and marks its members + * \param mvh + * \param moh + * \param groupselected + */ +void Embedding::TraverseGroup(OpenMesh::VertexHandle mvh, + OpenMesh::HalfedgeHandle moh, + OpenMesh::VPropHandleT<bool> groupselected) { + assert(meta_mesh_->from_vertex_handle(moh) == mvh); + assert(meta_mesh_->property(groupselected, meta_mesh_->to_vertex_handle(moh))); + auto mheh = meta_mesh_->next_halfedge_handle(moh); + while (!meta_mesh_->property(groupselected, meta_mesh_->to_vertex_handle(mheh)) + && meta_mesh_->from_vertex_handle(mheh) != meta_mesh_->to_vertex_handle(mheh)) { + if (meta_mesh_->to_vertex_handle(mheh) == mvh) { + mheh = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh)); + } else { + meta_mesh_->property(groupselected, meta_mesh_->to_vertex_handle(mheh)) = true; + mheh = meta_mesh_->next_halfedge_handle(mheh); + } + } +} + +/*! + * \brief Embedding::CompositeRelocate + * Relocation as a composite of a split and a collapse + * \param mvh + * \param bvh + * \param verbose + */ +void Embedding::CompositeRelocate(OpenMesh::VertexHandle mvh, + OpenMesh::VertexHandle bvh, bool verbose) { + if (!IsSectorBorder(bvh)) { + //qDebug() << "Relocation to Face"; + //qDebug() << "Find Face halfedge"; + auto mheh = FindFaceHalfedge(bvh); + auto mhehiter = mheh; + bool correct = false; + do { + if (meta_mesh_->to_vertex_handle(mhehiter) == mvh) { + correct = true; + mheh = mhehiter; + } else { + mhehiter = meta_mesh_->next_halfedge_handle(mhehiter); + } + } while (mhehiter!=mheh); + if (!correct) { + if (verbose) { + qDebug() << "Cannot relocate meta vertex " << mvh.idx() << " to base vertex " + << bvh.idx() << " since it doesn't lie in an incident face"; + } + return; + } + + //qDebug() << "Relocate: Face Split"; + // Insert the new point + Split(bvh, mheh, verbose); + if (debug_hard_stop_) return; + auto mhehc = meta_mesh_->next_halfedge_handle(mheh); + assert(meta_mesh_->is_valid_handle(mhehc)); + auto mvhn = meta_mesh_->to_vertex_handle(mhehc); + // Swap to ensure the relocated vertex has the same ID as before relocation + //qDebug() << "Relocate: Swap"; + MetaSwap(meta_mesh_->from_vertex_handle(mhehc), meta_mesh_->to_vertex_handle(mhehc)); + if (debug_hard_stop_) return; + //qDebug() << "Relocate: Collapse"; + // Collapse the old point into the new point + Collapse(mhehc, verbose); + if (debug_hard_stop_) return; + for (auto miheh : meta_mesh_->vih_range(mvhn)) { + //qDebug() << "Relocate: Retracing halfedge " << miheh.idx(); + Retrace(miheh); + } + } else { + //qDebug() << "Relocation to Edge"; + auto meh = GetMetaEdge(bvh); + auto mvh0 = meta_mesh_->to_vertex_handle(meta_mesh_->halfedge_handle(meh, 0)); + auto mvh1 = meta_mesh_->to_vertex_handle(meta_mesh_->halfedge_handle(meh, 1)); + if (mvh0 != mvh && mvh1 != mvh) { + if (verbose) { + qDebug() << "Cannot relocate meta vertex " << mvh.idx() << " to base vertex " + << bvh.idx() << " since it doesn't lie in an incident face"; + } + return; + } + if (!meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))) { + //qDebug() << "Relocate: Edge Split"; + // Insert the new point + Split(bvh, OpenMesh::PolyConnectivity::InvalidHalfedgeHandle, verbose); + if (debug_hard_stop_) return; + auto mhehc = meta_mesh_->opposite_halfedge_handle( + meta_mesh_->find_halfedge(base_mesh_->property(bv_connection_, bvh), mvh)); + assert(meta_mesh_->is_valid_handle(mhehc)); + auto mvhn = meta_mesh_->to_vertex_handle(mhehc); + // Swap to ensure the relocated vertex has the same ID as before relocation + //qDebug() << "Relocate: Swap"; + MetaSwap(meta_mesh_->from_vertex_handle(mhehc), meta_mesh_->to_vertex_handle(mhehc)); + if (debug_hard_stop_) return; + //qDebug() << "Relocate: Collapse"; + // Collapse the old point into the new point + Collapse(mhehc, verbose); + if (debug_hard_stop_) return; + for (auto miheh : meta_mesh_->vih_range(mvhn)) { + //qDebug() << "Relocate: Retracing halfedge " << miheh.idx(); + Retrace(miheh); + } + } + //qDebug() << "Relocation finished."; + } +} + +/*! + * \brief Embedding::Split + * Splits at point bvh; if it is on an edge performs an edge split otherwise + * a face split. mheh is an optional parameter for face splits denoting a + * half-edge of the nearest face. Use for optimization, otherwise the nearest + * half-edge is searched for from bvh. + * \param bvh + * \param mheh + * \param verbose + */ +void Embedding::Split(OpenMesh::VertexHandle bvh, OpenMesh::HalfedgeHandle mheh, + bool verbose) { + if (meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvh))) { + qDebug() << "Cannot split at vertex " << bvh.idx() << " since it is already a meta vertex"; + } else if (IsSectorBorder(bvh)) { + //qDebug() << "Edge split"; + EdgeSplit(GetMetaEdge(bvh), bvh, verbose); + } else { + //qDebug() << "Face split"; + FaceSplit(bvh, mheh, verbose); + } +} + +/*! + * \brief Embedding::Collapse + * Collapses the FROM vertex into the TO vertex + * \param mheh + * \param verbose + */ +void Embedding::Collapse(OpenMesh::HalfedgeHandle mheh, bool verbose) { + if (debug_hard_stop_) return; + + //qDebug() << "Calling collapse"; + // Check if collapsing is ok + if (!IsCollapseOkay(mheh, verbose)) { + if (verbose) { + qDebug() << "Cannot collapse halfedge " << mheh.idx(); + } + return; + } + + auto mvh = meta_mesh_->to_vertex_handle(mheh); + auto mheho = meta_mesh_->opposite_halfedge_handle(mheh); + auto mhehn = meta_mesh_->next_halfedge_handle(mheh); + auto mhehp = meta_mesh_->prev_halfedge_handle(mheh); + auto mhehon = meta_mesh_->next_halfedge_handle(mheho); + auto mhehop = meta_mesh_->prev_halfedge_handle(mheho); + auto mvhdel = meta_mesh_->from_vertex_handle(mheh); + auto bvhdel = meta_mesh_->property(mv_connection_, mvhdel); + + if (verbose + && !meta_mesh_->is_boundary(mheh) + && !meta_mesh_->is_boundary(mheho)) { + int ringeuler = OneRingEuler(mvhdel); + if (ringeuler != 1) { + qDebug() << "The patch around the vertex " << mvhdel.idx() << " that is to be collapsed" + " has euler characteristic " << ringeuler << "."; + } + } + + // Two pass tracing + //qDebug() << "First Trace Pass."; + // First pass : trace into the right topological area, bend in the meta mesh + // this step implicitly collapses the meta edge + if (!meta_mesh_->is_boundary(mheh)) { + while (!meta_mesh_->status(mheh).deleted() + && meta_mesh_->prev_halfedge_handle(mheh) != + meta_mesh_->opposite_halfedge_handle(mheh)) { + auto mhehcurr = meta_mesh_->prev_halfedge_handle(mheh); + BendMetaEdgeForwards(mhehcurr); + if (meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mhehcurr)) + || (meta_mesh_->prev_halfedge_handle(mheh) == + meta_mesh_->opposite_halfedge_handle(mheh) + && meta_mesh_->from_vertex_handle(mhehcurr) + != meta_mesh_->to_vertex_handle(mhehcurr))) { + assert(meta_mesh_->prev_halfedge_handle(mheh) == + meta_mesh_->opposite_halfedge_handle(mheh)); + // Remove the A->B edge + RemoveMetaEdgeFromBaseMesh(mheh); + RemoveMetaEdgeFromMetaMesh(mheh); + } + if (meta_mesh_->is_valid_handle(mhehcurr) + && !meta_mesh_->status(mhehcurr).deleted()) { + RemoveMetaEdgeFromBaseMesh(mhehcurr); + if (debug_hard_stop_) return; + Trace(mhehcurr, true, false); + if (debug_hard_stop_) return; + } else { + assert(!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehcurr))); + } + } + } else { + while (!meta_mesh_->status(mheh).deleted() + && meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh)) != mheh) { + auto mhehcurr = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh)); + BendMetaEdgeBackwards(mhehcurr); + if (meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mhehcurr)) + || (meta_mesh_->prev_halfedge_handle(mheh) == + meta_mesh_->opposite_halfedge_handle(mheh) + && meta_mesh_->from_vertex_handle(mhehcurr) + != meta_mesh_->to_vertex_handle(mhehcurr))) { + assert(meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh)) == mheh); + // Remove the A->B edge + RemoveMetaEdgeFromBaseMesh(mheh); + RemoveMetaEdgeFromMetaMesh(mheh); + } + if (meta_mesh_->is_valid_handle(mhehcurr) + && !meta_mesh_->status(mhehcurr).deleted()) { + RemoveMetaEdgeFromBaseMesh(mhehcurr); + if (debug_hard_stop_) return; + Trace(mhehcurr, true, false); + if (debug_hard_stop_) return; + } else { + assert(!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mhehcurr))); + } + } + } + + if (!meta_mesh_->status(mheh).deleted()) { + //qDebug() << "Foo"; + // Remove the A->B edge here if it was encased by a self-edge + RemoveMetaEdgeFromBaseMesh(mheh); + RemoveMetaEdgeFromMetaMesh(mheh); + } + + // Remove 1-Loop (case: self-edge) + if (mhehn == mhehop && mhehon == mheh) { + auto mhehouter = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mhehn)); + assert(meta_mesh_->from_vertex_handle(mhehn) == meta_mesh_->to_vertex_handle(mhehn)); + if (meta_mesh_->is_valid_handle(mhehn) + && !meta_mesh_->status(mhehn).deleted()) { + RemoveSelfLoop(mhehn); + } + if (meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mhehouter)) + == mhehouter) { + RemoveLoop(mhehouter); + Retrace(mhehouter, true, false); + if (debug_hard_stop_) return; + } + } else if (mhehon == mhehp && mhehn == mheho) { + auto mhehouter = meta_mesh_->next_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mhehon)); + assert(meta_mesh_->from_vertex_handle(mhehon) == meta_mesh_->to_vertex_handle(mhehon)); + if (meta_mesh_->is_valid_handle(mhehon) + && !meta_mesh_->status(mhehon).deleted()) { + RemoveSelfLoop(mhehon); + } + if (meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mhehouter)) + == mhehouter) { + RemoveLoop(mhehouter); + Retrace(mhehouter, true, false); + if (debug_hard_stop_) return; + } + } else { + // Remove 2-Loops + if (meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mhehn)) + == mhehn) { + RemoveLoop(mhehn); + } + if (meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mhehop)) + == mhehop) { + RemoveLoop(mhehop); + } + if (meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mhehp)) + == mhehp) { + RemoveLoop(mhehp); + } + if (meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mhehon)) + == mhehon) { + RemoveLoop(mhehon); + } + } + + //qDebug() << "Second Trace Pass."; + // Second pass : Retrace to look nicer + for (auto mhehit : meta_mesh_->voh_range(mvh)) { + if (!meta_mesh_->status(mhehit).deleted()) { + Retrace(mhehit, true, false); + if (debug_hard_stop_) return; + } + } + + // ensure disconnection of the collapsed vertex + assert(!meta_mesh_->is_valid_handle(base_mesh_->property(bv_connection_, bvhdel))); + assert(meta_mesh_->status(mvhdel).deleted()); + + // Sanity tests + for (auto mvoheh : meta_mesh_->voh_range(mvh)) { + assert(!meta_mesh_->status(mvoheh).deleted()); + assert(base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mvoheh))); + assert(base_mesh_->property(bhe_connection_, meta_mesh_->property(mhe_connection_, mvoheh)) + == mvoheh); + } + + //qDebug() << "Collapse finished"; +} + +/*! + * \brief Embedding::IsCollapseOkay + * \param mheh + * \param verbose + * \return whether mheh can be collapsed + */ +bool Embedding::IsCollapseOkay(OpenMesh::HalfedgeHandle mheh, bool verbose) { + if (!meta_mesh_->is_valid_handle(meta_mesh_->edge_handle(mheh))) + { + if (verbose) { + qDebug() << "Invalid halfedge handle"; + } + return false; + } + // is edge already deleted? + if (meta_mesh_->status(meta_mesh_->edge_handle(mheh)).deleted()) + { + if (verbose) { + qDebug() << "Deleted halfedge"; + } + return false; + } + + // is a boundary vertex being collapsed along a non-boundary edge? + if (meta_mesh_->is_boundary(meta_mesh_->from_vertex_handle(mheh)) + && !meta_mesh_->is_boundary(mheh) + && !meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh))) { + if (verbose) { + qDebug() << "Cannot collapse boundary into non-boundary"; + } + return false; + } + + if (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh))) + { + if (verbose) { + qDebug() << "Connected to an invalid halfedge, this should NOT happen"; + } + return false; + } + if (!base_mesh_->is_valid_handle(meta_mesh_->property(mhe_connection_, mheh))) + { + if (verbose) { + qDebug() << "Unconnected halfedge, this should NOT happen"; + } + return false; + } + + // Don't allow collapses when the number of halfedges of the face is equal to the + // total number of edges (equivalent to the same faces condition, but includes + // cases where there are self-edges). + size_t ctr = 1; + auto mhehnext = meta_mesh_->next_halfedge_handle(mheh); + while (mheh != mhehnext) { + ++ctr; + mhehnext = meta_mesh_->next_halfedge_handle(mhehnext); + } + if (ctr >= meta_mesh_->n_edges()) { + if (verbose) { + qDebug() << "minimal mesh reached"; + } + return false; + } + + // is vertex connected to itself? + if (meta_mesh_->from_vertex_handle(mheh) == meta_mesh_->to_vertex_handle(mheh)) { + if (verbose) { + qDebug() << "self-edge"; + } + return false; + } + + // Special case, two non-boundary edges encased by two boundary edges: + // A eg. here: collapsing the A->B halfedge with A->C + // /|\ and C->A being boundaries. Allowing this collapse would result + // | B | in 3 connections between A and C, the middle edge is removed as a loop, + // \|/ and the remaining face is no longer a triangle. + // C If C->A and A->C are not both boundaries this works fine as two loop edges + // can be collapsed. + if (meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle( + meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mheh)))) + == mheh + && meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle( + meta_mesh_->next_halfedge_handle(mheh))) + && meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle( + meta_mesh_->prev_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh)))) + && meta_mesh_->valence(meta_mesh_->face_handle(mheh)) == 3 + && meta_mesh_->valence(meta_mesh_->face_handle( + meta_mesh_->opposite_halfedge_handle(mheh))) == 3) { + if (verbose) { + qDebug() << "encased between two boundary faces"; + } + return false; + } + + // Are the two faces the same triangles? + // Deprecated test with the new addition counting edges + auto mhehiter0 = meta_mesh_->next_halfedge_handle(mheh); + auto mhehiter1 = meta_mesh_->prev_halfedge_handle( + meta_mesh_->opposite_halfedge_handle(mheh)); + bool sameface = true; + while (mhehiter0 != mheh) { + if (mhehiter0 != meta_mesh_->opposite_halfedge_handle(mhehiter1)) { + sameface = false; + } + mhehiter0 = meta_mesh_->next_halfedge_handle(mhehiter0); + mhehiter1 = meta_mesh_->prev_halfedge_handle(mhehiter1); + } + if (sameface && meta_mesh_->valence(meta_mesh_->face_handle(mheh)) == 3) { + if (verbose) { + qDebug() << "same triangle"; + } + return false; + } + + // TODO: think of more exceptions + return true; +} + +/*! + * \brief Embedding::BendMetaEdgeForwards + * A trick used in collapse, bending edges so as to always trace inside disk topology areas + * \param mheh + */ +void Embedding::BendMetaEdgeForwards(OpenMesh::HalfedgeHandle mheh) { + if (meta_mesh_->status(mheh).deleted()) { + assert(meta_mesh_->from_vertex_handle(mheh) == meta_mesh_->to_vertex_handle(mheh)); + return; + } + auto newtomvh = meta_mesh_->to_vertex_handle(meta_mesh_->next_halfedge_handle(mheh)); + + auto mhehopp = meta_mesh_->opposite_halfedge_handle(mheh); + auto mhehoppprev = meta_mesh_->prev_halfedge_handle(mhehopp); + auto mhehnext = meta_mesh_->next_halfedge_handle(mheh); + auto mhehnewnext = meta_mesh_->next_halfedge_handle(mhehnext); + + auto mfh = meta_mesh_->face_handle(mheh); + auto mfho = meta_mesh_->face_handle(mhehopp); + + // sever old connections + // he - he + meta_mesh_->set_next_halfedge_handle(mhehoppprev, mhehnext); + // v - he + if (meta_mesh_->halfedge_handle(meta_mesh_->to_vertex_handle(mheh)) == mhehopp) { + meta_mesh_->set_halfedge_handle(meta_mesh_->to_vertex_handle(mheh), + meta_mesh_->next_halfedge_handle(meta_mesh_->opposite_halfedge_handle(mhehnext))); + } + + + // add new connections + // he - he + meta_mesh_->set_next_halfedge_handle(mhehnext, mhehopp); + meta_mesh_->set_next_halfedge_handle(mheh, mhehnewnext); + + // he - v + meta_mesh_->set_vertex_handle(mheh, newtomvh); + + // f - v + meta_mesh_->set_halfedge_handle(mfh, mheh); + + // v - f + meta_mesh_->set_face_handle(mhehnext, mfho); +} + +/*! + * \brief Embedding::BendMetaEdgeBackwards + * A trick used in collapse, bending edges so as to always trace inside disk topology areas + * \param mheh + */ +void Embedding::BendMetaEdgeBackwards(OpenMesh::HalfedgeHandle mheh) { + if (meta_mesh_->status(mheh).deleted()) { + assert(meta_mesh_->from_vertex_handle(mheh) == meta_mesh_->to_vertex_handle(mheh)); + return; + } + auto newfrommvh = meta_mesh_->from_vertex_handle(meta_mesh_->prev_halfedge_handle(mheh)); + + auto mhehprev = meta_mesh_->prev_halfedge_handle(mheh); + auto mhehopp = meta_mesh_->opposite_halfedge_handle(mheh); + auto mhehoppnext = meta_mesh_->next_halfedge_handle(mhehopp); + auto mhehprevopp = meta_mesh_->opposite_halfedge_handle(mhehprev); + auto mhehprevprev = meta_mesh_->prev_halfedge_handle(mhehprev); + + auto mfh = meta_mesh_->face_handle(mheh); + auto mfho = meta_mesh_->face_handle(mhehopp); + + // sever old connections + // he - he + meta_mesh_->set_next_halfedge_handle(mhehprev, mhehoppnext); + // v - he + if (meta_mesh_->halfedge_handle(meta_mesh_->from_vertex_handle(mheh)) == mheh) { + meta_mesh_->set_halfedge_handle(meta_mesh_->from_vertex_handle(mheh), mhehprevopp); + } + + + // add new connections + // he - he + meta_mesh_->set_next_halfedge_handle(mhehopp, mhehprev); + meta_mesh_->set_next_halfedge_handle(mhehprevprev, mheh); + + // he - v + meta_mesh_->set_vertex_handle(mhehopp, newfrommvh); + + // f - v + meta_mesh_->set_halfedge_handle(mfh, mheh); + + // v - f + meta_mesh_->set_face_handle(mhehprev, mfho); +} + +/*! + * \brief Embedding::RemoveLoop + * removes a loop of edges AB->BA->AB.. by deleting BA + * \param mheh is AB + */ +void Embedding::RemoveLoop(OpenMesh::HalfedgeHandle mheh) { + // make sure this is a loop + if(meta_mesh_->next_halfedge_handle(meta_mesh_->next_halfedge_handle(mheh)) != mheh) { + return; + } + + // if the loop is already deleted, return (should not happen= + if (meta_mesh_->status(mheh).deleted() + || meta_mesh_->status(meta_mesh_->next_halfedge_handle(mheh)).deleted()) { + qDebug() << "Found a halfedge pointing towards a deleted halfedge in RemoveLoop."; + return; + } + + // if the deleted edge would be a boundary, remove the other edge instead. + if (meta_mesh_->is_boundary(meta_mesh_->next_halfedge_handle(mheh)) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle( + meta_mesh_->next_halfedge_handle(mheh)))) { + // Avoid endless loops; if both edges are boundaries keep both. + if (meta_mesh_->is_boundary(mheh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh))) { + return; + } + RemoveLoop(meta_mesh_->next_halfedge_handle(mheh)); + return; + } + + auto mheho = meta_mesh_->opposite_halfedge_handle(mheh); + auto mhehdel = meta_mesh_->next_halfedge_handle(mheh); + auto mhehdelo = meta_mesh_->opposite_halfedge_handle(mhehdel); + auto mvh0 = meta_mesh_->from_vertex_handle(mheh); + auto mvh1 = meta_mesh_->to_vertex_handle(mheh); + auto mfh = meta_mesh_->face_handle(mhehdelo); + auto mfhdel = meta_mesh_->face_handle(mheh); + + // he -> he + meta_mesh_->set_next_halfedge_handle(meta_mesh_->prev_halfedge_handle(mhehdelo), mheh); + meta_mesh_->set_next_halfedge_handle(mheh, meta_mesh_->next_halfedge_handle(mhehdelo)); + + if (meta_mesh_->next_halfedge_handle(mheho) == mhehdelo) { + meta_mesh_->set_next_halfedge_handle(mheho, mheh); + } + + // v -> he + if (meta_mesh_->halfedge_handle(mvh0) == mhehdelo) { + meta_mesh_->set_halfedge_handle(mvh0, mheh); + } + if (meta_mesh_->halfedge_handle(mvh1) == mhehdel) { + meta_mesh_->set_halfedge_handle(mvh1, meta_mesh_->opposite_halfedge_handle(mheh)); + } + + // he -> f + if (meta_mesh_->is_valid_handle(mfh)) meta_mesh_->set_face_handle(mheh, mfh); + + // f -> he + if (meta_mesh_->is_valid_handle(mfh)) meta_mesh_->set_halfedge_handle(mfh, mheh); + + // delete stuff in the base mesh + RemoveMetaEdgeFromBaseMesh(mhehdel); + + // delete stuff in the meta mesh + meta_mesh_->status(mfhdel).set_deleted(true); + meta_mesh_->status(meta_mesh_->edge_handle(mhehdel)).set_deleted(true); + if (meta_mesh_->has_halfedge_status()) { + meta_mesh_->status(mhehdel).set_deleted(true); + meta_mesh_->status(mhehdelo).set_deleted(true); + } + + assert(meta_mesh_->prev_halfedge_handle(mheh) != mhehdel); + assert(meta_mesh_->prev_halfedge_handle(meta_mesh_->next_halfedge_handle(mheh)) != mhehdelo); +} + +/*! + * \brief Embedding::RemoveSelfLoop + * removes a self loop mheh->mheh->mheh... + * \param mheh + */ +void Embedding::RemoveSelfLoop(OpenMesh::HalfedgeHandle mheh) { + // Avoid removing boundaries + if (meta_mesh_->is_boundary(mheh) + || meta_mesh_->is_boundary(meta_mesh_->opposite_halfedge_handle(mheh))) { + return; + } + + assert(meta_mesh_->next_halfedge_handle(mheh) == mheh); + auto mheho = meta_mesh_->opposite_halfedge_handle(mheh); + auto mhehop = meta_mesh_->prev_halfedge_handle(mheho); + auto mhehon = meta_mesh_->next_halfedge_handle(mheho); + // if this assert fails the face of mheho has only 2 edges, don't allow it + assert(mhehop != mhehon); + auto mfhdel = meta_mesh_->face_handle(mheh); + auto mvh = meta_mesh_->to_vertex_handle(mheh); + + // he - he + meta_mesh_->set_next_halfedge_handle(mhehop, mhehon); + + // v - he + if (meta_mesh_->halfedge_handle(mvh) == mheh + || meta_mesh_->halfedge_handle(mvh) == mheho) { + meta_mesh_->set_halfedge_handle(mvh, mhehon); + } + + // delete stuff in the base mesh + RemoveMetaEdgeFromBaseMesh(mheh); + + // delete stuff + meta_mesh_->status(mfhdel).set_deleted(true); + meta_mesh_->status(meta_mesh_->edge_handle(mheh)).set_deleted(true); + if (meta_mesh_->has_halfedge_status()) { + meta_mesh_->status(mheh).set_deleted(true); + meta_mesh_->status(mheho).set_deleted(true); + } +} + +/*! + * \brief Embedding::OneRingEuler + * \param mvh + * \return the euler characteristic of the 1-ring around mvh + */ +int Embedding::OneRingEuler(OpenMesh::VertexHandle mvh) { + auto mheh = meta_mesh_->halfedge_handle(mvh); + // V - E + F = g + // Find euler characteristic of mvhdel 1-ring + OpenMesh::VPropHandleT<bool> v_marked; + OpenMesh::HPropHandleT<bool> h_marked; + OpenMesh::FPropHandleT<bool> f_marked; + meta_mesh_->add_property(v_marked, "marked vertices"); + meta_mesh_->add_property(h_marked, "marked halfedges"); + meta_mesh_->add_property(f_marked, "marked faces"); + auto iter = mheh; + do { + meta_mesh_->property(h_marked, iter) = false; + meta_mesh_->property(v_marked, meta_mesh_->to_vertex_handle(iter)) = false; + meta_mesh_->property(f_marked, meta_mesh_->face_handle(iter)) = false; + if (meta_mesh_->to_vertex_handle(iter) == mvh) { + iter = meta_mesh_->opposite_halfedge_handle(iter); + } else { + iter = meta_mesh_->next_halfedge_handle(iter); + } + } while (iter != mheh); + int n_v = 0; + int n_f = 0; + int n_e = 0; + iter = mheh; + do { + if (!meta_mesh_->property(h_marked, iter)) { + ++n_e; + meta_mesh_->property(h_marked, iter) = true; + meta_mesh_->property(h_marked, meta_mesh_->opposite_halfedge_handle(iter)) = true; + } + if (!meta_mesh_->property(v_marked, meta_mesh_->to_vertex_handle(iter))) { + ++n_v; + meta_mesh_->property(v_marked, meta_mesh_->to_vertex_handle(iter)) = true; + } + if (!meta_mesh_->property(f_marked, meta_mesh_->face_handle(iter))) { + ++n_f; + meta_mesh_->property(f_marked, meta_mesh_->face_handle(iter)) = true; + } + if (meta_mesh_->to_vertex_handle(iter) == mvh) { + iter = meta_mesh_->opposite_halfedge_handle(iter); + } else { + iter = meta_mesh_->next_halfedge_handle(iter); + } + } while (iter != mheh); + int euler = n_v-n_e+n_f; + meta_mesh_->remove_property(v_marked); + meta_mesh_->remove_property(h_marked); + meta_mesh_->remove_property(f_marked); + return euler; +} + +/*! + * \brief Embedding::MetaSwap + * swaps all pointers and properties relevant to the embedding structure between mvh0 and mvh1 + * this is used in the composite relocation method to preserve vertex ids + * \param mvh0 + * \param mvh1 + */ +void Embedding::MetaSwap(OpenMesh::VertexHandle mvh0, OpenMesh::VertexHandle mvh1) { + // Store mvh0 into temp + auto tempcolor = meta_mesh_->color(mvh0); + auto temppoint = meta_mesh_->point(mvh0); + auto tempnormal = meta_mesh_->normal(mvh0); + auto temphalfedgehandle = meta_mesh_->halfedge_handle(mvh0); + auto tempmvconnection = meta_mesh_->property(mv_connection_, mvh0); + std::queue<OpenMesh::HalfedgeHandle> to_halfedges; + auto curr = meta_mesh_->opposite_halfedge_handle(temphalfedgehandle); + while (meta_mesh_->to_vertex_handle(curr) == mvh0) { + meta_mesh_->set_vertex_handle(curr, OpenMesh::PolyConnectivity::InvalidVertexHandle); + to_halfedges.push(curr); + curr = meta_mesh_->prev_halfedge_handle(meta_mesh_->opposite_halfedge_handle(curr)); + } + + // Write mvh1 into mvh0 + meta_mesh_->set_color(mvh0, meta_mesh_->color(mvh1)); + meta_mesh_->set_point(mvh0, meta_mesh_->point(mvh1)); + meta_mesh_->set_normal(mvh0, meta_mesh_->normal(mvh1)); + meta_mesh_->set_halfedge_handle(mvh0, meta_mesh_->halfedge_handle(mvh1)); + meta_mesh_->property(mv_connection_, mvh0) = meta_mesh_->property(mv_connection_, mvh1); + base_mesh_->property(bv_connection_, meta_mesh_->property(mv_connection_, mvh0)) = mvh0; + curr = meta_mesh_->opposite_halfedge_handle(meta_mesh_->halfedge_handle(mvh1)); + while (meta_mesh_->to_vertex_handle(curr) == mvh1) { + meta_mesh_->set_vertex_handle(curr, mvh0); + curr = meta_mesh_->prev_halfedge_handle(meta_mesh_->opposite_halfedge_handle(curr)); + } + + // write temp into mvh1 + meta_mesh_->set_color(mvh1, tempcolor); + meta_mesh_->set_point(mvh1, temppoint); + meta_mesh_->set_normal(mvh1, tempnormal); + meta_mesh_->set_halfedge_handle(mvh1, temphalfedgehandle); + meta_mesh_->property(mv_connection_, mvh1) = tempmvconnection; + base_mesh_->property(bv_connection_, meta_mesh_->property(mv_connection_, mvh1)) = mvh1; + while (!to_halfedges.empty()) { + auto mheh = to_halfedges.front(); + to_halfedges.pop(); + meta_mesh_->set_vertex_handle(mheh, mvh1); + } +} + +/*! + * \brief Embedding::DumpMetaMeshHalfedgeInfo Infodump, useful for some debugging. + */ +void Embedding::DumpMetaMeshHalfedgeInfo() { + qDebug() << "The metamesh has " << meta_mesh_->n_halfedges() << " halfedges."; + for (auto mheh : meta_mesh_->halfedges()) { + qDebug() << "Meta Halfedge " << mheh.idx() << " pointing from vertex " + << meta_mesh_->from_vertex_handle(mheh).idx() << " to vertex " + << meta_mesh_->to_vertex_handle(mheh).idx() << " with next halfedge handle " + << meta_mesh_->next_halfedge_handle(mheh).idx() << " and previous halfedge handle " + << meta_mesh_->prev_halfedge_handle(mheh).idx() << " and opposite halfedge handle " + << meta_mesh_->opposite_halfedge_handle(mheh).idx(); + } +} diff --git a/Embedding.hh b/Embedding.hh new file mode 100644 index 0000000000000000000000000000000000000000..6fb035b03c76fb76ac8b1e5b54e81cc3b585923a --- /dev/null +++ b/Embedding.hh @@ -0,0 +1,220 @@ +#pragma once + +#include <OpenMesh/Core/Mesh/PolyConnectivity.hh> +#include <ObjectTypes/PolyMesh/PolyMesh.hh> +#include <ObjectTypes/TriangleMesh/TriangleMesh.hh> +#include <OpenFlipper/common/Types.hh> +#include <ACG/Utils/HaltonColors.hh> +#include <Draw.hh> +#include <queue> +#include <cmath> + +class Embedding { +public: + Embedding(); + ~Embedding() {} + + enum TraceFaceAttr {UNPROCESSEDFACE, PREPROCESSEDFACE}; + enum RandomType {RATIO, TOTAL}; + + void CopyInitialization(TriMesh& base_mesh, PolyMesh& meta_mesh); + void SelectionTriangulation(TriMesh& base_mesh, PolyMesh& meta_mesh); + void RandomTriangulation(TriMesh& base_mesh, PolyMesh& meta_mesh, double ratio, + RandomType rtype); + void ColorizeMetaMesh(); + + void Trace(OpenMesh::HalfedgeHandle mheh, bool cleanup = true, + bool mark_trace = false, + TraceFaceAttr facetype = PREPROCESSEDFACE); + void Retrace(OpenMesh::HalfedgeHandle mheh, bool cleanup = true, + bool mark_trace = false, + TraceFaceAttr facetype = PREPROCESSEDFACE); + void Collapse(OpenMesh::HalfedgeHandle mheh, bool verbose = false); + bool IsCollapseOkay(OpenMesh::HalfedgeHandle mheh, bool verbose = false); + void Rotate(OpenMesh::EdgeHandle meh); + bool IsRotationOkay(OpenMesh::EdgeHandle meh); + void Relocate(OpenMesh::VertexHandle mvh, OpenMesh::VertexHandle bvh, bool verbose = false, + bool lowlevel = true); + void Split(OpenMesh::VertexHandle bvh, OpenMesh::HalfedgeHandle + mheh = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle, + bool verbose = false); + bool ErrStatus() {return debug_hard_stop_;} + void DumpMetaMeshHalfedgeInfo(); + + TriMesh* GetBaseMesh() {return base_mesh_;} + PolyMesh* GetMetaMesh() {return meta_mesh_;} + OpenMesh::EdgeHandle GetMetaEdge(OpenMesh::VertexHandle bvh); + + bool IsSectorBorder(OpenMesh::VertexHandle bvh, std::list<OpenMesh::HalfedgeHandle> + exceptions = {}); + + double MetaHalfedgeWeight(OpenMesh::HalfedgeHandle mheh); + double MetaVertexWeight(OpenMesh::VertexHandle mvh); + + double CalculateEdgeLength(OpenMesh::HalfedgeHandle mheh); + double CalculateEdgeLength(OpenMesh::EdgeHandle meh); + double CalculateFlippedEdgeLength(OpenMesh::HalfedgeHandle mheh); + double CalculateFlippedEdgeLength(OpenMesh::EdgeHandle meh); + + OpenMesh::VertexHandle MiddleBvh(OpenMesh::EdgeHandle meh); + OpenMesh::VertexHandle MiddleBvh(OpenMesh::HalfedgeHandle mheh); + + void CleanMetaMesh(); + void CleanUpBaseMesh(bool garbagecollection = true); + + void MetaGarbageCollection(std::vector<OpenMesh::VertexHandle*> vh_update = {}, + std::vector<OpenMesh::HalfedgeHandle*> heh_update = {}, + std::vector<OpenMesh::FaceHandle*> fh_update= {}); + void BaseGarbageCollection(std::vector<OpenMesh::VertexHandle*> vh_update = {}, + std::vector<OpenMesh::HalfedgeHandle*> heh_update = {}, + std::vector<OpenMesh::FaceHandle*> fh_update= {}); + // Removing an edge + void RemoveMetaEdgeFromBaseMesh(OpenMesh::HalfedgeHandle mheh, bool cleanup = true); + void RemoveMetaEdgeFromMetaMesh(OpenMesh::HalfedgeHandle mheh); + + void MarkVertex(OpenMesh::VertexHandle bvh); + + friend class Testing; + + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> bv_connection_; + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> mv_connection_; + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> bsplithandle_; + OpenMesh::HPropHandleT<OpenMesh::HalfedgeHandle> bhe_connection_; + OpenMesh::HPropHandleT<OpenMesh::HalfedgeHandle> mhe_connection_; + OpenMesh::HPropHandleT<OpenMesh::HalfedgeHandle> next_heh_; + OpenMesh::HPropHandleT<double> halfedge_weight_; + +private: + enum SplitTraversal {LEFT, RIGHT}; + bool TriangulationPipeline( + std::vector<OpenMesh::VertexHandle> meta_mesh_points); + void InitializeProperties(); + bool TriangulateMetaMesh(); + void PreProcessEdges(); + void ProcessHalfedge(OpenMesh::HalfedgeHandle mheh); + void ProcessEdge(OpenMesh::EdgeHandle meh); + void ProcessVertex(OpenMesh::VertexHandle bvh); + void CleanupVertex(OpenMesh::VertexHandle bvh); + void CleanupFace(OpenMesh::HalfedgeHandle mheh); + void CleanupHalfedge(OpenMesh::HalfedgeHandle mheh); + void ProcessNeighbors(OpenMesh::EdgeHandle meh); + void ProcessFace(OpenMesh::HalfedgeHandle mheh); + bool ConditionalSplit(OpenMesh::HalfedgeHandle bheh); + std::vector<OpenMesh::HalfedgeHandle> LeftHalfCircle(OpenMesh::HalfedgeHandle bheh); + std::vector<OpenMesh::HalfedgeHandle> GetBaseHalfedges(OpenMesh::HalfedgeHandle mheh); + OpenMesh::VertexHandle ConditionalCollapse(OpenMesh::VertexHandle bvh); + OpenMesh::HalfedgeHandle SplitBaseHe(OpenMesh::HalfedgeHandle bheh); + void CollapseBaseHe(OpenMesh::HalfedgeHandle bheh); + void LowLevelBaseCollapse(OpenMesh::HalfedgeHandle bheh); + void AdjustPointersForBheCollapse(OpenMesh::HalfedgeHandle bheh); + void MergeProperties(OpenMesh::HalfedgeHandle bheh0, OpenMesh::HalfedgeHandle bheh1); + void CreateMetaMeshVertices(std::vector<OpenMesh::VertexHandle> meta_mesh_points); + bool Delaunay(OpenMesh::VPropHandleT<double> voronoidistance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> to_heh, + OpenMesh::HPropHandleT<int> multiplicity_heh); + void TraverseBoundary(OpenMesh::VertexHandle mvh); + int fact(int n); + int nCk(int n, int k); + std::vector<int> VoronoiGenus(); + void SetFaceProperties(OpenMesh::FaceHandle bf, + OpenMesh::FaceHandle mf); + void CopyFaceProperties(OpenMesh::FaceHandle mfh, + std::vector<OpenMesh::HalfedgeHandle> boundaryborders); + OpenMesh::FaceHandle AddFace(std::vector<OpenMesh::VertexHandle> mvh, + std::vector<OpenMesh::HalfedgeHandle> mheh); + void AddBoundaries(); + OpenMesh::HalfedgeHandle FindHalfedge(OpenMesh::HalfedgeHandle bheh); + + void VoronoiBorders(); + void SetBorderProperties(OpenMesh::HalfedgeHandle bheh, + OpenMesh::HalfedgeHandle mheh); + + void A_StarTriangulation(); + + void NaiveVoronoi(std::queue<OpenMesh::VertexHandle> queue, + OpenMesh::VPropHandleT<double> voronoidistance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> to_heh); + + void ColorizeMetaMeshVertices(); + void ColorizeMetaEdges(); + void ColorizeVoronoiRegions(); + void ColorizeBorders(); + + void TestHalfedgeConsistency(); + + void BoundaryTrace(OpenMesh::HalfedgeHandle mheh, bool mark_trace = false); + void SimpleTrace(OpenMesh::HalfedgeHandle mheh, bool cleanup = true, + bool mark_trace = false); + void AdvancedTrace(OpenMesh::HalfedgeHandle mheh); + std::vector<OpenMesh::HalfedgeHandle> A_StarBidirectional(OpenMesh::HalfedgeHandle bheh0, + OpenMesh::HalfedgeHandle bheh1, bool mark_trace = false, + OpenMesh::HalfedgeHandle mheh + = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle); + double Distance(OpenMesh::VertexHandle bvhf, OpenMesh::VertexHandle bvhv); + double A_StarHeuristic(OpenMesh::VertexHandle bvhf, OpenMesh::VertexHandle bvhr, + OpenMesh::VertexHandle bvhv); + std::vector<OpenMesh::VertexHandle> A_StarNeighbors(OpenMesh::VertexHandle bvh, + OpenMesh::HalfedgeHandle mheh, + OpenMesh::VPropHandleT<bool> closed_set, + OpenMesh::VertexHandle towardsbvh, + OpenMesh::HalfedgeHandle bheh = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle); + bool ValidA_StarEdge(OpenMesh::HalfedgeHandle bheh, + OpenMesh::HalfedgeHandle mheh); + std::vector<OpenMesh::HalfedgeHandle> A_StarReconstructPath(OpenMesh::VertexHandle middle, + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> predecessor0, + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> predecessor1); + + std::pair<OpenMesh::HalfedgeHandle, OpenMesh::HalfedgeHandle> FindA_StarStartingHalfedges( + OpenMesh::HalfedgeHandle mheh, bool verbose = false); + void InsertPath(OpenMesh::HalfedgeHandle mheh, std::vector<OpenMesh::HalfedgeHandle> path); + + // Adding an edge + OpenMesh::HalfedgeHandle AddMetaEdge(OpenMesh::HalfedgeHandle mheh0, + OpenMesh::HalfedgeHandle mheh); + + // Rotation + void MetaRotation(OpenMesh::EdgeHandle meh); + + // Split + // Edges + void EdgeSplit(OpenMesh::EdgeHandle meh, OpenMesh::VertexHandle bvh + = OpenMesh::PolyConnectivity::InvalidVertexHandle, bool verbose = false); + void MetaEdgeSplit(OpenMesh::EdgeHandle meh, OpenMesh::VertexHandle bvh); + void OverwriteHalfedgeConnection(OpenMesh::HalfedgeHandle mheh, + OpenMesh::HalfedgeHandle bheh); + // Faces + void FaceSplit(OpenMesh::VertexHandle bvh, OpenMesh::HalfedgeHandle + mheh = OpenMesh::PolyConnectivity::InvalidHalfedgeHandle, + bool verbose = false); + OpenMesh::HalfedgeHandle FindFaceHalfedge(OpenMesh::VertexHandle bvh); + void MetaFaceSplit(OpenMesh::HalfedgeHandle mheh, OpenMesh::VertexHandle bvh); + + // Collapse + void BendMetaEdgeForwards(OpenMesh::HalfedgeHandle mheh); + void BendMetaEdgeBackwards(OpenMesh::HalfedgeHandle mheh); + void RemoveLoop(OpenMesh::HalfedgeHandle mheh); + void RemoveSelfLoop(OpenMesh::HalfedgeHandle mheh); + int OneRingEuler(OpenMesh::VertexHandle mvh); + + // Relocate + void LowLevelRelocate(OpenMesh::VertexHandle mvh, OpenMesh::VertexHandle bvh, + bool verbose = false); + void TraverseGroup(OpenMesh::VertexHandle mvh, + OpenMesh::HalfedgeHandle moh, + OpenMesh::VPropHandleT<bool> groupselected); + void CompositeRelocate(OpenMesh::VertexHandle mvh, OpenMesh::VertexHandle bvh, + bool verbose = false); + void MetaSwap(OpenMesh::VertexHandle mvh0, OpenMesh::VertexHandle mvh1); + + bool debug_hard_stop_ = false; + bool initial_triangulation_ = false; + bool boundaries_ = false; + + TriMesh* base_mesh_; + PolyMesh* meta_mesh_; + Utils::Draw draw_; + + OpenMesh::VPropHandleT<OpenMesh::VertexHandle> voronoiID_; + OpenMesh::HPropHandleT<OpenMesh::HalfedgeHandle> bhe_border_; + OpenMesh::HPropHandleT<OpenMesh::HalfedgeHandle> mhe_border_; +}; diff --git a/IsotropicRemesher.cc b/IsotropicRemesher.cc new file mode 100644 index 0000000000000000000000000000000000000000..b2d481d11c25c37a10682791c814e19d32beb146 --- /dev/null +++ b/IsotropicRemesher.cc @@ -0,0 +1,1002 @@ +#include "IsotropicRemesher.hh" +#include <ACG/Geometry/bsp/TriangleBSPT.hh> +#include <QDebug> +#include <QGraphicsView> +#include <algorithm> + +// TODO: if you are going to use the Remesher change the output path here +#define SCREENSHOT_PATH "/home/jschnathmeier/Screenshots" + +/*! + * \brief IsotropicRemesher::Remesh + * \param embedding: embedding object, needs to be triangulated + * \param target_length: target edge length as compared to the object diagonal length + * \param iterations: number of times the algorithm runs + * \param alpha: slack variable for the upper length limit for splits + * \param beta: slack variable for the lower length limit for collapses + * \param smtype: smoothing options + * \param screenshot: screenshot function; no screenshots if no function + * \param limitflips: only do flips that don't increase edge length + * \param strtype: straightening options + * \param corder: collapse order options + */ +void IsotropicRemesher::Remesh(Embedding* embedding, + double target_length, + uint iterations, + double alpha, + double beta, + SmoothingType smtype, + std::function<void (QString, QString, double, double)> screenshot, + bool limitflips, + StraighteningType strtype, + CollapsingOrder corder) { + debug_hard_stop_ = false; + using namespace PluginFunctions; + qDebug() << "Running the Isotropic Remeshing algorithm"; + auto base_mesh = embedding->GetBaseMesh(); + + const double inf = std::numeric_limits<double>::infinity(); + ACG::Vec3d minCorner = {+inf, +inf, +inf}; + ACG::Vec3d maxCorner = {-inf, -inf, -inf}; + for (auto bvh : base_mesh->vertices()) { + const auto& p = base_mesh->point(bvh); + minCorner.minimize(p); + maxCorner.maximize(p); + } + + double scale = (maxCorner-minCorner).length(); + double high = target_length * alpha * scale; + double low = high * beta / alpha; + + if (screenshot) { + QDir dir(QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations)); + if (!dir.exists()) { + dir.mkpath("."); + } + screenshot(QString("IsoRemesh0_Start"), + QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations) , + 0, 0); + } + Stopwatch* swatchtotal = new Stopwatch(); + for (uint i = 0; i < iterations; ++i) { + Stopwatch* swatchit = new Stopwatch(); + Stopwatch* swatchlocal = new Stopwatch(); + qDebug() << "Iteration" << i+1 << "; Splitting..."; + Splits(embedding, high); + if (debug_hard_stop_) return; + embedding->MetaGarbageCollection(); + embedding->CleanUpBaseMesh(); + if (screenshot) { + screenshot(QString("IsoRemeshIt_") + QString::number(i) + QString(".0Splits"), + QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations) , + 0, 0); + } + qDebug() << "Time elapsed: " << swatchlocal->Delta() << "ms."; + swatchlocal->Reset(); + qDebug() << "Iteration" << i+1 << "; Collapsing..."; + Collapses(embedding, low, corder); + if (debug_hard_stop_) return; + embedding->MetaGarbageCollection(); + embedding->CleanUpBaseMesh(); + if (screenshot) { + screenshot(QString("IsoRemeshIt_") + QString::number(i) + QString(".1Collapses"), + QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations) , + 0, 0); + } + qDebug() << "Time elapsed: " << swatchlocal->Delta() << "ms."; + swatchlocal->Reset(); + qDebug() << "Iteration" << i+1 << "; Flipping..."; + Flips(embedding, limitflips); + if (debug_hard_stop_) return; + embedding->MetaGarbageCollection(); + embedding->CleanUpBaseMesh(); + if (screenshot) { + screenshot(QString("IsoRemeshIt_") + QString::number(i) + QString(".2Flips"), + QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations) , + 0, 0); + } + qDebug() << "Time elapsed: " << swatchlocal->Delta() << "ms."; + swatchlocal->Reset(); + qDebug() << "Iteration" << i+1 << "; Smoothing..."; + if (strtype == IMPLICIT) { + Smoothing(embedding, smtype, true); + } else { + Smoothing(embedding, smtype, false); + } + if (debug_hard_stop_) return; + embedding->CleanUpBaseMesh(); + if (screenshot) { + screenshot(QString("IsoRemeshIt_") + QString::number(i) + QString(".3Smoothing"), + QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations) , + 0, 0); + } + qDebug() << "Time elapsed: " << swatchlocal->Delta() << "ms."; + swatchlocal->Reset(); + qDebug() << "Iteration" << i+1 << "; Straightening..."; + Straightening(embedding, strtype); + if (debug_hard_stop_) return; + embedding->CleanUpBaseMesh(); + qDebug() << "Time elapsed: " << swatchlocal->Delta() << "ms."; + qDebug() << "Iteration" << i+1 << " finished, time elapsed: " + << swatchit->Delta() << "ms."; + if (screenshot) { + qDebug() << "Iteration" << i+1 << "Screenshot"; + screenshot(QString("IsoRemeshIt_") + QString::number(i) + QString(".4Straightening"), + QString(SCREENSHOT_PATH) + "/target" + QString::number(target_length) + + "-iterations" + QString::number(iterations) , + 0, 0); + } + } + qDebug() << "Finished " << iterations << " iterations of Isotropic Remeshing" + " with target edge length " << target_length * scale; + qDebug() << "Time elapsed: " << swatchtotal->Delta() << "ms."; +} + +/*! + * \brief IsotropicRemesher::Splits + * \param embedding + * \param high: split edges longer than this + */ +void IsotropicRemesher::Splits(Embedding* embedding, double high) { + auto meta_mesh = embedding->GetMetaMesh(); + std::vector<OpenMesh::EdgeHandle> medges; + for (auto meh : meta_mesh->edges()) { + medges.push_back(meh); + } + std::random_shuffle(medges.begin(), medges.end()); + for (auto meh : medges) { + if (embedding->CalculateEdgeLength(meh) > high) { + embedding->Split(embedding->MiddleBvh(meh)); + if (embedding->ErrStatus()) { + qDebug() << "Splitting failed for meta edge " << meh.idx(); + debug_hard_stop_ = true; + } + } + if (meh.idx()%10 == 0) { + embedding->CleanUpBaseMesh(); + } + } +} + +/*! + * \brief IsotropicRemesher::Collapses + * \param embedding + * \param low: collapse edges shorter than this + * \param corder: collapse ordering (random / optimizing by valence / optimizing by + * edge weight heuristic) + */ +void IsotropicRemesher::Collapses(Embedding *embedding, double low, + CollapsingOrder corder) { + auto meta_mesh = embedding->GetMetaMesh(); + OpenMesh::VPropHandleT<bool> collapseblocked; + meta_mesh->add_property(collapseblocked, "Blocks collapsing"); + for (auto mvh : meta_mesh->vertices()) { + meta_mesh->property(collapseblocked, mvh) = false; + } + + std::vector<OpenMesh::HalfedgeHandle> mhalfedges; + for (auto mheh : meta_mesh->halfedges()) { + mhalfedges.push_back(mheh); + } + std::random_shuffle(mhalfedges.begin(), mhalfedges.end()); + std::multimap<OpenMesh::HalfedgeHandle, double> mhehs; + switch(corder) { + case RANDOM: { + for (auto mheh : mhalfedges) { + mhehs.insert(std::make_pair(mheh, 0.0)); + } + break; + } + case VALENCE: { + for (auto mheh : mhalfedges) { + double valw = 0.0; + + auto mheh0 = meta_mesh->prev_halfedge_handle(mheh); + auto mheh1 = meta_mesh->opposite_halfedge_handle(meta_mesh->next_halfedge_handle(mheh)); + auto mheho = meta_mesh->opposite_halfedge_handle(mheh); + auto mheh2 = meta_mesh->prev_halfedge_handle(mheho); + auto mheh3 = meta_mesh->opposite_halfedge_handle(meta_mesh->next_halfedge_handle(mheho)); + + auto mvh0 = meta_mesh->from_vertex_handle(mheh); + auto mvh1 = meta_mesh->to_vertex_handle(mheh); + auto mvh2 = meta_mesh->from_vertex_handle(mheh0); + auto mvh3 = meta_mesh->from_vertex_handle(mheh2); + + valw = meta_mesh->valence(mvh0) + meta_mesh->valence(mvh1); + if (meta_mesh->from_vertex_handle(mheh0) + == meta_mesh->from_vertex_handle(mheh1)) { + valw -= meta_mesh->valence(mvh2); + } + if (meta_mesh->from_vertex_handle(mheh2) + == meta_mesh->from_vertex_handle(mheh3)) { + valw -= meta_mesh->valence(mvh3); + } + + mhehs.insert(std::make_pair(mheh, valw)); + } + break; + } + case SPLITWEIGHT: { + for (auto mheh : mhalfedges) { + double splitw = 0.0; + + auto mheh0 = meta_mesh->prev_halfedge_handle(mheh); + auto mheh1 = meta_mesh->opposite_halfedge_handle(meta_mesh->next_halfedge_handle(mheh)); + auto mheho = meta_mesh->opposite_halfedge_handle(mheh); + auto mheh2 = meta_mesh->prev_halfedge_handle(mheho); + auto mheh3 = meta_mesh->opposite_halfedge_handle(meta_mesh->next_halfedge_handle(mheho)); + + auto mvh0 = meta_mesh->from_vertex_handle(mheh); + + int newmvh1edges = static_cast<int>(meta_mesh->valence(mvh0)) - 1; + if (meta_mesh->from_vertex_handle(mheh0) + == meta_mesh->from_vertex_handle(mheh1)) { + splitw -= embedding->MetaHalfedgeWeight(mheh0); + newmvh1edges -= 1; + } + auto mhehiter = meta_mesh->prev_halfedge_handle( + meta_mesh->opposite_halfedge_handle(mheh0)); + while (mhehiter != mheh3) { + splitw -= embedding->MetaHalfedgeWeight(mhehiter); + mhehiter = meta_mesh->prev_halfedge_handle( + meta_mesh->opposite_halfedge_handle(mhehiter)); + } + if (meta_mesh->from_vertex_handle(mheh2) + == meta_mesh->from_vertex_handle(mheh3)) { + splitw -= embedding->MetaHalfedgeWeight(mheh3); + newmvh1edges -= 1; + } + splitw += newmvh1edges * embedding->MetaHalfedgeWeight(mheh); + mhehs.insert(std::make_pair(mheh, splitw)); + } + break; + } + } + + uint mhehcounter = 0; + + for (auto pair : mhehs) { + auto mheh = pair.first; + if (meta_mesh->is_valid_handle(mheh) + && !meta_mesh->status(mheh).deleted()) { + auto meh = meta_mesh->edge_handle(mheh); + if (!meta_mesh->property(collapseblocked, + meta_mesh->from_vertex_handle(mheh)) + && embedding->IsCollapseOkay(mheh) + && embedding->CalculateEdgeLength(meh) < low) { + // If a collapse is about to happen block off the 1-Ring from being + // collapsed in this iteration. + for (auto mvh : meta_mesh->vv_range(meta_mesh->from_vertex_handle(mheh))) { + meta_mesh->property(collapseblocked, mvh) = true; + } + embedding->Collapse(mheh); + } + } + if (mheh.idx() % 100 == 0) { + qDebug() << "Collapsing mheh: " << mhehcounter; + } + ++mhehcounter; + } + embedding->CleanUpBaseMesh(); + embedding->MetaGarbageCollection();//); + meta_mesh->remove_property(collapseblocked); +} + +/*! + * \brief IsotropicRemesher::Flips + * \param embedding + * \param limitflips: if this is true flips are only done when they don't increase + * edge length (expensive check) + */ +void IsotropicRemesher::Flips(Embedding *embedding, bool limitflips) { + auto meta_mesh = embedding->GetMetaMesh(); + OpenMesh::EPropHandleT<int> flipvalue; + meta_mesh->add_property(flipvalue, "EP_flipvalue"); + std::vector<OpenMesh::EdgeHandle> medges; + for (auto meh : meta_mesh->edges()) { + meta_mesh->property(flipvalue, meh) = FlipEval(embedding, meh); + medges.push_back(meh); + } + std::random_shuffle(medges.begin(), medges.end()); + std::multimap<OpenMesh::EdgeHandle, double> mehs; + for (auto meh : medges) { + mehs.insert(std::make_pair(meh, -embedding->MetaHalfedgeWeight( + meta_mesh->halfedge_handle(meh, 0)))); + } + for (auto mpair : mehs) { + auto meh = mpair.first; + double newlen, oldlen; + if (meta_mesh->property(flipvalue, meh) > 0) { + auto v0 = meta_mesh->to_vertex_handle(meta_mesh->halfedge_handle(meh, 0)); + auto v1 = meta_mesh->to_vertex_handle(meta_mesh->halfedge_handle(meh, 1)); + auto v2 = meta_mesh->to_vertex_handle(meta_mesh->next_halfedge_handle( + meta_mesh->halfedge_handle(meh, 0))); + auto v3 = meta_mesh->to_vertex_handle(meta_mesh->next_halfedge_handle( + meta_mesh->halfedge_handle(meh, 1))); + bool flip = true; + for (auto ee : meta_mesh->ve_range(v0)) { + if (meta_mesh->property(flipvalue, ee) > meta_mesh->property(flipvalue, meh)) { + flip = false; + } + } + for (auto ee : meta_mesh->ve_range(v1)) { + if (meta_mesh->property(flipvalue, ee) > meta_mesh->property(flipvalue, meh)){ + flip = false; + } + } + if (flip) { + if (limitflips) { + oldlen = embedding->CalculateEdgeLength(meh); + } + embedding->Rotate(meh); + if (limitflips) { + newlen = embedding->CalculateEdgeLength(meh); + } + if (!limitflips || oldlen >= newlen) { + for (auto ee : meta_mesh->ve_range(v0)) { + meta_mesh->property(flipvalue, ee) = FlipEval(embedding, ee); + } + for (auto ee : meta_mesh->ve_range(v1)) { + meta_mesh->property(flipvalue, ee) = FlipEval(embedding, ee); + } + for (auto ee : meta_mesh->ve_range(v2)) { + meta_mesh->property(flipvalue, ee) = FlipEval(embedding, ee); + } + for (auto ee : meta_mesh->ve_range(v3)) { + meta_mesh->property(flipvalue, ee) = FlipEval(embedding, ee); + } + } else { + embedding->Rotate(meh); + } + } + } + if (meh.idx()%10 == 0) { + embedding->CleanUpBaseMesh(); + } + } + meta_mesh->remove_property(flipvalue); +} + +/*! + * \brief IsotropicRemesher::FlipEval: evaluate whether an edge should be flipped + * \param embedding + * \param meh + * \return the improvement of the meshes' valence deviation if this split + * was performed + */ +int IsotropicRemesher::FlipEval(Embedding *embedding, OpenMesh::EdgeHandle meh) { + auto meta_mesh = embedding->GetMetaMesh(); + auto heh0 = meta_mesh->halfedge_handle(meh, 0); + auto heh1 = meta_mesh->halfedge_handle(meh, 1); + auto fv = meta_mesh->to_vertex_handle(heh0); + auto lv = meta_mesh->to_vertex_handle(meta_mesh->next_halfedge_handle(heh0)); + auto bv = meta_mesh->to_vertex_handle(heh1); + auto rv = meta_mesh->to_vertex_handle(meta_mesh->next_halfedge_handle(heh1)); + uint local_objective_old = (meta_mesh->valence(bv)-6)*(meta_mesh->valence(bv)-6) + +(meta_mesh->valence(fv)-6)*(meta_mesh->valence(fv)-6) + +(meta_mesh->valence(rv)-6)*(meta_mesh->valence(rv)-6) + +(meta_mesh->valence(lv)-6)*(meta_mesh->valence(lv)-6); + uint local_objective_new = (meta_mesh->valence(bv)-7)*(meta_mesh->valence(bv)-7) + +(meta_mesh->valence(fv)-7)*(meta_mesh->valence(fv)-7) + +(meta_mesh->valence(rv)-5)*(meta_mesh->valence(rv)-5) + +(meta_mesh->valence(lv)-5)*(meta_mesh->valence(lv)-5); + int local_improvement = static_cast<int>(local_objective_old - local_objective_new); + // Triangles, + // Old: (bv, rv, fv), (bv, fv, lv) + auto fnold1 = (meta_mesh->point(rv)-meta_mesh->point(bv)) + %(meta_mesh->point(fv)-meta_mesh->point(bv)); + auto fnold2 = (meta_mesh->point(fv)-meta_mesh->point(bv)) + %(meta_mesh->point(lv)-meta_mesh->point(bv)); + // New: (bv, rv, lv), (fv, lv, rv) + auto fnnew1 = (meta_mesh->point(rv)-meta_mesh->point(bv)) + %(meta_mesh->point(lv)-meta_mesh->point(bv)); + auto fnnew2 = (meta_mesh->point(lv)-meta_mesh->point(fv)) + %(meta_mesh->point(rv)-meta_mesh->point(fv)); + + bool geometrically_sound = false; + if(((fnold1|fnnew1) > 0 || (fnold2|fnnew1) > 0) + && ((fnold1|fnnew2) > 0 || (fnold2|fnnew2) > 0)) + geometrically_sound = true; + + if (local_improvement > 0 + && embedding->IsRotationOkay(meh) + && geometrically_sound) { + return local_improvement; + } + return 0; +} + +/*! + * \brief IsotropicRemesher::Smoothing + * \param embedding + * \param stype: smoothing options; forestfire, vertexweights or vertexdistances + * \param shakeup: smoothes vertices twice, moving them away from center first and then + * back to it. Not recommended as it is very expensive and doesn't seem to help much. + */ +void IsotropicRemesher::Smoothing(Embedding *embedding, SmoothingType stype, bool shakeup) { + switch(stype) { + case FORESTFIRE: { + qDebug() << "Forest Fire Smoothing"; + SmoothingFF(embedding); + break; + } + case VERTEXWEIGHTS: { + qDebug() << "Vertex Weights Smoothing"; + SmoothingVWD(embedding, VERTEXWEIGHTS, shakeup); + break; + } + case VERTEXDISTANCES:{ + qDebug() << "Vertex Distances Smoothing"; + SmoothingVWD(embedding, VERTEXDISTANCES, shakeup); + break; + } + } +} + +/*! + * \brief IsotropicRemesher::SmoothingVWD + * \param embedding + * \param stype + * \param shakeup + */ +void IsotropicRemesher::SmoothingVWD(Embedding *embedding, SmoothingType stype + , bool shakeup) { + auto meta_mesh = embedding->GetMetaMesh(); + auto base_mesh = embedding->GetBaseMesh(); + OpenMesh::VPropHandleT<std::vector<double>> distances; + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> direction; + base_mesh->add_property(distances, "Distances from each patch vertex"); + std::vector<OpenMesh::VertexHandle> mvhlist; + std::vector<OpenMesh::VertexHandle *> mvhlist_pointers; + for (auto mvh : meta_mesh->vertices()) { + mvhlist.push_back(mvh); + } + std::random_shuffle(mvhlist.begin(), mvhlist.end()); + uint ctr = 0; + for (auto mvh : mvhlist) { + if (meta_mesh->is_valid_handle(mvh) + && !meta_mesh->status(mvh).deleted()) { + if (ctr%10 == 0) { + qDebug() << "Smoothing meta vertex " << ctr; + } + for (auto bvh : base_mesh->vertices()) { + base_mesh->property(distances, bvh) = {}; + } + SmoothVertexVWD(embedding, &distances, mvh, stype, shakeup); + if (debug_hard_stop_) return; + embedding->CleanUpBaseMesh(); + } + ++ctr; + } + embedding->MetaGarbageCollection();//mvhlist_pointers); + base_mesh->remove_property(distances); +} + +/*! + * \brief IsotropicRemesher::SmoothVertexVWD + * \param embedding + * \param distances + * \param mvh + * \param stype + * \param shakeup + */ +void IsotropicRemesher::SmoothVertexVWD(Embedding *embedding, + const OpenMesh::VPropHandleT<std::vector<double>>* distances, + OpenMesh::VertexHandle mvh, + SmoothingType stype, + bool shakeup) { + const double inf = std::numeric_limits<double>::infinity(); + auto meta_mesh = embedding->GetMetaMesh(); + auto base_mesh = embedding->GetBaseMesh(); + // Ensure the vertex is moved twice in order to clean up the underlying basemesh + if (shakeup) { + embedding->Relocate(mvh, base_mesh->to_vertex_handle(meta_mesh->property( + embedding->mhe_connection_, meta_mesh->opposite_halfedge_handle( + meta_mesh->halfedge_handle(mvh))))); + embedding->CleanUpBaseMesh(); + } + std::list<OpenMesh::HalfedgeHandle> mih_list; + for (auto mhehit : meta_mesh->vih_range(mvh)) { + if (meta_mesh->is_valid_handle(mhehit) + && !meta_mesh->status(mhehit).deleted()) { + mih_list.push_back(mhehit); + } + } + //qDebug() << "Smoothing Vertex " << mvh.idx(); + uint it = 0; + double mindist = inf; + auto bvhmin = OpenMesh::PolyConnectivity::InvalidVertexHandle; + struct cmp { + bool operator()(const std::pair<OpenMesh::VertexHandle, std::vector<double>> &a, + const std::pair<OpenMesh::VertexHandle, std::vector<double>> &b) { + return a.second.back() > b.second.back(); + } + }; + std::priority_queue<std::pair<OpenMesh::VertexHandle, std::vector<double>>, + std::vector<std::pair<OpenMesh::VertexHandle, std::vector<double>>>, cmp> minheap; + for (auto mhehit : mih_list) { + //qDebug() << "Measuring sector distances for boundary vertex " + // << meta_mesh->from_vertex_handle(mhehit).idx(); + if (meta_mesh->from_vertex_handle(mhehit) == meta_mesh->to_vertex_handle(mhehit)) { + //qDebug() << "Self Edge"; + break; + } + auto mvhit = meta_mesh->from_vertex_handle(mhehit); + auto bvhit = meta_mesh->property(embedding->mv_connection_, mvhit); + assert(base_mesh->is_valid_handle(bvhit)); + assert(embedding->IsSectorBorder(bvhit, mih_list)); + std::queue<OpenMesh::VertexHandle> patchvertices; + auto bhehit = meta_mesh->property(embedding->mhe_connection_, mhehit); + assert(base_mesh->property(*distances, + base_mesh->to_vertex_handle(bhehit)).size() == it); + assert(base_mesh->property(embedding->bhe_connection_, bhehit) == mhehit); + if (embedding->IsSectorBorder(base_mesh->to_vertex_handle(bhehit), mih_list)) { + qDebug() << "The first bheh " << base_mesh->to_vertex_handle(bhehit).idx() + << "of an mheh ends in a sector border outside its patch, this should " + "be impossible."; + qDebug() << "The bheh starts with a meta vertex:" << meta_mesh->is_valid_handle( + base_mesh->property(embedding->bv_connection_, + base_mesh->from_vertex_handle(bhehit))); + qDebug() << "The bheh ends in a meta vertex:" << meta_mesh->is_valid_handle( + base_mesh->property(embedding->bv_connection_, + base_mesh->to_vertex_handle(bhehit))); + qDebug() << "The bheh ends in:" << base_mesh->property(embedding->bv_connection_, + base_mesh->to_vertex_handle(bhehit)).idx() << " the middle vertex " + "of the patch is:" << mvh.idx(); + } + base_mesh->property(*distances, base_mesh->to_vertex_handle(bhehit)).push_back( + base_mesh->calc_edge_length(bhehit)); + minheap.push(std::make_pair(base_mesh->to_vertex_handle(bhehit), + base_mesh->property(*distances, base_mesh->to_vertex_handle(bhehit)))); + // patchvertices is now populated by vertices inside the patch surrounding the seed + // meta vertex + //qDebug() << "Distance Scoring Loop"; + while(!minheap.empty()) { + auto bvhcurr = minheap.top().first; + minheap.pop(); + + bool borderparity = (meta_mesh->is_boundary(mvh) == base_mesh->is_boundary(bvhcurr)); + + //qDebug() << "Distance Scoring"; + if (stype == VERTEXWEIGHTS) { + auto currdist = DistanceScoreVW(embedding, distances, bvhcurr, + static_cast<uint>(mih_list.size())); + if (borderparity && currdist < mindist) { + mindist = currdist; + bvhmin = bvhcurr; + } + } else if (stype == VERTEXDISTANCES) { + auto currdist = DistanceScoreVD(embedding, distances, bvhcurr, + static_cast<uint>(mih_list.size())); + if (borderparity && currdist < mindist) { + mindist = currdist; + bvhmin = bvhcurr; + } + } else { + qFatal("Called smoothing with invalid parameters."); + return; + } + + auto bvhcurrdistprop = base_mesh->property(*distances, bvhcurr); + if (bvhcurrdistprop.size() < it+1) { + qDebug() << "Iteration " << it << ", Vertex " << bvhcurr.idx() + << "'s distance property only has size " + << bvhcurrdistprop.size(); + qDebug() << "The vertex is a meta vertex:" + << meta_mesh->is_valid_handle(base_mesh->property( + embedding->bv_connection_, bvhcurr)); + qDebug() << "The vertex is a sector border:" + << embedding->IsSectorBorder(bvhcurr); + qDebug() << "The vertex is a sector border (with exceptions):" + << embedding->IsSectorBorder(bvhcurr, mih_list); + base_mesh->status(bvhcurr).set_selected(true); + embedding->MarkVertex(bvhcurr); + qDebug() << "Marking the middle of the patch"; + embedding->MarkVertex(meta_mesh->property(embedding->mv_connection_, mvh)); + debug_hard_stop_ = true; + return; + } + + //qDebug() << "Finding Neighbors"; + // Find neighbors, limited by sector border + for (auto boheh : base_mesh->voh_range(bvhcurr)) { + if (!embedding->IsSectorBorder(base_mesh->to_vertex_handle(boheh), mih_list)) { + if (base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)).size() < it+1) { + base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)).push_back( + base_mesh->property(*distances, bvhcurr).at(it) + + base_mesh->calc_edge_length(boheh)); + assert(base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)).size() + == it+1); + minheap.push(std::make_pair(base_mesh->to_vertex_handle(boheh), + base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)))); + } else if (base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)).at(it) > + base_mesh->property(*distances, bvhcurr).at(it) + base_mesh->calc_edge_length(boheh)) { + base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)).at(it) + = base_mesh->property(*distances, bvhcurr).at(it) + base_mesh->calc_edge_length(boheh); + minheap.push(std::make_pair(base_mesh->to_vertex_handle(boheh), + base_mesh->property(*distances, base_mesh->to_vertex_handle(boheh)))); + } + } + } + } + ++it; + } + // The new, faster relocate method sadly cannot yet handle complex patches. + // Make sure to use the old method if a patch is complex, so check that here. + bool simple = true; + for (auto moh : meta_mesh->voh_range(mvh)) { + auto mvhit = meta_mesh->to_vertex_handle(moh); + if (mvhit == mvh) { + simple = false; + } + } + if (simple) { + embedding->Relocate(mvh, bvhmin); + } else { + embedding->Relocate(mvh, bvhmin, false); + } + if (embedding->ErrStatus()) { + qDebug() << "Relocation failed for vertex " << mih_list.front().idx(); + debug_hard_stop_ = true; + } +} + +/*! + * \brief IsotropicRemesher::DistanceScoreVW + * \param embedding + * \param distances + * \param bvh + * \param numv + * \return the distance score for vertex weight smoothing; the sum of the quadratic + * distances from the patch vertices (minimize this) + */ +double IsotropicRemesher::DistanceScoreVW(Embedding *embedding, + const OpenMesh::VPropHandleT<std::vector<double>>* distances, + OpenMesh::VertexHandle bvh, uint numv) { + const double inf = std::numeric_limits<double>::infinity(); + auto base_mesh = embedding->GetBaseMesh(); + if (base_mesh->property(*distances, bvh).size() < numv) { + return inf; + } + double retval = 0; + for (double num : base_mesh->property(*distances, bvh)) { + retval += num*num; + } + if (base_mesh->is_valid_handle(base_mesh->property(embedding->bsplithandle_, bvh))) { + retval *= 2; + } + return retval; +} + +/*! + * \brief IsotropicRemesher::DistanceScoreVD + * \param embedding + * \param distances + * \param bvh + * \param numv + * \return the distance score for vertex distance smoothing; returns the highest distance + * bvh has from any of the patch vertices; (minimize this) + */ +double IsotropicRemesher::DistanceScoreVD(Embedding *embedding, + const OpenMesh::VPropHandleT<std::vector<double>>* distances, + OpenMesh::VertexHandle bvh, uint numv) { + const double inf = std::numeric_limits<double>::infinity(); + auto base_mesh = embedding->GetBaseMesh(); + if (base_mesh->property(*distances, bvh).size() < numv) { + return inf; + } + double retval = 0; + for (double num : base_mesh->property(*distances, bvh)) { + if (num > retval) { + retval = num; + } + } + if (base_mesh->is_valid_handle(base_mesh->property(embedding->bsplithandle_, bvh))) { + retval *= 2; + } + return retval; +} + +/*! + * \brief IsotropicRemesher::SmoothingFF + * \param embedding + */ +void IsotropicRemesher::SmoothingFF(Embedding *embedding) { + const double inf = std::numeric_limits<double>::infinity(); + auto meta_mesh = embedding->GetMetaMesh(); + auto base_mesh = embedding->GetBaseMesh(); + OpenMesh::VPropHandleT<double> distance; + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> direction; + base_mesh->add_property(distance, "Distance from the patch border"); + base_mesh->add_property(direction, "Direction to aquire first neighbors"); + std::queue<OpenMesh::VertexHandle> mvhlist; + for (auto mvh : meta_mesh->vertices()) { + mvhlist.push(mvh); + } + while (!mvhlist.empty()) { + auto mvh = mvhlist.front(); + mvhlist.pop(); + if (meta_mesh->is_valid_handle(mvh) + && !meta_mesh->status(mvh).deleted()) { + if (mvh.idx()%10 == 0) { + qDebug() << "Smoothing meta vertex " << mvh.idx(); + } + for (auto bvh : base_mesh->vertices()) { + base_mesh->property(distance, bvh) = inf; + base_mesh->property(direction, bvh) = + OpenMesh::PolyConnectivity::InvalidHalfedgeHandle; + } + std::list<OpenMesh::HalfedgeHandle> mih_list; + for (auto mhehit : meta_mesh->vih_range(mvh)) { + mih_list.push_back(mhehit); + } + SmoothVertexFF(embedding, distance, direction, mih_list); + if (debug_hard_stop_) return; + embedding->CleanUpBaseMesh(); + embedding->MetaGarbageCollection(); + } + } + base_mesh->remove_property(distance); + base_mesh->remove_property(direction); +} + +/*! + * \brief IsotropicRemesher::SmoothVertexFF + * \param embedding + * \param distance: property used for smoothing + * \param direction: property used for smoothing + * \param mih_list: list of incoming meta halfedges towards the center of the patch + */ +void IsotropicRemesher::SmoothVertexFF(Embedding *embedding, + OpenMesh::VPropHandleT<double> distance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> direction, + std::list<OpenMesh::HalfedgeHandle> mih_list) { + const double inf = std::numeric_limits<double>::infinity(); + auto meta_mesh = embedding->GetMetaMesh(); + auto base_mesh = embedding->GetBaseMesh(); + auto mvh = meta_mesh->to_vertex_handle(mih_list.front()); + struct cmp { + bool operator()(const std::pair<OpenMesh::VertexHandle, double> &a, + const std::pair<OpenMesh::VertexHandle, double> &b) { + return a.second > b.second; + } + }; + std::priority_queue<std::pair<OpenMesh::VertexHandle, double>, + std::vector<std::pair<OpenMesh::VertexHandle, double>>, cmp> minheap; + + for (auto mhehit : mih_list) { + auto mheprev = meta_mesh->prev_halfedge_handle(mhehit); + while (meta_mesh->from_vertex_handle(mheprev) != mvh) { + auto bhehit = meta_mesh->property(embedding->mhe_connection_, mheprev); + assert(bhehit.is_valid()); + assert(base_mesh->is_valid_handle(bhehit)); + auto bvhit = base_mesh->from_vertex_handle(bhehit); + base_mesh->property(distance, bvhit) = 0.0; + base_mesh->property(direction, bvhit) = bhehit; + minheap.push(std::make_pair(bvhit, 0.0)); + while (base_mesh->property(embedding->next_heh_, bhehit).is_valid()) { + bhehit = base_mesh->property(embedding->next_heh_, bhehit); + bvhit = base_mesh->from_vertex_handle(bhehit); + base_mesh->property(distance, bvhit) = 0.0; + base_mesh->property(direction, bvhit) = bhehit; + minheap.push(std::make_pair(bvhit, 0.0)); + } + mheprev = meta_mesh->prev_halfedge_handle(mheprev); + } + } + + OpenMesh::VertexHandle middle = OpenMesh::PolyConnectivity::InvalidVertexHandle; + double maxdist = -inf; + + while (!minheap.empty()) { + auto bvcurr = minheap.top().first; + auto neighbors = ClosedNeighborhoodFF(embedding, bvcurr, distance, direction, mih_list); + if (!neighbors.empty()) { + for (auto neighbor : neighbors) { + //base_mesh->status(base_mesh->find_halfedge(bvcurr, neighbor)).set_selected(true); + minheap.push(std::make_pair(neighbor, base_mesh->property(distance, neighbor))); + } + } + double newdist = minheap.top().second; + if (base_mesh->is_valid_handle(base_mesh->property(embedding->bsplithandle_, + minheap.top().first))) { + newdist *= 2; + } + if (maxdist < newdist) { + maxdist = newdist; + middle = minheap.top().first; + } + minheap.pop(); + } + + assert(middle.is_valid()); + + embedding->Relocate(meta_mesh->to_vertex_handle(mih_list.front()), middle); + if (embedding->ErrStatus()) { + qDebug() << "Relocation failed for vertex " << mih_list.front().idx(); + debug_hard_stop_ = true; + } + //qDebug() << "Finished Relocating"; +} + +/*! + * \brief IsotropicRemesher::ClosedNeighborhoodFF + * \param embedding + * \param bvh + * \param distance + * \param direction + * \param mih_list + * \return the vertices in the neighborhood of bvh, considering patch borders + */ +std::vector<OpenMesh::VertexHandle> IsotropicRemesher::ClosedNeighborhoodFF( + Embedding* embedding, OpenMesh::VertexHandle bvh, + OpenMesh::VPropHandleT<double> distance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> direction, + std::list<OpenMesh::HalfedgeHandle> mih_list) { + auto base_mesh = embedding->GetBaseMesh(); + auto meta_mesh = embedding->GetMetaMesh(); + std::vector<OpenMesh::VertexHandle> retval; + auto bdistance = base_mesh->property(distance, bvh); + auto mvh = meta_mesh->to_vertex_handle(mih_list.front()); + if (embedding->IsSectorBorder(bvh, mih_list) || + (base_mesh->property(embedding->bv_connection_, bvh).is_valid() + && base_mesh->property(embedding->bv_connection_, bvh) != mvh)) { + auto bheh0 = base_mesh->property(direction, bvh); + if (!base_mesh->is_valid_handle(bheh0)) { + bheh0 = base_mesh->halfedge_handle(bvh); + } + auto bhehit = base_mesh->opposite_halfedge_handle(base_mesh->prev_halfedge_handle(bheh0)); + auto start = bhehit; + do { + auto blength = base_mesh->calc_edge_length(bhehit); + auto bvdis = base_mesh->property(distance, bvh); + auto itdis = base_mesh->property(distance, base_mesh->to_vertex_handle(bhehit)); + if (bvdis + blength < itdis) { + base_mesh->property(distance, base_mesh->to_vertex_handle(bhehit)) = bvdis + blength; + retval.push_back(base_mesh->to_vertex_handle(bhehit)); + } + bhehit = base_mesh->opposite_halfedge_handle(base_mesh->prev_halfedge_handle(bhehit)); + } while(start != bhehit + && !embedding->IsSectorBorder(base_mesh->to_vertex_handle(bhehit), mih_list)); + } else { + for (auto bvhit : base_mesh->vv_range(bvh)) { + if (!embedding->IsSectorBorder(bvhit, mih_list)) { + auto blength = base_mesh->calc_edge_length(base_mesh->find_halfedge(bvhit, bvh)); + auto itdistance = base_mesh->property(distance, bvhit); + if (bdistance + blength < itdistance) { + itdistance = bdistance + blength; + base_mesh->property(distance, bvhit) = itdistance; + retval.push_back(bvhit); + } + } + } + } + return retval; +} + +/*! + * \brief IsotropicRemesher::Straightening + * \param embedding + * \param strtype + */ +void IsotropicRemesher::Straightening(Embedding *embedding, StraighteningType strtype) { + auto meta_mesh = embedding->GetMetaMesh(); + auto base_mesh = embedding->GetBaseMesh(); + uint ctr = 0; + switch(strtype) { + case NONE: { + break; + } + case IMPLICIT: { + // Straightening handled in smoothing by moving vertices twice. + break; + } + case SIMPLE: { + // Straighten edgewise + std::vector<OpenMesh::EdgeHandle> medges; + for (auto meh : meta_mesh->edges()) { + medges.push_back(meh); + } + std::random_shuffle(medges.begin(), medges.end()); + for (auto meh : medges) { + if (ctr%100 == 0) { + qDebug() << "Straightening edge " << ctr; + } + embedding->Retrace(meta_mesh->halfedge_handle(meh, 0)); + ++ctr; + if (ctr%20 == 0) { + embedding->CleanUpBaseMesh(); + } + } + break; + } + case PATCHWISE: { + // Straighten patchwise + std::vector<OpenMesh::VertexHandle> mverts; + for (auto mvh : meta_mesh->vertices()) { + mverts.push_back(mvh); + } + std::random_shuffle(mverts.begin(), mverts.end()); + for (auto mvh : mverts) { + if (ctr%10 == 0) { + qDebug() << "Straightening vertex " << ctr; + } + if (!meta_mesh->status(mvh).deleted()) { + if (embedding->ErrStatus()) { + debug_hard_stop_ = true; + return; + } + + // Non-original vertices may slip away upon cleanup if the patch around them + // is cleared. In that case cleanup that patch edgewise + if (base_mesh->is_valid_handle(base_mesh->property(embedding->bsplithandle_, + meta_mesh->property(embedding->mv_connection_, mvh)))) { + for (auto moh : meta_mesh->voh_range(mvh)) { + embedding->Retrace(moh); + } + } else { + enum facetype{noselfedgesdisc, selfedgesorhighergenus, boundary}; + facetype ft = noselfedgesdisc; + for (auto moh : meta_mesh->voh_range(mvh)) { + auto mvhit = meta_mesh->to_vertex_handle(moh); + if (mvhit == mvh) { + ft = selfedgesorhighergenus; + } + if (meta_mesh->is_boundary(meta_mesh->to_vertex_handle(moh)) + && ft == noselfedgesdisc) { + ft = boundary; + } + } + switch(ft) { + case noselfedgesdisc: { + for (auto moh : meta_mesh->voh_range(mvh)) { + embedding->RemoveMetaEdgeFromBaseMesh(moh); + } + embedding->BaseGarbageCollection(); + for (auto moh : meta_mesh->voh_range(mvh)) { + embedding->Trace(moh); + } + break; + } + case boundary: { + for (auto moh : meta_mesh->voh_range(mvh)) { + embedding->RemoveMetaEdgeFromBaseMesh(moh); + } + embedding->BaseGarbageCollection(); + auto mheh0 = meta_mesh->halfedge_handle(mvh); + auto mheh1 = meta_mesh->prev_halfedge_handle(mheh0); + embedding->Trace(mheh0); + if (mheh0 != mheh1) { + embedding->Trace(mheh1); + } + for (auto moh : meta_mesh->voh_range(mvh)) { + if (moh != mheh0 && moh != mheh1) { + embedding->Trace(moh); + } + } + break; + } + case selfedgesorhighergenus: { + for (auto moh : meta_mesh->voh_range(mvh)) { + embedding->Retrace(moh); + } + break; + } + } + } + } + if (ctr%20 == 0) { + embedding->CleanUpBaseMesh(); + } + ++ctr; + } + break; + } + } +} diff --git a/IsotropicRemesher.hh b/IsotropicRemesher.hh new file mode 100644 index 0000000000000000000000000000000000000000..2c531f45bfe6374414132f0a4542ae14e2246d12 --- /dev/null +++ b/IsotropicRemesher.hh @@ -0,0 +1,67 @@ +#pragma once + +#include <OpenMesh/Core/Mesh/PolyConnectivity.hh> +#include <ObjectTypes/PolyMesh/PolyMesh.hh> +#include <ObjectTypes/TriangleMesh/TriangleMesh.hh> +#include <OpenFlipper/common/Types.hh> +#include "Embedding.hh" +#include "Stopwatch.hh" + +#include <functional> +#include <map> + +/*! + * \brief The IsotropicRemesher class + */ +class IsotropicRemesher { +public: + IsotropicRemesher() {} + ~IsotropicRemesher() {} + + enum SmoothingType{FORESTFIRE, VERTEXWEIGHTS, VERTEXDISTANCES}; + enum StraighteningType{NONE, IMPLICIT, SIMPLE, PATCHWISE}; + enum CollapsingOrder{RANDOM, VALENCE, SPLITWEIGHT}; + + void Remesh(Embedding* embedding, + double target_length, + uint iterations = 1, + double alpha = 4.0/3.0, + double beta = 4.0/5.0, + SmoothingType smtype = VERTEXWEIGHTS, + std::function<void (QString, QString, double, double)> screenshot = {}, + bool limitflips = false, + StraighteningType strtype = PATCHWISE, + CollapsingOrder corder = SPLITWEIGHT); + bool DebugStopStatus() {return debug_hard_stop_;} + +private: + void Splits(Embedding* embedding, double high); + void Collapses(Embedding* embedding, double low, CollapsingOrder corder = SPLITWEIGHT); + void Flips(Embedding* embedding, bool limitflips = false); + int FlipEval(Embedding* embedding, OpenMesh::EdgeHandle meh); + void Smoothing(Embedding* embedding, SmoothingType stype = FORESTFIRE, bool shakeup = true); + void SmoothingVWD(Embedding* embedding, SmoothingType stype, bool shakeup = true); + void SmoothVertexVWD(Embedding* embedding, + const OpenMesh::VPropHandleT<std::vector<double>>* distances, + OpenMesh::VertexHandle mvh, + SmoothingType stype, + bool shakeup = true); + double DistanceScoreVW(Embedding* embedding, + const OpenMesh::VPropHandleT<std::vector<double>>* distances, + OpenMesh::VertexHandle bvh, uint numv); + double DistanceScoreVD(Embedding* embedding, + const OpenMesh::VPropHandleT<std::vector<double>>* distances, + OpenMesh::VertexHandle bvh, uint numv); + void SmoothingFF(Embedding* embedding); + void SmoothVertexFF(Embedding* embedding, OpenMesh::VPropHandleT<double> distance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> direction, + std::list<OpenMesh::HalfedgeHandle> mih_list); + std::vector<OpenMesh::VertexHandle> ClosedNeighborhoodFF(Embedding* embedding, + OpenMesh::VertexHandle bvh, OpenMesh::VPropHandleT<double> distance, + OpenMesh::VPropHandleT<OpenMesh::HalfedgeHandle> direction, + std::list<OpenMesh::HalfedgeHandle> mih_list); + void Straightening(Embedding* embedding, StraighteningType strtype = PATCHWISE); + + + bool debug_hard_stop_ = false; +}; diff --git a/MetaMeshPlugin.cc b/MetaMeshPlugin.cc index fe23121887f280b737060aff4eb8667d0edada7b..a854c7a495302a5800084bc573da48e66c877174 100644 --- a/MetaMeshPlugin.cc +++ b/MetaMeshPlugin.cc @@ -4,26 +4,761 @@ #include <ObjectTypes/TriangleMesh/TriangleMesh.hh> #include <OpenFlipper/BasePlugin/PluginFunctions.hh> +#include <iostream> +#include <QDebug> + +/*! + * \brief MetaMeshPlugin::initializePlugin + */ void MetaMeshPlugin::initializePlugin() { widget_ = new MetaMeshToolbox(); - connect(widget_->button_dispatch, SIGNAL(clicked()), this, SLOT(dispatch())); + connect(widget_->button_triangulate_, SIGNAL(clicked()), this, SLOT(triangulate())); + connect(widget_->button_randomize_ratio_, SIGNAL(clicked()), this, + SLOT(randomizeratio())); + connect(widget_->button_randomize_total_, SIGNAL(clicked()), this, + SLOT(randomizetotal())); + connect(widget_->button_run_test_, SIGNAL(clicked()), this, SLOT(test_meta_mesh())); + connect(widget_->button_retrace_, SIGNAL(clicked()), this, SLOT(retrace())); + connect(widget_->button_rotate_, SIGNAL(clicked()), this, SLOT(rotate())); + connect(widget_->button_split_, SIGNAL(clicked()), this, SLOT(split())); + connect(widget_->button_collapse_, SIGNAL(clicked()), this, SLOT(collapse())); + connect(widget_->button_relocate_, SIGNAL(clicked()), this, SLOT(relocate())); + connect(widget_->button_remesh_, SIGNAL(clicked()), this, SLOT(remesh())); + connect(widget_->button_dummy1_, SIGNAL(clicked()), this, SLOT(DummySlotFunction1())); + connect(widget_->button_dummy2_, SIGNAL(clicked()), this, SLOT(DummySlotFunction2())); + connect(widget_->button_next_, SIGNAL(clicked()), this, SLOT(nxt())); + connect(widget_->button_opp_, SIGNAL(clicked()), this, SLOT(opp())); + connect(widget_->button_prev_, SIGNAL(clicked()), this, SLOT(prv())); emit addToolbox("MetaMesh", widget_); + + printf("Trying to create MetaMesh object\n"); + embedding_ = new Embedding(); + testing_ = new Testing(embedding_); + printf("Created MetaMesh object\n"); } +/*! + * \brief MetaMeshPlugin::pluginsInitialized + */ void MetaMeshPlugin::pluginsInitialized() { + printf("Plugins Initialized\n"); +} +/*! + * \brief MetaMeshPlugin::slotObjectUpdated + * \param _identifier + */ +void MetaMeshPlugin::slotObjectUpdated(int _identifier) +{ + if (_identifier == meta_mesh_id_ && !selection_mutex_ + && widget_->checkbox_copy_markings_->checkState() == Qt::Checked) { + selection_mutex_ = true; + copyselection(); + selection_mutex_ = false; + } } -void MetaMeshPlugin::dispatch() +/*! + * \brief MetaMeshPlugin::triangulate triangulates a meta mesh from marked vertices on the currently + * loaded mesh + */ +void MetaMeshPlugin::triangulate() { - using namespace PluginFunctions; - for (auto* o : objects(TARGET_OBJECTS, DATA_TRIANGLE_MESH)) { - TriMesh& mesh = *triMesh(o); - // ... - } + qDebug() << "Triangulate Button pressed"; + using namespace PluginFunctions; + for (auto* o : objects(TARGET_OBJECTS, DATA_TRIANGLE_MESH)) { + emit addEmptyObject(DATA_POLY_MESH, meta_mesh_id_); + base_mesh_id_ = o->id(); + assert(meta_mesh_id_ >= 0); + meta_mesh_ = polyMesh(meta_mesh_id_); + meta_mesh_->clear(); + TriMesh& base_mesh = *triMesh(o); + + qDebug() << "Call triangulation on MetaMesh in object loop"; + embedding_->SelectionTriangulation(base_mesh, *meta_mesh_); + meta_mesh_->update_normals(); + embedding_->GetBaseMesh()->update_normals(); + + //meta_mesh_->clear(); + for (auto bvh : embedding_->GetBaseMesh()->vertices()) { + embedding_->GetBaseMesh()->status(bvh).set_selected(false); + } + emit updatedObject(o->id(), UPDATE_ALL); + qDebug() << "Exited InitialTriangulation function"; + } + if (embedding_->ErrStatus()) { + widget_->checkbox_copy_markings_->setCheckState(Qt::Unchecked); + } + calculateoffset(); + addoffset(); + embedding_->ColorizeMetaMesh(); + emit updatedObject(base_mesh_id_, UPDATE_ALL); + emit updatedObject(meta_mesh_id_, UPDATE_ALL); +} + +/*! + * \brief MetaMeshPlugin::randomizetotal randomize an absolute number of vertices + */ +void MetaMeshPlugin::randomizetotal() { + randomize(Embedding::RandomType::TOTAL); +} + +/*! + * \brief MetaMeshPlugin::randomizeratio randomize a ratio of vertices + */ +void MetaMeshPlugin::randomizeratio() { + randomize(Embedding::RandomType::RATIO); +} + +/*! + * \brief MetaMeshPlugin::randomize randomize meta mesh vertices and triangulate + * \param type + */ +void MetaMeshPlugin::randomize(Embedding::RandomType type) { + using namespace PluginFunctions; + for (auto* o : objects(TARGET_OBJECTS, DATA_TRIANGLE_MESH)) { + emit addEmptyObject(DATA_POLY_MESH, meta_mesh_id_); + base_mesh_id_ = o->id(); + assert(meta_mesh_id_ >= 0); + meta_mesh_ = polyMesh(meta_mesh_id_); + meta_mesh_->clear(); + TriMesh& base_mesh = *triMesh(o); + + qDebug() << "Call triangulation on MetaMesh in object loop"; + embedding_->RandomTriangulation(base_mesh, *meta_mesh_, + widget_->input_ratio_->value(), type); + meta_mesh_->update_normals(); + embedding_->GetBaseMesh()->update_normals(); + emit updatedObject(o->id(), UPDATE_ALL); + qDebug() << "Exited InitialTriangulation function"; + } + if (embedding_->ErrStatus()) { + widget_->checkbox_copy_markings_->setCheckState(Qt::Unchecked); + } + calculateoffset(); + addoffset(); + embedding_->ColorizeMetaMesh(); + emit updatedObject(meta_mesh_id_, UPDATE_ALL); +} + +/*! + * \brief MetaMeshPlugin::test_meta_mesh run selected test + */ +void MetaMeshPlugin::test_meta_mesh() { + using namespace PluginFunctions; + if (embedding_->GetMetaMesh() == nullptr) { + qDebug() << "Run some triangulation before calling tests on the meta mesh."; + return; + } + + preoperation(); + + switch (widget_->combo_box_tests_->currentIndex()) { + case 0: { + testing_->DisplayMeshInformation(); + break; + } + case 1: { + testing_->MeshTests(); + break; + } + case 2: { + testing_->OperationTests(); + break; + } + case 3: { + testing_->TestFlips(); + break; + } + case 4: { + testing_->TestSplits(); + break; + } + case 5: { + testing_->TestCollapses(); + break; + } + case 6: { + testing_->TestRelocation(); + } + } + if (embedding_->ErrStatus()) { + widget_->checkbox_copy_markings_->setCheckState(Qt::Unchecked); + embedding_->GetBaseMesh()->update_normals(); + emit updatedObject(base_mesh_id_, UPDATE_ALL); + addoffset(); + embedding_->ColorizeMetaMesh(); + return; + } + + postoperation(UPDATE_ALL, UPDATE_ALL); +} + +/*! + * \brief MetaMeshPlugin::retrace retrace the selected meta edges / halfedges. None selected means + * everything will be retraced. + */ +void MetaMeshPlugin::retrace() { + using namespace PluginFunctions; + preoperation(); + auto retracequeue = GetHalfedgeSelection(); + bool emptyqueue = retracequeue.empty(); + while (!retracequeue.empty()) { + embedding_->Retrace(retracequeue.front(), true); + retracequeue.pop(); + } + + // If nothing is selected retrace everything + if (emptyqueue) { + for (auto meh : embedding_->GetMetaMesh()->edges()) { + auto mheh = embedding_->GetMetaMesh()->halfedge_handle(meh, 0); + embedding_->Retrace(mheh); + } + } + + postoperation(UPDATE_ALL, UPDATE_TOPOLOGY); +} + +/*! + * \brief MetaMeshPlugin::rotate rotate all selected meta edges and halfedges + */ +void MetaMeshPlugin::rotate() { + using namespace PluginFunctions; + preoperation(); + auto rotationqueue = GetHalfedgeSelection(); + + while (!rotationqueue.empty()) { + embedding_->Rotate(embedding_->GetMetaMesh()->edge_handle(rotationqueue.front())); + rotationqueue.pop(); + } + + postoperation(UPDATE_ALL, UPDATE_TOPOLOGY); +} + +/*! + * \brief MetaMeshPlugin::split split faces and edges at selected base vertices + */ +void MetaMeshPlugin::split() { + using namespace PluginFunctions; + preoperation(); + + for (auto bvh : embedding_->GetBaseMesh()->vertices()) { + if (embedding_->GetBaseMesh()->status(bvh).selected()) { + embedding_->Split(bvh); + embedding_->GetBaseMesh()->status(bvh).set_selected(false); + } + } + + postoperation(UPDATE_TOPOLOGY, UPDATE_TOPOLOGY); +} + +/*! + * \brief MetaMeshPlugin::collapse collapse all selected meta edges and halfedges + */ +void MetaMeshPlugin::collapse() { + using namespace PluginFunctions; + preoperation(); + + auto collapsequeue = GetHalfedgeSelection(); + while (!collapsequeue.empty()) { + embedding_->Collapse(collapsequeue.front(), true); + collapsequeue.pop(); + } + + postoperation(UPDATE_TOPOLOGY, UPDATE_TOPOLOGY); +} + +/*! + * \brief MetaMeshPlugin::relocate relocate selected meta vertex to selected non-meta vertex + */ +void MetaMeshPlugin::relocate() { + using namespace PluginFunctions; + preoperation(); + + OpenMesh::VertexHandle mvh = OpenMesh::PolyConnectivity::InvalidVertexHandle; + OpenMesh::VertexHandle bvh = OpenMesh::PolyConnectivity::InvalidVertexHandle; + for (auto bvh_iter : embedding_->GetBaseMesh()->vertices()) { + if (embedding_->GetBaseMesh()->status(bvh_iter).selected()) { + if (embedding_->GetBaseMesh()->property(embedding_->bv_connection_, bvh_iter).is_valid()) { + mvh = embedding_->GetBaseMesh()->property(embedding_->bv_connection_, bvh_iter); + } else { + bvh = bvh_iter; + } + embedding_->GetBaseMesh()->status(bvh_iter).set_selected(false); + } + } + for (auto mvh_iter : embedding_->GetMetaMesh()->vertices()) { + if (embedding_->GetMetaMesh()->status(mvh_iter).selected()) { + mvh = mvh_iter; + embedding_->GetMetaMesh()->status(mvh_iter).set_selected(false); + } + } + if (bvh.is_valid() && mvh.is_valid()) { + embedding_->Relocate(mvh, bvh, true); + } + + postoperation(UPDATE_TOPOLOGY, UPDATE_TOPOLOGY); +} + +/*! + * \brief MetaMeshPlugin::remesh calls IsotropicRemesher::Remesh with menu selected parameters + */ +void MetaMeshPlugin::remesh() { + using namespace PluginFunctions; + preoperation(); + + IsotropicRemesher remesher; + auto take_screenshot = [&](const QString& name, const QString& dir, + const int& width = 0, const int& height = 0) { + screenshot(name, dir, width, height); + }; + + switch(widget_->combo_box_remeshing_->currentIndex()) { + case IsotropicRemesher::FORESTFIRE: { + remesher.Remesh(embedding_, widget_->input_length_->value(), + static_cast<uint>(widget_->input_iterations_->value()), 4.0/3.0, 4.0/5.0, + IsotropicRemesher::FORESTFIRE, take_screenshot); + break; + } + case IsotropicRemesher::VERTEXWEIGHTS:{ + remesher.Remesh(embedding_, widget_->input_length_->value(), + static_cast<uint>(widget_->input_iterations_->value()), 4.0/3.0, 4.0/5.0, + IsotropicRemesher::VERTEXWEIGHTS, take_screenshot); + break; + } + case IsotropicRemesher::VERTEXDISTANCES:{ + remesher.Remesh(embedding_, widget_->input_length_->value(), + static_cast<uint>(widget_->input_iterations_->value()), 4.0/3.0, 4.0/5.0, + IsotropicRemesher::VERTEXDISTANCES, take_screenshot); + break; + } + } + + postoperation(UPDATE_TOPOLOGY, UPDATE_TOPOLOGY); +} + +/*! + * \brief MetaMeshPlugin::calculateoffset calculate an offset to separate base and meta mesh. + */ +void MetaMeshPlugin::calculateoffset() { + const double inf = std::numeric_limits<double>::infinity(); + ACG::Vec3d minCorner = {+inf, +inf, +inf}; + ACG::Vec3d maxCorner = {-inf, -inf, -inf}; + for (auto bvh : embedding_->GetBaseMesh()->vertices()) { + auto p = embedding_->GetBaseMesh()->point(bvh); + maxCorner.maximize(p); + minCorner.minimize(p); + } + meta_mesh_visualization_offset_[0] = (maxCorner[0]-minCorner[0])*1.3; +} + +/*! + * \brief MetaMeshPlugin::addoffset move the meta mesh away from the base mesh + */ +void MetaMeshPlugin::addoffset() { + for (auto mvh : embedding_->GetMetaMesh()->vertices()) { + embedding_->GetMetaMesh()->point(mvh) += meta_mesh_visualization_offset_/2.0; + } + for (auto bvh : embedding_->GetBaseMesh()->vertices()) { + embedding_->GetBaseMesh()->point(bvh) -= meta_mesh_visualization_offset_/2.0; + } +} + +/*! + * \brief MetaMeshPlugin::removeoffset move the meta mesh back to the base mesh + */ +void MetaMeshPlugin::removeoffset() { + for (auto mvh : embedding_->GetMetaMesh()->vertices()) { + embedding_->GetMetaMesh()->point(mvh) -= meta_mesh_visualization_offset_/2.0; + } + for (auto bvh : embedding_->GetBaseMesh()->vertices()) { + embedding_->GetBaseMesh()->point(bvh) += meta_mesh_visualization_offset_/2.0; + } +} + +/*! + * \brief MetaMeshPlugin::copyselection copy selected elements from the meta mesh + * into the base mesh (select the corresponding elements) + */ +void MetaMeshPlugin::copyselection() { + if (!metameshsanitychecks(false)) + return; + using namespace PluginFunctions; + removeoffset(); + for (auto bheh : embedding_->GetBaseMesh()->halfedges()) { + embedding_->GetBaseMesh()->status(bheh).set_selected(false); + } + for (auto mheh : embedding_->GetMetaMesh()->halfedges()) { + auto bheh = embedding_->GetMetaMesh()->property(embedding_->mhe_connection_, mheh); + embedding_->GetBaseMesh()->status(bheh).set_selected( + embedding_->GetMetaMesh()->status(mheh).selected()); + while (embedding_->GetBaseMesh()->status(bheh).selected() && + embedding_->GetBaseMesh()->property(embedding_->next_heh_, bheh) + != OpenMesh::PolyConnectivity::InvalidHalfedgeHandle) { + bheh = embedding_->GetBaseMesh()->property(embedding_->next_heh_, bheh); + embedding_->GetBaseMesh()->status(bheh).set_selected(true); + } + } + for (auto beh : embedding_->GetBaseMesh()->edges()) { + embedding_->GetBaseMesh()->status(beh).set_selected(false); + } + for (auto meh : embedding_->GetMetaMesh()->edges()) { + auto bheh = embedding_->GetMetaMesh()->property(embedding_->mhe_connection_, + embedding_->GetMetaMesh()->halfedge_handle(meh, 0)); + auto beh = embedding_->GetBaseMesh()->edge_handle(bheh); + embedding_->GetBaseMesh()->status(beh).set_selected( + embedding_->GetMetaMesh()->status(meh).selected()); + while (embedding_->GetBaseMesh()->status(beh).selected() && + embedding_->GetBaseMesh()->property(embedding_->next_heh_, bheh) + != OpenMesh::PolyConnectivity::InvalidHalfedgeHandle) { + bheh = embedding_->GetBaseMesh()->property(embedding_->next_heh_, bheh); + auto beh = embedding_->GetBaseMesh()->edge_handle(bheh); + embedding_->GetBaseMesh()->status(beh).set_selected(true); + } + } + + embedding_->GetBaseMesh()->garbage_collection(); + + addoffset(); + emit updatedObject(base_mesh_id_, UPDATE_SELECTION); + UpdateMetaMesh(UPDATE_SELECTION); +} + +/*! + * \brief MetaMeshPlugin::DummySlotFunction1 function for the first dummy button, + * used to test various things on the mesh + */ +void MetaMeshPlugin::DummySlotFunction1() { + using namespace PluginFunctions; + for (auto bheh : embedding_->GetBaseMesh()->halfedges()) { + if (embedding_->GetMetaMesh()->is_valid_handle( + embedding_->GetBaseMesh()->property( + embedding_->bhe_connection_, bheh))) { + embedding_->GetBaseMesh()->status(bheh).set_selected(true); + } + } + emit updatedObject(base_mesh_id_, UPDATE_SELECTION); +} + +/*! + * \brief MetaMeshPlugin::DummySlotFunction2 function for the first dummy button, + * used to test various things on the mesh + */ +void MetaMeshPlugin::DummySlotFunction2() { + using namespace PluginFunctions; + for (auto mheh : meta_mesh_->halfedges()) { + if (meta_mesh_->is_boundary(mheh)) { + meta_mesh_->status(mheh).set_selected(true); + } + } + emit updatedObject(meta_mesh_id_, UPDATE_SELECTION); + emit updatedObject(base_mesh_id_, UPDATE_SELECTION); +} + +/*! + * \brief MetaMeshPlugin::nxt move all selections on meta halfedges forward to their + * next_halfedge_handle + */ +void MetaMeshPlugin::nxt() { + if (!metameshsanitychecks()) + return; + std::vector<OpenMesh::EdgeHandle> unmark_eh; + std::vector<OpenMesh::HalfedgeHandle> unmark_heh; + std::vector<OpenMesh::EdgeHandle> mark_eh; + std::vector<OpenMesh::HalfedgeHandle> mark_heh; + for (auto meh : embedding_->GetMetaMesh()->edges()) { + if (embedding_->GetMetaMesh()->status(meh).selected()) { + auto mheh = embedding_->GetMetaMesh()->halfedge_handle(meh, 0); + unmark_eh.push_back(meh); + mark_eh.push_back(embedding_->GetMetaMesh()->edge_handle( + embedding_->GetMetaMesh()->next_halfedge_handle(mheh))); + } + } + for (auto mheh : embedding_->GetMetaMesh()->halfedges()) { + if (embedding_->GetMetaMesh()->status(mheh).selected()) { + unmark_heh.push_back(mheh); + mark_heh.push_back(embedding_->GetMetaMesh()->next_halfedge_handle(mheh)); + } + } + for (auto unmark : unmark_eh) { + embedding_->GetMetaMesh()->status(unmark).set_selected(false); + } + for (auto unmark : unmark_heh) { + embedding_->GetMetaMesh()->status(unmark).set_selected(false); + } + for (auto mark : mark_eh) { + embedding_->GetMetaMesh()->status(mark).set_selected(true); + } + for (auto mark : mark_heh) { + embedding_->GetMetaMesh()->status(mark).set_selected(true); + } + UpdateMetaMesh(UPDATE_SELECTION_EDGES); + UpdateMetaMesh(UPDATE_SELECTION_HALFEDGES); +} + +/*! + * \brief MetaMeshPlugin::opp move all selections of meta halfedges to + * their opposite_halfedge_handle + */ +void MetaMeshPlugin::opp() { + if (!metameshsanitychecks()) + return; + std::vector<OpenMesh::EdgeHandle> unmark_eh; + std::vector<OpenMesh::HalfedgeHandle> unmark_heh; + std::vector<OpenMesh::EdgeHandle> mark_eh; + std::vector<OpenMesh::HalfedgeHandle> mark_heh; + for (auto meh : embedding_->GetMetaMesh()->edges()) { + if (embedding_->GetMetaMesh()->status(meh).selected()) { + auto mheh = embedding_->GetMetaMesh()->halfedge_handle(meh, 0); + unmark_eh.push_back(meh); + mark_eh.push_back(embedding_->GetMetaMesh()->edge_handle( + embedding_->GetMetaMesh()->opposite_halfedge_handle(mheh))); + } + } + for (auto mheh : embedding_->GetMetaMesh()->halfedges()) { + if (embedding_->GetMetaMesh()->status(mheh).selected()) { + unmark_heh.push_back(mheh); + mark_heh.push_back(embedding_->GetMetaMesh()->opposite_halfedge_handle(mheh)); + } + } + for (auto unmark : unmark_eh) { + embedding_->GetMetaMesh()->status(unmark).set_selected(false); + } + for (auto unmark : unmark_heh) { + embedding_->GetMetaMesh()->status(unmark).set_selected(false); + } + for (auto mark : mark_eh) { + embedding_->GetMetaMesh()->status(mark).set_selected(true); + } + for (auto mark : mark_heh) { + embedding_->GetMetaMesh()->status(mark).set_selected(true); + } + UpdateMetaMesh(UPDATE_SELECTION_EDGES); + UpdateMetaMesh(UPDATE_SELECTION_HALFEDGES); +} + +/*! + * \brief MetaMeshPlugin::prv move all halfedge selections on the meta mesh + * to their prev_halfedge_handle + */ +void MetaMeshPlugin::prv() { + if (!metameshsanitychecks()) + return; + std::vector<OpenMesh::EdgeHandle> unmark_eh; + std::vector<OpenMesh::HalfedgeHandle> unmark_heh; + std::vector<OpenMesh::EdgeHandle> mark_eh; + std::vector<OpenMesh::HalfedgeHandle> mark_heh; + for (auto meh : embedding_->GetMetaMesh()->edges()) { + if (embedding_->GetMetaMesh()->status(meh).selected()) { + auto mheh = embedding_->GetMetaMesh()->halfedge_handle(meh, 0); + unmark_eh.push_back(meh); + mark_eh.push_back(embedding_->GetMetaMesh()->edge_handle( + embedding_->GetMetaMesh()->prev_halfedge_handle(mheh))); + } + } + for (auto mheh : embedding_->GetMetaMesh()->halfedges()) { + if (embedding_->GetMetaMesh()->status(mheh).selected()) { + unmark_heh.push_back(mheh); + mark_heh.push_back(embedding_->GetMetaMesh()->prev_halfedge_handle(mheh)); + } + } + for (auto unmark : unmark_eh) { + embedding_->GetMetaMesh()->status(unmark).set_selected(false); + } + for (auto unmark : unmark_heh) { + embedding_->GetMetaMesh()->status(unmark).set_selected(false); + } + for (auto mark : mark_eh) { + embedding_->GetMetaMesh()->status(mark).set_selected(true); + } + for (auto mark : mark_heh) { + embedding_->GetMetaMesh()->status(mark).set_selected(true); + } + UpdateMetaMesh(UPDATE_SELECTION_EDGES); + UpdateMetaMesh(UPDATE_SELECTION_HALFEDGES); +} + +/*! + * \brief MetaMeshPlugin::metameshsanitychecks checks a few conditions that make + * visualizing the meta_mesh_ unsafe (crashes the GPUoptimizer or other problems) + * \param verbose + * \return false if the meta mesh should not be virualized + */ +bool MetaMeshPlugin::metameshsanitychecks(bool verbose) { + if (meta_mesh_ == nullptr) { + if (verbose) + qDebug() << "The meta mesh is a null pointer."; + return false; + } + if (meta_mesh_->n_vertices() < 3) { + if (verbose) + qDebug() << "meta_mesh_ n_vertices is " << meta_mesh_->n_vertices() + << "but it needs to be at least 3 to be visualized."; + return false; + } + if (meta_mesh_->n_edges() == 0) { + if (verbose) + qDebug() << "The meta mesh has no edges."; + return false; + } + return true; +} + +/*! + * \brief MetaMeshPlugin::MetaMeshUpdateNormals + */ +void MetaMeshPlugin::MetaMeshUpdateNormals() { + if (metameshsanitychecks(false)) { + meta_mesh_->update_normals(); + } +} + +/*! + * \brief MetaMeshPlugin::EmbeddingGarbageCollection + */ +void MetaMeshPlugin::EmbeddingGarbageCollection() { + if (metameshsanitychecks(false)) { + embedding_->MetaGarbageCollection(); + } +} + +/*! + * \brief MetaMeshPlugin::UpdateMetaMesh + * \param _type + */ +void MetaMeshPlugin::UpdateMetaMesh(const UpdateType &_type) { + if (metameshsanitychecks(false)) { + emit updatedObject(meta_mesh_id_, _type); + } +} + +/*! + * \brief MetaMeshPlugin::GetHalfedgeSelection + * \return a list of selected meta halfedges (when they or their corresponding base + * halfedges are selected) + */ +std::queue<OpenMesh::HalfedgeHandle> MetaMeshPlugin::GetHalfedgeSelection() { + std::queue<OpenMesh::HalfedgeHandle> output; + if (widget_->checkbox_copy_markings_->checkState() == Qt::Checked) { + for (auto mheh : embedding_->GetMetaMesh()->halfedges()) { + if (embedding_->GetMetaMesh()->status(mheh).selected()) { + embedding_->GetMetaMesh()->status(mheh).set_selected(false); + output.push(mheh); + } + } + for (auto meh : embedding_->GetMetaMesh()->edges()) { + if (embedding_->GetMetaMesh()->status(meh).selected()) { + auto mheh = embedding_->GetMetaMesh()->halfedge_handle(meh, 0); + embedding_->GetMetaMesh()->status(mheh).set_selected(false); + output.push(mheh); + } + } + } + if (widget_->checkbox_copy_markings_->checkState() == Qt::Unchecked) { + for (auto bheh : embedding_->GetBaseMesh()->halfedges()) { + if (embedding_->ErrStatus()) { + break; + } + if (embedding_->GetBaseMesh()->status(bheh).selected()) { + embedding_->GetBaseMesh()->status(bheh).set_selected(false); + auto mheh = embedding_->GetBaseMesh()->property(embedding_->bhe_connection_, bheh); + if (mheh.is_valid()) { + output.push(mheh); + } + } + } + for (auto beh : embedding_->GetBaseMesh()->edges()) { + if (embedding_->ErrStatus()) { + break; + } + if (embedding_->GetBaseMesh()->status(beh).selected()) { + auto bheh = embedding_->GetBaseMesh()->halfedge_handle(beh, 0); + embedding_->GetBaseMesh()->status(beh).set_selected(false); + auto mheh = embedding_->GetBaseMesh()->property(embedding_->bhe_connection_, bheh); + if (mheh.is_valid()) { + output.push(mheh); + } + } + } + } + return output; +} + +/*! + * \brief MetaMeshPlugin::screenshot updates view and takes(saves) a screenshot + * this function will be handed over to IsotropicRemesher + * \param name filename + * \param dir directory + * \param width dimension X + * \param height dimension Y + */ +void MetaMeshPlugin::screenshot(const QString& name, const QString& dir, + const int& width, const int& height) { + //qDebug() << "screenshotfunction"; + using namespace PluginFunctions; + // Take viewer snapshot + + QCoreApplication::processEvents(); + EmbeddingGarbageCollection(); + MetaMeshUpdateNormals(); + embedding_->BaseGarbageCollection(); + addoffset(); + QCoreApplication::processEvents(); + embedding_->ColorizeMetaMesh(); + UpdateMetaMesh(UPDATE_ALL); + embedding_->GetBaseMesh()->update_normals(); + emit updatedObject(base_mesh_id_, UPDATE_ALL); + QCoreApplication::processEvents(); + + QImage image; + PluginFunctions::viewerSnapshot(PluginFunctions::ACTIVE_VIEWER, image, width, height); + image.save(dir + QDir::separator() + name + QString(".png"), "PNG"); + + removeoffset(); +} + +/*! + * \brief MetaMeshPlugin::preoperation functions to be called before operations on the + * mesh are performed. Currently only removes the meta mesh offset, but this wrapper + * is here for consistency with postoperation() + */ +void MetaMeshPlugin::preoperation() { + removeoffset(); +} + +/*! + * \brief MetaMeshPlugin::postoperation functions to be called after operations on the + * mesh are performed such as garbage collection, updating normals, adding an offset + * to the meta mesh, colorizing it etc. + * \param typem meta mesh update type eg. UPDATE_SELECTION + * \param typeb base mesh update type eg. UPDATE_TOPOLOGY + * \param verbose + */ +void MetaMeshPlugin::postoperation(const UpdateType &typem, const UpdateType &typeb, + bool verbose) { + if (embedding_->ErrStatus() + || !metameshsanitychecks(verbose)) { + widget_->checkbox_copy_markings_->setCheckState(Qt::Unchecked); + embedding_->GetBaseMesh()->update_normals(); + emit updatedObject(base_mesh_id_, typeb); + addoffset(); + embedding_->ColorizeMetaMesh(); + embedding_->CleanMetaMesh(); + return; + } + embedding_->GetBaseMesh()->update_normals(); + + EmbeddingGarbageCollection(); + MetaMeshUpdateNormals(); + + addoffset(); + embedding_->ColorizeMetaMesh(); + + UpdateMetaMesh(typem); + emit updatedObject(base_mesh_id_, typeb); } diff --git a/MetaMeshPlugin.hh b/MetaMeshPlugin.hh index 83083a5c75faced7a6b91bc8cd15f522186d656b..3d109145648b099423ce61be515d75719f63b826 100644 --- a/MetaMeshPlugin.hh +++ b/MetaMeshPlugin.hh @@ -3,23 +3,31 @@ #include <OpenFlipper/BasePlugin/BaseInterface.hh> #include <OpenFlipper/BasePlugin/ToolboxInterface.hh> #include <OpenFlipper/BasePlugin/LoggingInterface.hh> +#include <OpenFlipper/BasePlugin/LoadSaveInterface.hh> #include <OpenFlipper/common/Types.hh> #include "MetaMeshToolbox.hh" +#include "Embedding.hh" +#include "IsotropicRemesher.hh" +#include "Testing.hh" -class MetaMeshPlugin : public QObject, BaseInterface, ToolboxInterface, LoggingInterface +class MetaMeshPlugin : public QObject, BaseInterface, ToolboxInterface, + LoggingInterface, LoadSaveInterface { - Q_OBJECT - Q_INTERFACES(BaseInterface) + Q_OBJECT + Q_INTERFACES(BaseInterface) Q_INTERFACES(ToolboxInterface) - Q_INTERFACES(LoggingInterface) + Q_INTERFACES(LoggingInterface) + Q_INTERFACES(LoadSaveInterface) Q_PLUGIN_METADATA(IID "org.OpenFlipper.Plugins.Plugin-MetaMesh") signals: + // BaseInterface - void updateView(); - void updatedObject(int _identifier, const UpdateType& _type) override; + void updateView() override; + void updatedObject(int _identifier, const UpdateType& _type) override; + // ToolboxInterface void addToolbox(QString _name, QWidget* _widget) override; @@ -28,20 +36,64 @@ signals: void log(Logtype _type, QString _message) override; void log(QString _message) override; + // LoadSaveInterface + void addEmptyObject(DataType _type, int& _id) override; + public: QString name() override { return QString("MetaMesh"); } - QString description() override { return QString(""); } + QString description() override { return QString(""); } public slots: QString version() override { return QString("1.0"); } - void dispatch(); + void triangulate(); + void randomizetotal(); + void randomizeratio(); + void test_meta_mesh(); + void retrace(); + void rotate(); + void split(); + void collapse(); + void relocate(); + void remesh(); + void togglecopy(); + void copyselection(); + void DummySlotFunction1(); + void DummySlotFunction2(); + void nxt(); + void opp(); + void prv(); private slots: // BaseInterface void initializePlugin() override; void pluginsInitialized() override; + void slotObjectUpdated(int _identifier) override; + private: + bool metameshsanitychecks(bool verbose = true); + void randomize(Embedding::RandomType type); + void calculateoffset(); + void addoffset(); + void removeoffset(); + void MetaMeshUpdateNormals(); + void EmbeddingGarbageCollection(); + void UpdateMetaMesh(const UpdateType &_type); + std::queue<OpenMesh::HalfedgeHandle> GetHalfedgeSelection(); + void screenshot(const QString& name, const QString& dir, + const int& width = 0, const int& height = 0); + void preoperation(); + void postoperation(const UpdateType& typem, const UpdateType &typeb, + bool verbose = false); + + bool selection_mutex_ = false; + MetaMeshToolbox* widget_; + Embedding* embedding_; + Testing* testing_; + int base_mesh_id_=-1; + int meta_mesh_id_=-1; + PolyMesh* meta_mesh_; + ACG::Vec3d meta_mesh_visualization_offset_ = {0.0f, 0.0f, 0.0f}; }; diff --git a/MetaMeshToolbox.cc b/MetaMeshToolbox.cc index 71488796626c3923821bdaf0f3beca1ae1123401..0331249a350f9f0aa01263eb729c559fa11c6bdc 100644 --- a/MetaMeshToolbox.cc +++ b/MetaMeshToolbox.cc @@ -1,12 +1,104 @@ #include "MetaMeshToolbox.hh" -#include <QVBoxLayout> +#include <QGridLayout> +#include <QLabel> MetaMeshToolbox::MetaMeshToolbox(QWidget* parent) : QWidget(parent) { - button_dispatch = new QPushButton("Dispatch"); - - auto* layout = new QVBoxLayout(); - layout->addWidget(button_dispatch); - this->setLayout(layout); + QGridLayout* layout = new QGridLayout(this); + + vbox_ = new QVBoxLayout; + checkbox_implicit_dijkstra_ = new QRadioButton("Implicit Dijkstra from Voronoi"); + checkbox_a_star_triangulation_ = new QRadioButton("Bidirectional A* triangulation"); + checkbox_a_star_midpoint_triangulation_ = new QRadioButton("A* with midpoints from Voronoi"); + mode_selection_ = new QGroupBox("Initial triangulation type"); + checkbox_a_star_triangulation_->setChecked(true); + vbox_->addWidget(checkbox_implicit_dijkstra_); + vbox_->addWidget(checkbox_a_star_triangulation_); + vbox_->addWidget(checkbox_a_star_midpoint_triangulation_); + vbox_->addStretch(1); + mode_selection_->setLayout(vbox_); + + combo_box_tests_ = new QComboBox; + combo_box_tests_->addItem("Display Mesh Info"); + combo_box_tests_->addItem("Mesh Properties"); + combo_box_tests_->addItem("Mesh Operations"); + combo_box_tests_->addItem("Rotations"); + combo_box_tests_->addItem("Splits"); + combo_box_tests_->addItem("Collapses"); + combo_box_tests_->addItem("Relocations"); + + combo_box_remeshing_ = new QComboBox; + combo_box_remeshing_->addItem("Forest Fire"); + combo_box_remeshing_->addItem("Vertex Weights"); + combo_box_remeshing_->addItem("Vertex Distances"); + + button_triangulate_ = new QPushButton("Triangulate"); + button_randomize_ratio_ = new QPushButton("Randomize a ratio of points"); + button_randomize_total_ = new QPushButton("Randomize a number of points"); + button_run_test_ = new QPushButton("Run test"); + checkbox_copy_markings_ = new QCheckBox("Selection Copying"); + button_retrace_ = new QPushButton("Retrace"); + button_rotate_ = new QPushButton("Rotate"); + button_split_ = new QPushButton("Split"); + button_collapse_ = new QPushButton("Collapse"); + button_relocate_ = new QPushButton("Relocate"); + button_dummy1_ = new QPushButton("Insert"); + button_dummy2_ = new QPushButton("Delete"); + button_next_ = new QPushButton("nxt"); + button_opp_ = new QPushButton("opp"); + button_prev_ = new QPushButton("prv"); + button_remesh_ = new QPushButton("Remesh"); + + checkbox_copy_markings_->setCheckState(Qt::Checked); + + input_ratio_ = new QDoubleSpinBox(); + input_ratio_->setRange(0,1); + input_ratio_->setDecimals(4); + input_ratio_->setSingleStep(0.0001); + input_ratio_->setValue(0.003); + input_ratio_->setToolTip("Ratio of Base Mesh Vertices to be " + "randomly assigned as Meta Mesh vertices"); + + input_length_ = new QDoubleSpinBox(); + input_length_->setRange(0,1); + input_length_->setDecimals(3); + input_length_->setSingleStep(0.001); + input_length_->setValue(0.2); + input_length_->setToolTip("Edge length in % of object diagonal"); + + input_iterations_ = new QSpinBox(); + input_iterations_->setRange(1,100); + input_iterations_->setValue(10); + input_iterations_->setSingleStep(1); + input_iterations_->setToolTip("Number of times the algorithm runs"); + + QLabel* label_length = new QLabel("Length:"); + QLabel* label_iterations = new QLabel("Iterations:"); + + layout->addWidget(button_triangulate_, 0, 0, 1, 6); + layout->addWidget(button_randomize_ratio_, 1, 0, 1, 6); + layout->addWidget(button_randomize_total_, 2, 0, 1, 6); + layout->addWidget(mode_selection_, 3, 0, 1, 6); + layout->addWidget(input_ratio_, 4, 0, 1, 6); + layout->addWidget(button_run_test_, 5, 0, 1, 3); + layout->addWidget(combo_box_tests_, 5, 3, 1, 3); + layout->addWidget(checkbox_copy_markings_, 6, 0, 1, 3); + layout->addWidget(button_retrace_, 6, 3, 1, 3); + layout->addWidget(button_rotate_, 7, 0, 1, 3); + layout->addWidget(button_split_, 7, 3, 1, 3); + layout->addWidget(button_collapse_, 8, 0, 1, 3); + layout->addWidget(button_relocate_, 8, 3, 1, 3); + layout->addWidget(button_dummy1_, 9, 0, 1, 3); + layout->addWidget(button_dummy2_, 9, 3, 1, 3); + layout->addWidget(button_next_, 10, 0, 1, 2); + layout->addWidget(button_opp_, 10, 2, 1, 2); + layout->addWidget(button_prev_, 10, 4, 1, 2); + layout->addWidget(button_remesh_, 11, 0, 1, 3); + layout->addWidget(combo_box_remeshing_, 11, 3, 1, 3); + layout->addWidget(label_length, 12, 0, 1, 3); + layout->addWidget(input_length_, 13, 0, 1, 3); + layout->addWidget(label_iterations, 12, 3, 1, 3); + layout->addWidget(input_iterations_, 13, 3, 1, 3); + this->setLayout(layout); } diff --git a/MetaMeshToolbox.hh b/MetaMeshToolbox.hh index 0a224058b2ee7b38e2f438284683ad9e30b06e85..ebe4ff06a974e5dc93a0e535918bdf871ea4f554 100644 --- a/MetaMeshToolbox.hh +++ b/MetaMeshToolbox.hh @@ -1,15 +1,47 @@ #pragma once #include <QtGui> +#include <QGroupBox> #include <QPushButton> +#include <QDoubleSpinBox> +#include <QVBoxLayout> +#include <QRadioButton> +#include <QComboBox> +#include <QCheckBox> class MetaMeshToolbox : public QWidget { - Q_OBJECT + Q_OBJECT public: - MetaMeshToolbox(QWidget* parent = nullptr); - - QPushButton* button_dispatch; + MetaMeshToolbox(QWidget* parent = nullptr); + + QGroupBox* mode_selection_; + QVBoxLayout* vbox_; + QRadioButton* checkbox_implicit_dijkstra_; + QRadioButton* checkbox_a_star_triangulation_; + QRadioButton* checkbox_a_star_midpoint_triangulation_; + QComboBox* combo_box_tests_; + QComboBox* combo_box_remeshing_; + + QPushButton* button_triangulate_; + QPushButton* button_randomize_ratio_; + QPushButton* button_randomize_total_; + QPushButton* button_run_test_; + QCheckBox* checkbox_copy_markings_; + QPushButton* button_retrace_; + QPushButton* button_rotate_; + QPushButton* button_split_; + QPushButton* button_collapse_; + QPushButton* button_relocate_; + QPushButton* button_remesh_; + QPushButton* button_dummy1_; + QPushButton* button_dummy2_; + QPushButton* button_next_; + QPushButton* button_opp_; + QPushButton* button_prev_; + QDoubleSpinBox* input_length_; + QDoubleSpinBox* input_ratio_; + QSpinBox* input_iterations_; }; diff --git a/RWTHColors.hh b/RWTHColors.hh new file mode 100644 index 0000000000000000000000000000000000000000..3ac1a7ef4cefda8ff882fa9dd33befd7c6e1ee1d --- /dev/null +++ b/RWTHColors.hh @@ -0,0 +1,23 @@ +/** + * Colors.hh + */ +#pragma once + +#include <ACG/Math/VectorT.hh> +#include <QColor> + +namespace Utils { + +static constexpr ACG::Vec4f RWTH_BLUE(0.0, 0.329, 0.624, 1.0); +static constexpr ACG::Vec4f RWTH_GREEN(0.341, 0.671, 0.153, 1.0); +static constexpr ACG::Vec4f RWTH_ORANGE(0.965, 0.659, 0.0, 1.0); +static constexpr ACG::Vec4f RWTH_VIOLETT(0.380, 0.129, 0.345, 1.0); +static constexpr ACG::Vec4f RWTH_RED(0.800, 0.027, 0.118, 1.0); +static constexpr ACG::Vec4f RWTH_MAGENTA(0.890, 0.000, 0.400, 1.0); +static constexpr ACG::Vec4f RWTH_YELLOW(1.000, 0.929, 0.000, 1.0); + +static constexpr ACG::Vec4f RWTH_BLACK(0.0, 0.0, 0.0, 1.0); +static constexpr ACG::Vec4f RWTH_GRAY_75(0.392, 0.396, 0.404, 1.0); +static constexpr ACG::Vec4f RWTH_GRAY_50(0.612, 0.620, 0.624, 1.0); + +} // namespace Utils diff --git a/Stopwatch.cc b/Stopwatch.cc new file mode 100644 index 0000000000000000000000000000000000000000..af9d8b7b955414fc3b66f40db7c7a9ca868d30a3 --- /dev/null +++ b/Stopwatch.cc @@ -0,0 +1,27 @@ +#include "Stopwatch.hh" + +/*! + * \brief Stopwatch::Stopwatch a simple stopwatch, starts timer when initialized + */ +Stopwatch::Stopwatch() { + Reset(); +} + +/*! + * \brief Stopwatch::Delta + * \return the time passed since the last timer reset + */ +double Stopwatch::Delta() { + auto end = std::chrono::time_point_cast< + std::chrono::milliseconds>(std::chrono::system_clock::now()); + auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(end-start_).count(); + return delta; +} + +/*! + * \brief Stopwatch::Reset reset the timer + */ +void Stopwatch::Reset() { + start_ = std::chrono::time_point_cast< + std::chrono::milliseconds>(std::chrono::system_clock::now()); +} diff --git a/Stopwatch.hh b/Stopwatch.hh new file mode 100644 index 0000000000000000000000000000000000000000..3988f5d37a778a27c535e7af47a7a1b014254898 --- /dev/null +++ b/Stopwatch.hh @@ -0,0 +1,15 @@ +#pragma once + +#include <chrono> + +// A simple stopwatch with ms precision +class Stopwatch { +public: + Stopwatch(); + ~Stopwatch(); + double Delta(); + void Reset(); + +private: + std::chrono::time_point<std::chrono::system_clock> start_; +}; diff --git a/Testing.cc b/Testing.cc new file mode 100644 index 0000000000000000000000000000000000000000..b43bdb32b318fa034ac9d7fd5f082239f9f84979 --- /dev/null +++ b/Testing.cc @@ -0,0 +1,649 @@ +#include "Testing.hh" +#include <QDebug> +#include <math.h> +#include <random> + +#define BASE_MESH_ embedding_->base_mesh_ +#define META_MESH_ embedding_->meta_mesh_ + +/*! + * \brief Testing::Testing + * \param embedding + */ +Testing::Testing(Embedding* embedding) : embedding_(embedding) { + +} + +/*! + * \brief Testing::TriangulationTests tests the triangulation, however the bhe_border + * and mhe_border properties cannot be tested properly on meshes with boundaries, thus + * this test is deprecated and disabled for now. + */ +void Testing::TriangulationTests() { + ResetStop(); + if (META_MESH_ == nullptr) { + qDebug() << "Uninitialized Meta Mesh, run some triangulation first."; + return; + } + + qDebug() << "Deprecated test"; + return; + qDebug() << "Testing Meta Mesh properties and connectivity after an intial triangulation."; + + Stopwatch* swatch = new Stopwatch(); + + TestVertices(); + if (stop_) return; + TestHalfedgesInitialTriangulation(); + if (stop_) return; + TestFaces(); + if (stop_) return; + + double delta = swatch->Delta(); + qDebug() << "All tests passed, time elapsed: " << delta; + + DisplayMeshInformation(); +} + +/*! + * \brief Testing::MeshTests Calls various tests on the mesh that should always pass. + * These tests do not change the meta mesh + */ +void Testing::MeshTests() { + ResetStop(); + if (META_MESH_ == nullptr) { + qDebug() << "Uninitialized Meta Mesh, run some triangulation first."; + return; + } + qDebug() << "Testing Meta Mesh properties and connectivity (this should always pass)."; + + Stopwatch* swatch = new Stopwatch(); + + TestVertices(); + if (stop_) return; + TestHalfedges(); + if (stop_) return; + TestFaces(); + if (stop_) return; + TestMetaMesh(); + if (stop_) return; + + double delta = swatch->Delta(); + qDebug() << "All tests passed, time elapsed: " << delta << "ms."; + + DisplayMeshInformation(); +} + +/*! + * \brief Testing::OperationTests Spams basic operations on the mesh and tests for + * inconsistencies. This will drastically change the meta mesh. + */ +void Testing::OperationTests() { + ResetStop(); + if (META_MESH_ == nullptr) { + qDebug() << "Uninitialized Meta Mesh, run some triangulation first."; + return; + } + + qDebug() << "Testing basic Trimesh operations"; + Stopwatch* swatch = new Stopwatch(); + + TestFlips(true); + if (stop_) return; + TestSplits(true); + if (stop_) return; + TestCollapses(true); + if (stop_) return; + TestRelocation(true); + if (stop_) return; + + double delta = swatch->Delta(); + qDebug() << "All tests passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::DisplayMeshInformation outputs various information on the meta mesh + * such as numbers of elements, avg. edge valence and standard deviation from it etc. + */ +void Testing::DisplayMeshInformation() { + unsigned long baseeuler = BASE_MESH_->n_vertices() - BASE_MESH_->n_edges() + + BASE_MESH_->n_faces(); + unsigned long metaeuler = META_MESH_->n_vertices() - META_MESH_->n_edges() + + META_MESH_->n_faces(); + double basegenus = (2-baseeuler)/2.0; + double metagenus = (2-metaeuler)/2.0; + double avgmetaedgelength = 0.0; + for (auto meh : META_MESH_->edges()) { + avgmetaedgelength += embedding_->CalculateEdgeLength(meh); + } + avgmetaedgelength /= META_MESH_->n_edges(); + double metaedgelengthdeviation = 0.0; + for (auto meh : META_MESH_->edges()) { + metaedgelengthdeviation += (avgmetaedgelength-embedding_->CalculateEdgeLength(meh)) + *(avgmetaedgelength-embedding_->CalculateEdgeLength(meh)); + } + metaedgelengthdeviation /= META_MESH_->n_edges(); + double avgvalence = 0.0; + double valencedeviation = 0.0; + for (auto mvh : META_MESH_->vertices()) { + avgvalence += META_MESH_->valence(mvh); + valencedeviation += (6-META_MESH_->valence(mvh))*(6-META_MESH_->valence(mvh)); + } + avgvalence /= META_MESH_->n_vertices(); + valencedeviation /= META_MESH_->n_vertices(); + qDebug() << "Base Mesh- V: " << BASE_MESH_->n_vertices() + << "E: " << BASE_MESH_->n_edges() + << "F: " << BASE_MESH_->n_faces() + << "Eul: " << baseeuler + << "Gen: " << basegenus; + qDebug() << "Meta Mesh- V: " << META_MESH_->n_vertices() + << "E: " << META_MESH_->n_edges() + << "F: " << META_MESH_->n_faces() + << "Eul: " << metaeuler + << "Gen: " << metagenus; + qDebug() << "Embedding- Avg. Edge Length: " << avgmetaedgelength + << " deviation: " << metaedgelengthdeviation + << " avg valence: " << avgvalence + << " deviation from 6: " << valencedeviation; +} + +/*! + * \brief Testing::TestVertices + */ +void Testing::TestVertices() { + if (META_MESH_->n_vertices() == 0) { + qDebug() << "Meta Mesh has no vertices."; + } else { + TestVertexConnections(); + if (stop_) return; + + //qDebug() << "Passed all vertex tests."; + } +} + +/*! + * \brief Testing::TestHalfedges + */ +void Testing::TestHalfedges() { + if (META_MESH_->n_halfedges() == 0) { + qDebug() << "Meta Mesh has no halfedges."; + return; + } else { + TestHalfedgeConnections(); + if (stop_) return; + TestHalfedgeSymmetry(); + if (stop_) return; + + //qDebug() << "Passed all halfedge tests."; + } +} + +/*! + * \brief Testing::TestHalfedgesInitialTriangulation + */ +void Testing::TestHalfedgesInitialTriangulation() { + if (META_MESH_->n_halfedges() == 0) { + qDebug() << "Meta Mesh has no halfedges."; + return; + } else { + TestHalfedgeConnections(); + if (stop_) return; + TestHalfedgeSymmetry(); + if (stop_) return; + TestVoronoiBorderSymmetry(); + if (stop_) return; + TestVoronoiBorderConnectivity(); + if (stop_) return; + + //qDebug() << "Passed all halfedge tests."; + } +} + +/*! + * \brief Testing::TestFaces + */ +void Testing::TestFaces() { + if (META_MESH_->n_faces() == 0) { + qDebug() << "Meta Mesh has no faces."; + } else { + TestTriMesh(); + if (stop_) return; + + //qDebug() << "Passed all face tests."; + } +} + +/*! + * \brief Testing::TestMetaMesh + */ +void Testing::TestMetaMesh() { + if (META_MESH_->n_faces() == 0) { + qDebug() << "Meta Mesh has no faces."; + } else { + TestMetaMeshHalfedgeConnectivity(); + if (stop_) return; + + //qDebug() << "Passed all meta mesh connectivity tests."; + } +} + + +/*! + * \brief Testing::TestVertexConnections + * Test that each meta vertex is connected to a corresponding + * base vertex that is also connected to it + */ +void Testing::TestVertexConnections() { + Stopwatch* swatch = new Stopwatch(); + for (auto mvh : META_MESH_->vertices()) { + VisualAssert(BASE_MESH_->is_valid_handle( + META_MESH_->property(embedding_->mv_connection_, mvh)), + "Test failed: Unconnected Meta Vertex", {}); + if (stop_) return; + VisualAssert(BASE_MESH_->property(embedding_->bv_connection_, + META_MESH_->property(embedding_->mv_connection_, mvh)) == mvh, + "Test failed: Base Vertex that Meta Vertex is connected to is not connected back", + {META_MESH_->property(embedding_->mv_connection_, mvh)}); + if (stop_) return; + } + double delta = swatch->Delta(); + qDebug() << "TestVertexConnections passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestHalfedgeConnections + * Test that each meta halfedge is connected to a base halfedge that in turn + * is also connected to it and forms a chain of base half_edges connected via + * next_heh until it reaches the meta vertex. + */ +void Testing::TestHalfedgeConnections() { + Stopwatch* swatch = new Stopwatch(); + for (auto mheh : META_MESH_->halfedges()) { + TestHalfedgeConnection(mheh); + if (stop_) return; + } + double delta = swatch->Delta(); + qDebug() << "TestHalfedgeConnections passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestHalfedgeConnection + * \param mheh + */ +void Testing::TestHalfedgeConnection(OpenMesh::HalfedgeHandle mheh) { + auto bheh = META_MESH_->property(embedding_->mhe_connection_, mheh); + // Meta Halfedge has a valid connection that has not been deleted + VisualAssert(BASE_MESH_->is_valid_handle(bheh) + && !BASE_MESH_->status(bheh).deleted(), + "Test failed: The meta halfedge " + std::to_string(mheh.idx()) + + " has no valid base vertex connection at bheh " + + std::to_string(bheh.idx()), {}); + if (stop_) return; + // The connected base halfedge also has a connection to the meta halfedge + VisualAssert(BASE_MESH_->property(embedding_->bhe_connection_, bheh) == mheh, + "Test failed: The base halfedge is not connected back to the meta halfedge", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh)}); + if (stop_) return; + // Both originate from the same vertex + VisualAssert(BASE_MESH_->property(embedding_->bv_connection_, + BASE_MESH_->from_vertex_handle(bheh)) == META_MESH_->from_vertex_handle(mheh), + "Test failed: The base halfedge originates from a different vertex than the " + "meta halfedge", {BASE_MESH_->from_vertex_handle(bheh), + META_MESH_->property(embedding_->mv_connection_, META_MESH_->from_vertex_handle(mheh))}); + if (stop_) return; + auto curr = BASE_MESH_->property(embedding_->next_heh_, bheh); + // Test the connectivity of the meta halfedge, it should be a chain of + // base halfedges connected to the next one via next_heh_ and connected to the meta + // halfdge via bhe_connection_ + while (BASE_MESH_->is_valid_handle(curr)) { + //BASE_MESH_->status(curr).set_selected(true); + auto mheh0 = BASE_MESH_->property(embedding_->bhe_connection_, bheh); + auto mheh1 = BASE_MESH_->property(embedding_->bhe_connection_, curr); + VisualAssert(BASE_MESH_->property(embedding_->bhe_connection_, curr) == mheh, + "Test failed: A base halfedge is not connected to its meta halfedge;" + " The previous halfedge" + std::to_string(bheh.idx()) + " is connected" + " to meta halfedge " + std::to_string(mheh0.idx()) + " and the current" + " halfedge " + std::to_string(curr.idx()) + " is connected to meta" + " halfedge " + std::to_string(mheh1.idx()) +".", + {BASE_MESH_->from_vertex_handle(curr), BASE_MESH_->to_vertex_handle(curr)}); + if (stop_) return; + bheh = curr; + curr = BASE_MESH_->property(embedding_->next_heh_, curr); + } + if (curr.is_valid()) { + BASE_MESH_->status(bheh).set_selected(true); + } + VisualAssert(!curr.is_valid(), + "Test failed: A meta halfedge contains a halfedge not in the base mesh, the " + "next_heh_ pointed to by bheh " + std::to_string(bheh.idx()) + " is " + "valid but not in the base mesh. Marking bheh.", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh)}); + if (stop_) return; + + // Test that the final halfedge ends in a base vertex connected to the meta vertex the + // meta halfedge ends in + VisualAssert(BASE_MESH_->property(embedding_->bv_connection_, + BASE_MESH_->to_vertex_handle(bheh)) == META_MESH_->to_vertex_handle(mheh), + "Test failed: The base halfedge ends in a different vertex than the " + "meta halfedge", {BASE_MESH_->to_vertex_handle(bheh), + META_MESH_->property(embedding_->mv_connection_, META_MESH_->to_vertex_handle(mheh))}); + if (stop_) return; +} + +/*! + * \brief Testing::TestHalfedgeSymmetry + */ +void Testing::TestHalfedgeSymmetry() { + Stopwatch* swatch = new Stopwatch(); + for (auto bheh : BASE_MESH_->halfedges()) { + auto mheh0 = BASE_MESH_->property(embedding_->bhe_connection_, bheh); + auto mheh1 = BASE_MESH_->property(embedding_->bhe_connection_, + BASE_MESH_->opposite_halfedge_handle(bheh)); + if (!META_MESH_->is_valid_handle(mheh0)) { + VisualAssert(!META_MESH_->is_valid_handle(mheh1), + "Test failed: A base halfedge is connected but not its opposite halfedge", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh), + BASE_MESH_->from_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh)), + BASE_MESH_->to_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh))}); + if (stop_) return; + } else { + VisualAssert(META_MESH_->opposite_halfedge_handle(mheh0) == mheh1, + "Test failed: A base halfedge is connected but its opposite halfedge is not" + " connected the the opposite meta halfedge", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh), + BASE_MESH_->from_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh)), + BASE_MESH_->to_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh))}); + if (stop_) return; + } + } + double delta = swatch->Delta(); + qDebug() << "TestHalfedgeSymmetry passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestVoronoiBorderSymmetry + */ +void Testing::TestVoronoiBorderSymmetry() { + Stopwatch* swatch = new Stopwatch(); + for (auto bheh : BASE_MESH_->halfedges()) { + auto mheh0 = BASE_MESH_->property(embedding_->bhe_border_, bheh); + auto mheh1 = BASE_MESH_->property(embedding_->bhe_border_, + BASE_MESH_->opposite_halfedge_handle(bheh)); + if (!META_MESH_->is_valid_handle(mheh0)) { + VisualAssert(!META_MESH_->is_valid_handle(mheh1), + "Test failed: A base halfedge is a border but not its opposite halfedge", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh), + BASE_MESH_->from_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh)), + BASE_MESH_->to_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh))}); + if (stop_) return; + } else { + VisualAssert(META_MESH_->opposite_halfedge_handle(mheh0) == mheh1, + "Test failed: A base halfedge is a border but its opposite halfedge is not" + " connected a border to the opposite meta halfedge", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh), + BASE_MESH_->from_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh)), + BASE_MESH_->to_vertex_handle(BASE_MESH_->opposite_halfedge_handle(bheh))}); + if (stop_) return; + } + } + double delta = swatch->Delta(); + qDebug() << "TestVoronoiBorderSymmetry passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestVoronoiBorderConnectivity + * Ensure the correctness and consistency of the voronoi borders, marked by the + * property handles bhe_border_ and mhe_border_ + */ +void Testing::TestVoronoiBorderConnectivity() { + Stopwatch* swatch = new Stopwatch(); + for (auto mheh : META_MESH_->halfedges()) { + auto bheh = META_MESH_->property(embedding_->mhe_border_, mheh); + // Traverse the border on the base mesh and test it + TestBorderTraversal(bheh, mheh); + if (stop_) return; + } + double delta = swatch->Delta(); + qDebug() << "TestVoronoiBorderConnectivity passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestBorderTraversal + * \param bheh + * \param mheh + */ +void Testing::TestBorderTraversal(OpenMesh::HalfedgeHandle bheh, + OpenMesh::HalfedgeHandle mheh) { + // Test for existence + VisualAssert(BASE_MESH_->is_valid_handle(bheh), + "Test failed: Base halfedge " + std::to_string(bheh.idx()) + " should be" + " a border region but does not exist", {}); + if (stop_) return; + // Test for reciprocality + VisualAssert(BASE_MESH_->property(embedding_->bhe_border_, bheh) == mheh, + "Test failed: Base halfedge " + std::to_string(bheh.idx()) + " is not " + "connected to the proper meta halfedge demarking its border", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh)}); + if (stop_) return; + auto bheh0 = BASE_MESH_->next_halfedge_handle(BASE_MESH_->opposite_halfedge_handle(bheh)); + auto bheh1 = BASE_MESH_->next_halfedge_handle(bheh0); + auto mv0 = META_MESH_->from_vertex_handle(mheh); + auto mv1 = META_MESH_->to_vertex_handle(mheh); + // Test for correctness based on voronoi region + VisualAssert(mv0 == BASE_MESH_->property(embedding_->voronoiID_, + BASE_MESH_->from_vertex_handle(bheh)), + "Test failed: The voronoiID of the halfedges does not correspond " + "to its border property", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh)}); + if (stop_) return; + VisualAssert(mv1 == BASE_MESH_->property(embedding_->voronoiID_, + BASE_MESH_->to_vertex_handle(bheh)), + "Test failed: The voronoiID of the halfedges does not correspond " + "to its border property", + {BASE_MESH_->from_vertex_handle(bheh), BASE_MESH_->to_vertex_handle(bheh)}); + if (stop_) return; + // Traverse further + if (BASE_MESH_->property(embedding_->voronoiID_, BASE_MESH_->from_vertex_handle(bheh0)) + == mv0 + && BASE_MESH_->property(embedding_->voronoiID_, BASE_MESH_->to_vertex_handle(bheh0)) + == mv1) { + if (!stop_) TestBorderTraversal(bheh0, mheh); + } else if (BASE_MESH_->property(embedding_->voronoiID_, BASE_MESH_->from_vertex_handle(bheh1)) + == mv0 + && BASE_MESH_->property(embedding_->voronoiID_, BASE_MESH_->to_vertex_handle(bheh1)) + == mv1) { + if (!stop_) TestBorderTraversal(bheh1, mheh); + } +} + +/*! + * \brief Testing::TestTriMesh + * Make sure the meta mesh is a TriMesh by testing each face + */ +void Testing::TestTriMesh() { + Stopwatch* swatch = new Stopwatch(); + for (auto mfh : META_MESH_->faces()) { + auto mheh = META_MESH_->halfedge_handle(mfh); + auto mheh3 = META_MESH_->next_halfedge_handle( + META_MESH_->next_halfedge_handle( + META_MESH_->next_halfedge_handle(mheh))); + // In a triangular face the halfedge 3 after the current halfedge is the current halfedge. + VisualAssert(mheh == mheh3, + "Test failed: a meta face is not a triangle", {}); + if (stop_) return; + } + double delta = swatch->Delta(); + qDebug() << "TestTriMesh passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestMetaMeshHalfedgeConnectivity + */ +void Testing::TestMetaMeshHalfedgeConnectivity() { + Stopwatch* swatch = new Stopwatch(); + for (auto mheh : META_MESH_->halfedges()) { + auto bvh0 = META_MESH_->property(embedding_->mv_connection_, + META_MESH_->from_vertex_handle(mheh)); + auto bvh1 = META_MESH_->property(embedding_->mv_connection_, + META_MESH_->to_vertex_handle(mheh)); + auto mheh1 = META_MESH_->next_halfedge_handle(mheh); + VisualAssert(META_MESH_->is_valid_handle(mheh1), "mheh " + std::to_string(mheh.idx()) + + " has an invalid next_halfedge_handle " + std::to_string(mheh1.idx()), {}); + auto mheh2 = META_MESH_->next_halfedge_handle(mheh1); + VisualAssert(META_MESH_->is_valid_handle(mheh1), "mheh1 " + std::to_string(mheh1.idx()) + + " has an invalid next_halfedge_handle " + std::to_string(mheh2.idx()), {}); + auto bvh2 = META_MESH_->property(embedding_->mv_connection_, + META_MESH_->to_vertex_handle(mheh1)); + auto bvh3 = META_MESH_->property(embedding_->mv_connection_, + META_MESH_->to_vertex_handle(mheh2)); + if (META_MESH_->is_boundary(mheh)) { + VisualAssert(META_MESH_->is_boundary(META_MESH_->next_halfedge_handle(mheh)), + "Test failed: a boundary halfedge does not point towards another" + " boundary halfedge", {bvh0, bvh1, bvh2}); + } else { + VisualAssert(bvh0 == bvh3, + "Test failed: a halfedge is not part of a triangle.", {bvh0, bvh1, bvh2, bvh3}); + } + if (stop_) return; + } + double delta = swatch->Delta(); + qDebug() << "TestMetaMeshHalfedgeConnectivity passed, time elapsed: " + << delta << "ms."; +} + +/*! + * \brief Testing::TestFlips + * \param nostop + */ +void Testing::TestFlips(bool nostop) { + Stopwatch* swatch = new Stopwatch(); + if (nostop) ResetStop(); + for (auto meh : META_MESH_->edges()) { + if (META_MESH_->is_valid_handle(meh) && !META_MESH_->status(meh).deleted()) { + if (embedding_->IsRotationOkay(meh)) { + auto mvh0 = META_MESH_->to_vertex_handle(META_MESH_->halfedge_handle(meh, 0)); + auto mvh1 = META_MESH_->to_vertex_handle(META_MESH_->halfedge_handle(meh, 1)); + embedding_->Rotate(meh); + embedding_->Rotate(meh); + auto mvh2 = META_MESH_->to_vertex_handle(META_MESH_->halfedge_handle(meh, 0)); + auto mvh3 = META_MESH_->to_vertex_handle(META_MESH_->halfedge_handle(meh, 1)); + VisualAssert(((mvh0 == mvh3) && (mvh1 == mvh2)), + "After rotating meta edge" + std::to_string(meh.idx()) + " twice " + "the start/end vertices mismatched.", {}); + if (stop_) return; + embedding_->CleanUpBaseMesh(); + } + } + } + double delta = swatch->Delta(); + qDebug() << "TestFlips passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestSplits + * \param nostop + */ +void Testing::TestSplits(bool nostop) { + Stopwatch* swatch = new Stopwatch(); + if (nostop) ResetStop(); + // Test Edge splits + for (auto meh : META_MESH_->edges()) { + if (meh.idx()%5 == 0) { + embedding_->Split(embedding_->MiddleBvh(meh)); + } + } + // Test Face splits + for (auto bvh : BASE_MESH_->vertices()) { + if (bvh.idx()%500 == 0) { + embedding_->Split(bvh); + } + } + double delta = swatch->Delta(); + qDebug() << "TestSplits passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestCollapses + * \param nostop + */ +void Testing::TestCollapses(bool nostop) { + Stopwatch* swatch = new Stopwatch(); + if (nostop) ResetStop(); + size_t n_e = META_MESH_->n_edges()+1; + //qDebug() << "Number of edges: " << META_MESH_->n_edges(); + while (n_e > META_MESH_->n_edges()) { + n_e = META_MESH_->n_edges(); + size_t temp = n_e; + while (temp > 0) { + if (embedding_->IsCollapseOkay( + META_MESH_->halfedge_handle(META_MESH_->edge_handle(temp-1), 0))) { + embedding_->Collapse(META_MESH_->halfedge_handle(META_MESH_->edge_handle(temp-1), 0), + true); + embedding_->MetaGarbageCollection(); + embedding_->CleanUpBaseMesh(); + //qDebug() << "Number of edges: " << META_MESH_->n_edges(); + if (embedding_->ErrStatus()) { + return; + } + //qDebug() << "Number of edges: " << META_MESH_->n_edges(); + } + --temp; + if (temp > META_MESH_->n_edges()) + temp = META_MESH_->n_edges(); + } + } + double delta = swatch->Delta(); + qDebug() << "TestCollapses passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::TestRelocation + * \param nostop + */ +void Testing::TestRelocation(bool nostop) { + Stopwatch* swatch = new Stopwatch(); + if (nostop) ResetStop(); + std::random_device rd; //Will be used to obtain a seed for the random number engine + std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() + std::uniform_int_distribution<> dis(0, static_cast<int>(BASE_MESH_->n_vertices())-1); + for (auto mvh : META_MESH_->vertices()) { + auto bvh = BASE_MESH_->vertex_handle(dis(gen)); + embedding_->Relocate(mvh, bvh); + } + + double delta = swatch->Delta(); + qDebug() << "TestRelocation passed, time elapsed: " << delta << "ms."; +} + +/*! + * \brief Testing::VisualAssert like an assert(), but doesn't crash the program. Instead + * stops ongoing tests and visually marks faulty parts in the mesh for visual debugging + * \param condition assert(condition), otherwise -> + * \param message output message if condition fails + * \param visualize visualization if condition fails + */ +void Testing::VisualAssert(bool condition, std::string message, + std::vector<OpenMesh::VertexHandle> visualize) { + if (!condition) { + stop_ = true; + qDebug() << QString::fromStdString(message); + for (auto v : visualize) { + VisualizeVertex(v); + } + } +} + +/*! + * \brief Testing::VisualizeVertex + * \param bvh vertex to be visualized + */ +void Testing::VisualizeVertex(OpenMesh::VertexHandle bvh) { + qDebug() << "Marking vertex: " << bvh.idx() << "in white."; + auto vertex_colors_pph = BASE_MESH_->vertex_colors_pph(); + BASE_MESH_->property(vertex_colors_pph, bvh) = ACG::Vec4f(0.0f, 0.0f, 0.0f, 1.0f); + for (auto vvh : BASE_MESH_->vv_range(bvh)) { + if (BASE_MESH_->property(vertex_colors_pph, vvh) != ACG::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)) { + BASE_MESH_->property(vertex_colors_pph, vvh) = ACG::Vec4f(1.0f, 1.0f, 1.0f, 1.0f); + } + } +} diff --git a/Testing.hh b/Testing.hh new file mode 100644 index 0000000000000000000000000000000000000000..2e6457fbf5c14e54d99c25d73d31bd28c6a95d0e --- /dev/null +++ b/Testing.hh @@ -0,0 +1,54 @@ +#pragma once + +#include <OpenMesh/Core/Mesh/PolyConnectivity.hh> +#include <ObjectTypes/PolyMesh/PolyMesh.hh> +#include <ObjectTypes/TriangleMesh/TriangleMesh.hh> +#include <OpenFlipper/common/Types.hh> +#include <ACG/Utils/HaltonColors.hh> + +#include "Embedding.hh" +#include "Stopwatch.hh" + +class Testing { +public: + Testing(Embedding* embedding); + ~Testing() {} + + void TriangulationTests(); + void MeshTests(); + void OperationTests(); + void ResetStop() {stop_=false;} + void DisplayMeshInformation(); + + // Triangulation + void TestVertices(); + void TestHalfedges(); + void TestHalfedgesInitialTriangulation(); + void TestFaces(); + void TestMetaMesh(); + + void TestVertexConnections(); + void TestHalfedgeConnections(); + void TestHalfedgeSymmetry(); + void TestVoronoiBorderSymmetry(); + void TestVoronoiBorderConnectivity(); + void TestTriMesh(); + void TestMetaMeshHalfedgeConnectivity(); + + // Basic Operation Tests + void TestFlips(bool nostop = false); + void TestSplits(bool nostop = false); + void TestCollapses(bool nostop = false); + void TestRelocation(bool nostop = false); + +private: + void TestHalfedgeConnection(OpenMesh::HalfedgeHandle mheh); + void TestBorderTraversal(OpenMesh::HalfedgeHandle bheh, + OpenMesh::HalfedgeHandle mheh); + void VisualAssert(bool condition, std::string message, + std::vector<OpenMesh::VertexHandle> visualize); + void VisualizeVertex(OpenMesh::VertexHandle bvh); + + bool stop_=false; + Embedding* embedding_; +}; diff --git a/renamer.py b/renamer.py new file mode 100644 index 0000000000000000000000000000000000000000..12b309ae0bbc9aa26fb582dbfbf1d53f206eb794 --- /dev/null +++ b/renamer.py @@ -0,0 +1,22 @@ +# A simple script to rename the output screenshots of IsotropicRemesher +# so that their names end in sorted numbers; this allows easy use as +# frames to render a video, eg. with ffmpeg: +# ffmpeg -f image2 -pix_fmt yuv420p -i target1-iterations20/IsoRemesh%d.png t1-i20.mp4 + +from glob import glob +import os + +files_list = glob("*") +for file in files_list: + numbers = [file] + for s in file: + if s.isdigit(): + numbers.append(s) + l = len(numbers) + if l == 2: + numbers.append("0") + if l == 3: + numbers.append(str(int(numbers[1])*5+int(numbers[2])+1)) + if l == 4: + numbers.append(str((int(numbers[1])*10+int(numbers[2]))*5+int(numbers[3])+1)) + os.rename(numbers[0], "IsoRemesh"+numbers[-1]+".png")