/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #ifndef itkQuadEdgeMeshEulerOperatorJoinVertexFunction_hxx #define itkQuadEdgeMeshEulerOperatorJoinVertexFunction_hxx #include "itkQuadEdgeMeshZipMeshFunction.h" #include "itkNumericTraits.h" #include #include namespace itk { template QuadEdgeMeshEulerOperatorJoinVertexFunction::QuadEdgeMeshEulerOperatorJoinVertexFunction() : Superclass() , m_OldPointID(0) , m_EdgeStatus(STANDARD_CONFIG) {} template void QuadEdgeMeshEulerOperatorJoinVertexFunction::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); os << indent << "OldPointID: " << static_cast::PrintType>(m_OldPointID) << std::endl; os << indent << "EdgeStatus: "; switch (m_EdgeStatus) { default: case STANDARD_CONFIG: os << "STANDARD_CONFIG" << std::endl; break; case QUADEDGE_ISOLATED: os << "QUADEDGE_ISOLATED" << std::endl; break; case FACE_ISOLATED: os << "FACE_ISOLATED" << std::endl; break; case EDGE_NULL: os << "EDGE_NULL" << std::endl; break; case MESH_NULL: os << "MESH_NULL" << std::endl; break; case EDGE_ISOLATED: os << "EDGE_ISOLATED" << std::endl; break; case TOO_MANY_COMMON_VERTICES: os << "TOO_MANY_COMMON_VERTICES" << std::endl; break; case TETRAHEDRON_CONFIG: os << "TETRAHEDRON_CONFIG" << std::endl; break; case SAMOSA_CONFIG: os << "SAMOSA_CONFIG" << std::endl; break; case EYE_CONFIG: os << "EYE_CONFIG" << std::endl; break; case EDGE_JOINING_DIFFERENT_BORDERS: os << "EDGE_JOINING_DIFFERENT_BORDERS" << std::endl; break; } } template auto QuadEdgeMeshEulerOperatorJoinVertexFunction::Evaluate(QEType * e) -> OutputType { std::stack edges_to_be_deleted; m_EdgeStatus = CheckStatus(e, edges_to_be_deleted); switch (m_EdgeStatus) { default: case STANDARD_CONFIG: return Process(e); // Isolated quad edge case QUADEDGE_ISOLATED: return ProcessIsolatedQuadEdge(e); // Isolated face case FACE_ISOLATED: return ProcessIsolatedFace(e, edges_to_be_deleted); // e == 0 case EDGE_NULL: // m_Mesh == 0 case MESH_NULL: // e->IsIsolated() && e_sym->IsIsolated() case EDGE_ISOLATED: // more than 2 common vertices in 0-ring of org and dest respectively case TOO_MANY_COMMON_VERTICES: // Tetrahedron case case TETRAHEDRON_CONFIG: // Samosa case case SAMOSA_CONFIG: // Eye case case EYE_CONFIG: return ((QEType *)nullptr); case EDGE_JOINING_DIFFERENT_BORDERS: return ((QEType *)nullptr); } } template TQEType * QuadEdgeMeshEulerOperatorJoinVertexFunction::Process(QEType * e) { QEType * e_sym = e->GetSym(); // General case bool wasLeftFace = e->IsLeftSet(); bool wasRiteFace = e->IsRightSet(); bool wasLeftTriangle = e->IsLnextOfTriangle(); bool wasRiteTriangle = e_sym->IsLnextOfTriangle(); PointIdentifier NewDest = e->GetDestination(); PointIdentifier NewOrg = e->GetOrigin(); QEType * leftZip = e->GetLnext(); QEType * riteZip = e->GetOprev(); // // \ | / // // \ | / // // \ | / // // ------------------ b ------------- Y // // ___/ | | // // _<_leftZip__/ | | // // / ^ | // // X left e rite | // // \____________ | | // // \___ | | // // \ | | // // ------------------ a --riteZip->-- Y // // / | \ // // / | \ // // / | \ // // this->m_Mesh->LightWeightDeleteEdge(e); this->m_OldPointID = this->m_Mesh->Splice(leftZip, riteZip); // // | / __Y // // | / __/ | // // __<_leftZip___ | / __/ | // // / \ | / __/ | // // / \__ | / / | // // X NOFACE __ [a = b] NOFACE | // // \ / / / | \ \__ | // // \______________/ _/ / | \ \__ | // // __/ / | \ riteZip | // // __/ / | \ \__| // // / / | \ Y // // // When the Lnext and/or the Rnext ring of the argument edge was originally // the one[s] of a triangle, the above edge deletion created the odd // situation of having two different edges adjacent to the same two // vertices (which is quite a bad thing). This is was is depicted on // the above ascii-graph, the original left face was a triangle and // the resulting situation has two different edges adjacent to the // two vertices X and a. In order to clean up things, we can call the // Zip(MeshFunction) algorithm which handles this case. // Once things are back to normal, we recreate faces when they were // originally present. // using Zip = QuadEdgeMeshZipMeshFunction; if (wasLeftTriangle) { auto zip = Zip::New(); zip->SetInput(this->m_Mesh); if (QEType::m_NoPoint != zip->Evaluate(leftZip)) { itkDebugMacro("Zip must return NoPoint (left)."); return ((QEType *)nullptr); } } else { if (wasLeftFace) { this->m_Mesh->AddFace(leftZip); } } // NewOrg = riteZip->GetOprev( )->GetDestination( ); if (wasRiteTriangle) { NewOrg = riteZip->GetDestination(); auto zip = Zip::New(); zip->SetInput(this->m_Mesh); if (QEType::m_NoPoint != zip->Evaluate(riteZip)) { itkDebugMacro("Zip must return NoPoint (right)."); return ((QEType *)nullptr); } } else { NewOrg = riteZip->GetLprev()->GetOrigin(); if (wasRiteFace) { this->m_Mesh->AddFace(riteZip); } } OutputType result = this->m_Mesh->FindEdge(NewOrg, NewDest); if (!result) { result = this->m_Mesh->FindEdge(NewDest)->GetSym(); } return (result); } template TQEType * QuadEdgeMeshEulerOperatorJoinVertexFunction::ProcessIsolatedQuadEdge(QEType * e) { QEType * temp = (e->IsIsolated()) ? e->GetSym() : e; QEType * rebuildEdge = temp->GetOprev(); m_OldPointID = temp->GetSym()->GetOrigin(); bool e_leftset = e->IsLeftSet(); this->m_Mesh->LightWeightDeleteEdge(e); if (e_leftset) { this->m_Mesh->AddFace(rebuildEdge); } // this case has no symmetric case in SplitVertex // i.e. it is impossible to reconstruct such a pathological // case using SplitVertex. Thus the return value is // of less interest. // We return an edge whose dest is a, whichever. return (rebuildEdge); } template TQEType * QuadEdgeMeshEulerOperatorJoinVertexFunction::ProcessIsolatedFace( QEType * e, std::stack & EdgesToBeDeleted) { PointIdentifier org = e->GetOrigin(); PointIdentifier dest = e->GetDestination(); // delete all elements while (!EdgesToBeDeleted.empty()) { this->m_Mesh->LightWeightDeleteEdge(EdgesToBeDeleted.top()); EdgesToBeDeleted.pop(); } // it now returns one edge from NewDest or NewOrg if there are any // else nullptr QEType * temp = this->m_Mesh->FindEdge(dest); if (temp != nullptr) { return temp; } else { return this->m_Mesh->FindEdge(org); } } template bool QuadEdgeMeshEulerOperatorJoinVertexFunction::IsFaceIsolated(QEType * e, const bool iWasLeftFace, std::stack & oToBeDeleted) { bool border; QEType * e_sym = e->GetSym(); // turn around the face (left or right one) while edges are on the border // and push them into a stack (which will be used to delete properly all // elements ) QEType * temp = iWasLeftFace ? e : e_sym; QEType * e_it = temp; oToBeDeleted.push(e_it); e_it = e_it->GetLnext(); do { oToBeDeleted.push(e_it); border = e_it->IsAtBorder(); e_it = e_it->GetLnext(); } while ((e_it != temp) && border); return border; } template auto QuadEdgeMeshEulerOperatorJoinVertexFunction::CheckStatus(QEType * e, std::stack & oToBeDeleted) -> EdgeStatusType { #ifndef NDEBUG if (!e) { itkDebugMacro("Input is not an edge."); return EDGE_NULL; } if (!this->m_Mesh) { itkDebugMacro("No mesh present."); return MESH_NULL; } #endif QEType * e_sym = e->GetSym(); bool IsEdgeIsolated = e->IsIsolated(); bool IsSymEdgeIsolated = e_sym->IsIsolated(); if (IsEdgeIsolated || IsSymEdgeIsolated) { if (IsEdgeIsolated && IsSymEdgeIsolated) { // We could shrink the edge to a point, // But we consider this case to be degenerated. itkDebugMacro("Argument edge isolated."); return EDGE_ISOLATED; } // One the endpoints (and only one) of the incoming edge is isolated. // Instead of "shrinking" the edge it suffice to delete it. Note that // this also avoids trouble since the definition of leftZip or riteZip // would be improper in this case (in fact leftZip or riteZip would // in fact be e or e->GetSym( )... // // When e is adjacent to a face, we must retrieve the edge [ a, b ] // in order to rebuild the face after edge deletion. If the left face // of e is set then the right face is also set... since it is the same // face ! Here is the situation: // // b----a----X // | | | // | e | // | | | // | | // X----X----X // // We are not yet sure of the orientation of e and which endpoint // of e is attached in a. return QUADEDGE_ISOLATED; } PointIdentifier number_common_vertices = CommonVertexNeighboor(e); if (number_common_vertices > 2) { itkDebugMacro("The 2 vertices have more than 2 common neighboor vertices."); return TOO_MANY_COMMON_VERTICES; } if (number_common_vertices == 2) { if (IsTetrahedron(e)) { itkDebugMacro("It forms a tetrahedron."); return TETRAHEDRON_CONFIG; } } // General case bool wasLeftFace = e->IsLeftSet(); bool wasRiteFace = e->IsRightSet(); if (wasLeftFace && wasRiteFace) { if (IsSamosa(e)) { itkDebugMacro("SAMOSA_CONFIG."); return SAMOSA_CONFIG; } if (IsEye(e)) { itkDebugMacro("EYE_CONFIG."); return EYE_CONFIG; } if (IsEdgeLinkingTwoDifferentBorders(e)) { itkDebugMacro("EDGE_JOINING_DIFFERENT_BORDERS."); return EDGE_JOINING_DIFFERENT_BORDERS; } } else { if (wasLeftFace || wasRiteFace) { if (IsFaceIsolated(e, wasLeftFace, oToBeDeleted)) { itkDebugMacro("FACE_ISOLATED."); return FACE_ISOLATED; } } } return STANDARD_CONFIG; } template bool QuadEdgeMeshEulerOperatorJoinVertexFunction::IsTetrahedron(QEType * e) { if (e->GetOrder() == 3) { QEType * e_sym = e->GetSym(); if (e_sym->GetOrder() == 3) { if (e->GetLprev()->GetOrder() == 3) { if (e_sym->GetLprev()->GetOrder() == 3) { bool left_triangle = e->IsLnextOfTriangle(); bool right_triangle = e_sym->IsLnextOfTriangle(); if (left_triangle && right_triangle) { CellIdentifier id_left_right_triangle; if (e->GetLprev()->IsRightSet()) { id_left_right_triangle = e->GetLprev()->GetRight(); } else { return false; } CellIdentifier id_left_left_triangle; if (e->GetLnext()->IsRightSet()) { id_left_left_triangle = e->GetLnext()->GetRight(); } else { return false; } CellIdentifier id_right_left_triangle; if (e_sym->GetLnext()->IsRightSet()) { id_right_left_triangle = e_sym->GetLnext()->GetRight(); } else { return false; } CellIdentifier id_right_right_triangle; if (e_sym->GetLprev()->IsRightSet()) { id_right_right_triangle = e_sym->GetLprev()->GetRight(); } else { return false; } if ((id_left_right_triangle == id_right_left_triangle) && (id_left_left_triangle == id_right_right_triangle)) { return true; } } } } } } return false; } template bool QuadEdgeMeshEulerOperatorJoinVertexFunction::IsSamosa(QEType * e) { return ((e->GetOrder() == 2) && (e->GetSym()->GetOrder() == 2)); } template bool QuadEdgeMeshEulerOperatorJoinVertexFunction::IsEye(QEType * e) { bool OriginOrderIsTwo = (e->GetOrder() == 2); bool DestinationOrderIsTwo = (e->GetSym()->GetOrder() == 2); return ((OriginOrderIsTwo && !DestinationOrderIsTwo) || (!OriginOrderIsTwo && DestinationOrderIsTwo)); } template auto QuadEdgeMeshEulerOperatorJoinVertexFunction::CommonVertexNeighboor(QEType * e) -> PointIdentifier { QEType * qe = e; QEType * e_it = qe->GetOnext(); using PointIdentifierList = std::list; PointIdentifierList dir_list; PointIdentifierList sym_list; PointIdentifierList intersection_list; PointIdentifier id; do { id = e_it->GetDestination(); dir_list.push_back(id); e_it = e_it->GetOnext(); } while (e_it != qe); qe = qe->GetSym(); e_it = qe; do { id = e_it->GetDestination(); sym_list.push_back(id); e_it = e_it->GetOnext(); } while (e_it != qe); dir_list.sort(); sym_list.sort(); std::set_intersection( dir_list.begin(), dir_list.end(), sym_list.begin(), sym_list.end(), std::back_inserter(intersection_list)); return static_cast(intersection_list.size()); } template bool QuadEdgeMeshEulerOperatorJoinVertexFunction::IsEdgeLinkingTwoDifferentBorders(QEType * e) { QEType * t = e; QEType * e_it = t; bool org_border; do { org_border = e_it->IsAtBorder(); e_it = e_it->GetOnext(); } while ((e_it != t) && (!org_border)); if (!org_border) { return false; } else { t = e->GetSym(); e_it = t; bool dest_border; do { dest_border = e_it->IsAtBorder(); e_it = e_it->GetOnext(); } while ((e_it != t) && (!dest_border)); if (!dest_border) { return false; } else { return true; } } } } // end namespace itk #endif