/*========================================================================= * * 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 itkGeometricalQuadEdge_hxx #define itkGeometricalQuadEdge_hxx #include #include namespace itk { /** */ template const typename GeometricalQuadEdge::OriginRefType GeometricalQuadEdge::m_NoPoint = std::numeric_limits::max(); /** * Constructor */ template GeometricalQuadEdge::GeometricalQuadEdge() : m_Origin(m_NoPoint) , m_Data() , m_LineCellIdent(0) {} /** */ template bool GeometricalQuadEdge::SetLnextRingWithSameLeftFace( const DualOriginRefType faceGeom, int maxSize) { #ifndef NDEBUG if (!this->IsLnextSharingSameFace(maxSize)) { itkQEDebugMacro("Lnext() edges do NOT share the same Left()."); return (false); } #endif IteratorGeom it = this->BeginGeomLnext(); while (maxSize && (it != this->EndGeomLnext())) { it.Value()->SetLeft(faceGeom); ++it; --maxSize; } return (true); } /** * \brief Check whether the Lnext() ring of "this" edge is exactly of * size three AND if those three edges all share the same Left(). * @return Returns true when the Lnext() ring is the one of a triangle. * Returns false otherwise. */ template bool GeometricalQuadEdge::IsLnextOfTriangle() { return (this->IsLnextSharingSameFace(3)); } /** * \brief Check whether the incoming argument is in the Onext() ring * of "this" edge or not. * @param b The edge to test. * @return Returns true when "this" edge and the incoming argument are * in the same Onext() ring. Returns false otherwise. */ template bool GeometricalQuadEdge::IsInOnextRing(Self * b) { for (IteratorGeom it = this->BeginGeomOnext(); it != this->EndGeomOnext(); ++it) { if (b == it.Value()) { return true; } } return false; } /** * \brief Check whether the incoming argument is in the Lnext() ring * of "this" edge or not. * @param b The edge to test. * @return Returns true when "this" edge and the incoming argument are * in the same Lnext() ring. Returns false otherwise. */ template bool GeometricalQuadEdge::IsInLnextRing(Self * b) { for (IteratorGeom it = this->BeginGeomLnext(); it != this->EndGeomLnext(); ++it) { if (b == it.Value()) { return true; } } return false; } /** * \brief Check whether edge's Origin is internal to the mesh (as opposed * to being on the boundary) by looking if all the edges in the * Onext() ring have a face set on both their Left() and Right() * side. */ template bool GeometricalQuadEdge::IsOriginInternal() const { ConstIteratorGeom it = this->BeginGeomOnext(); while (it != this->EndGeomOnext()) { using QuadEdgeType = typename ConstIteratorGeom::QuadEdgeType; const QuadEdgeType * value = it.Value(); if (!value->IsInternal()) { return false; } ++it; } return true; } /** * \brief Consider the first few edges in Lnext() ring of "this" edge. * Check whether those edges all share the same Left(). * @param maxSize Looks at most maxSize edges in the Lnext() ring. * @return Returns true when the Lnext() ring share THE same * Left() faces. Return false otherwise. */ template bool GeometricalQuadEdge::IsLnextSharingSameFace(int maxSize) { IteratorGeom it = this->BeginGeomLnext(); while (maxSize && (it != this->EndGeomLnext())) { // The condition isn't complicated: if left faces aren't set, // continue, if just one is set return false, if both are set // check if the face is the same bool facesAreNotSet = !this->IsLeftSet() && !it.Value()->IsLeftSet(); bool facesAreTheSame = this->GetLeft() == it.Value()->GetLeft(); bool facesAreSet = this->IsLeftSet() && it.Value()->IsLeftSet(); // // FIXME: This boolean expression can be simplified. // ALEX : what about the version below ? // // if ( this->IsLeftSet() ) // one left set // { // if (it.Value()->IsLeftSet()) // two left set // { // if( !(this->GetLeft() == it.Value()->GetLeft()) ) // { // return( false ); // not same face // } // } // else // only one set // { // return( false ); // } // } // else // one not set // { // if(it.Value()->IsLeftSet()) // only one set // { // return( false ); // } // } // if (!(facesAreNotSet || (facesAreSet && facesAreTheSame))) { return (false); } ++it; --maxSize; } if (it != this->EndGeomLnext()) { // The Lnext ring is bigger than the caller expected return (false); } return (true); } /** */ template auto GeometricalQuadEdge::GetNextBorderEdgeWithUnsetLeft(Self * edgeTest) -> Self * { // Definition: an edge is said to be a boundary edge when it is adjacent to // noface i.e. when at least one of the faces edge->GetLeft() or // edge->GetRight() is unset. Definition: an point is said to be a boundary // point when at least one of the edges of its Onext() ring is a boundary // edge. // // Assume "this" edge belongs to a triangulation (i.e. it belongs to a QEMesh // which represents a 2-manifold) which possesses a boundary. Assume "this" // edge instance is a boundary edge. Let us denote by P the point which is // the origin of "this" edge i.e. P is this->Origin(). By definition P is a // boundary point. Then AT LEAST two [see the note below] edges of the // Onext() ring of P [which all have the point P as Origin()] are themselves // boundary edges. And among those boundary edges AT LEAST one has it's // Left() face unset. By iterating over the Onext() ring (which defines a // local ordering on edges) this method searches for the first edge whose // Left() face is unset AND which is encountered AFTER edgeTest. // // @param edgeTest When present, this edge will be considered as // the entry edge in the Onext() ring. When absent it shall // be defaulted to "this" edge. (see the warning below). // @return When "this" edge is a boundary edge, return the first // edge in "this" Onext() ring whose Left() face is unset // AND located after edgeTest. // When "this" edge is NOT a boundary edge the 0 is // returned. // @warning When the Mesh possessing "this" edge is a 2-manifold // then result of this method is unique in the sense that // it is independent from the edgeTest parameter. // But when the Mesh is not 2-manifold (this state can // happen at intermediary stages of the building process, // or during "surgical" operations on the Mesh, and // even though the Mesh represents a triangulation) // the result of this method is not unique in the sense // that the result depends on the edgeTest parameter. // Let us illustrate this dependence by considering a // Mesh (which is a triangulation) which is not a 2-manifold. // Assume the point P (the origin of "this" edge i.e. // P = this->Originv()) is TWICE on the border i.e. it // is adjacent twice to noface. We can consider the situation // of the following diagram, which depicts some Onext() // ring around point P: // // \ / // // \ * / // // i3 b2 counter-clockwise // // * \ / NO FACE Onext() order. // // \ / // // ----b4-----P----b1------ // // /|\ // // NO FACE / | \ // // / | \ * <------ a * indicates the // // / | \ the presence of a face // // / | \ // // b5 i6 i7 // // / * | * \ // // / | \ // // // In this example, and if we assume the Onext() order is // represented counter-clockwise, the edges are ordered as // follows: // b1, b2, i3, b4, b5, i6, i7 // (when arbitrarily starting at edge b1). // We have four Boundary edges labeled b1, b2, b4, b5 and // we have three internal edges (i.e. non boundary edges) // labeled i3, i6 and i7. // Depending on edgeTest, the result of this method // will NOT return the same edge: // - when edgeTest == b5 (or i6 or i7 or b1) then the edge // b1 will be returned, // - when edgeTest == b2 (or i3 or b4) then the edge // b4 will be returned, // Eventually, when edgeTest is absent, the result shall // depend on the position of "this" in the Onext() ring(). // // Be sure the Onext ring isn't already full if (this->IsOriginInternal()) { itkQEDebugMacro("No border edge available; point is internal."); itkAssertInDebugAndIgnoreInReleaseMacro(false); return (nullptr); } // Update reference edgeTest = (!edgeTest) ? this : edgeTest; // On efficiency purposes if (edgeTest->IsIsolated()) { return (edgeTest); } // Ok, no more special cases IteratorGeom it = edgeTest->BeginGeomOnext(); IteratorGeom end = edgeTest->EndGeomOnext(); while (it != end) { if (!it.Value()->IsLeftSet()) { return (it.Value()); } ++it; } // No border edge found itkQEDebugMacro("No border edge available; point is internal."); itkAssertInDebugAndIgnoreInReleaseMacro(false); return (nullptr); } /** */ template bool GeometricalQuadEdge::InsertAfterNextBorderEdgeWithUnsetLeft( Self * isol, Self * hint) { // When the geometry of isol is set it must match the // one of "this" Origin(). If the geometry is not set, we assume // that both Origin are the same, regardless their actual value. // Note: The purpose of this test is to avoid introducing some // incoherence in the geometry at Origin(). // The things should go this way: // 1/ when the geometry of "this" Origin is not set, then be paranoid // and suspect the situation is already snafu: // 1a/ if all edges of "this" Onext ring have an unset Origin() // (the situation is coherent), then proceed (Result=0) // whatever the value of isol.Origin() might be. // 1b/ if one of the edges of "this" Onext ring has an Origin() set, // then we deduce that there is already some geometrical // incoherence at this->Origin() and exit this method (Result=1). // 2/ Then when we didn't exit at stage 1, consider isol.Origin(): // 2a/ when isol.Origin() is absent proceed (result=0), // 2b/ when isol.Origin() is present and Origin == isol.OriginSet then // proceed (result=0), // 2c/ when isol.Origin() is present and Origin != isol.OriginSet then // exit (result=1). // // Here is what is implemented: // +-----------+----------------+--------------------------+--------+ // | OriginSet | isol.OriginSet | Origin == isol.OriginSet | Result | // +-----------+----------------+--------------------------+--------+ // | 0 | 0 | 0 | 0 | // | 0 | 0 | 1 | 0 | // | 0 | 1 | 0 | 1 | // | 0 | 1 | 1 | 1 | // +-----------+----------------+--------------------------+--------+ // | 1 | 0 | 0 | 1 | // | 1 | 0 | 1 | 1 | // | 1 | 1 | 0 | 1 | // | 1 | 1 | 1 | 0 | // +-----------+----------------+--------------------------+--------+ // if (!(!(IsOriginSet() || isol->IsOriginSet()) || (IsOriginSet() && isol->IsOriginSet() && (m_Origin == isol->m_Origin)))) { itkQEDebugMacro("Isolated Origin() differs from this Origin."); return (false); } // Find out if this point has some room left for edge insertion: Self * edgeAfter = this->GetNextBorderEdgeWithUnsetLeft(hint); if (!edgeAfter) { itkQEDebugMacro("No border edge available; the point is surrounded by faces."); return (false); } // Normally, an edge was found edgeAfter->Splice(isol); return (true); } /** */ template bool GeometricalQuadEdge::ReorderOnextRingBeforeAddFace(Self * second) { // Assume "this->Originv()" is a boundary point P that is thrice adjacent // to noface and consider the given situation is the one depicted by // the following diagram where: // - P is "this->Originv()" instance, // - the * (star) indicates the presence of a face, // - b1, b2, b3, b4, b5, b6 denote boundary edges, // - p denotes some generic point, // - A and B denote some specific points we want to discuss, // - the Onext() ring order is represented counter-clockwise // [which is coherent with the definition of edge->GetRight()] // i.e. the ordering of the edges is: // b1, b2, b3, b4, b5, b6, b1... // // p N p // / \ O / \ // // / \ / \ // // / \ F / \ counter-clockwise // // / b3 A b2 \ Onext() ring order // // / \ C / \ // // / * \ E / * \ // // / \ / \ // // A------b4------P------b1-------B // // / \ // // / \ // // NO FACE / \ NO FACE // // / \ // // b5 b6 // // / * \ // // / \ // // p---------------p // // // At P this Mesh doesn't represent a 2-manifold (since we are thrice // on the boundary). Nevertheless such a situation could arise in // intermediary stages (e.g. when building the Mesh, or during // surgical changes on the Mesh). // Now, assume we are asked to build the triangle [P, A, B]. Note // that this request is not absurd since the current situation at // P isn't the one of a 2-manifold: hence when building the current // Onext() ring of P, we had not enough information to decide // whether b4.Onext() should be b5 or b1. It is ONLY when we are // required to build the triangle [P, A, B] that we have the // additional information that b4.Onext() is indeed b1. // When we are required to build triangle [P, A, B], we hence // need to change the Onext() ring order at P, i.e. we need to deal // with the triangle [P, b5, b6] which currently prevents // b4.Onext() to be b1. In other terms, when considering the // additional information that b4.Onext() is b1, and before // building the triangle [P, A, B], we need to reorder // the Onext() ring of P from its current state // b1, b2, b3, b4, b5, b6, b1... // to an order coherent with the [P, A, B] request, i.e. // b1, b2, b5, b6, b3, b4, b1... // // In order to establish the "proper" Onext() ring at P we use // two Splice operations. The algorithm goes: // - first disconnect the piece of the surface containing the edge // [PB] (it would be the same process if we chose [PA]) from // the Onext() ring at P. // - second, re-integrate the disconnected piece at the desired // location i.e. side by side with [PA] (respectively [PB] if // we chose [PA] at first stage). // By "piece of surface containing the edge [PB]" we mean [all] // the triangle[s] starting at [PB] in the Onext() order and // having a left face set. // // We can illustrate this process on bit more general diagram than // the last case (where the "piece of surface containing the edge // [PB]" is constituted by two triangles) and when using // the arguments of this method (i.e. [PA] = this and [PB] = second). // The initial stage is the following (we note first=this=[PA] and // second=[PB]) where the Onext() ring order is: // first, b2, b3, second, b5, bsplice, b7, first... // // p N A // // / \ O / \ // // / \ / \ // // / \ F / \ counter-clockwise // // / b2 A first \ Onext() ring order // // / \ C / \ // // / * \ E / * \ // // / \ / \ // // p-------b3------P------b7-------p // // /|\ // // / | \ // // NO FACE / | \ NO FACE // // / | \ // // second b5 bsplice // // / * | * \ // // / | \ // // B-------p-------p // // // The first stage, implemented as // second->Oprev()->Splice( bsplice ), // yields the following diagram: // // p N A // // / \ O / \ // // / \ F / \ // // / \ A / \ counter-clockwise // // / b2 C first \ Onext() ring order // // / \ E / \ // // / * \ / * \ // // / \ / \ // // p-------b3------P------b7-------p // // // // NO FACE // // // // /|\ // // / | \ // // / | \ // // / | \ // // second b5 bsplice // // / * | * \ // // / | \ // // B-------p-------p // // // and the second stage, implemented as // first->Splice( bsplice ), // yields the following diagram: // // A // // B__ NO FACE / \ // // | \__ / \ // // | \__ / \ counter- // // | second first \ clockwise for all // // | \__ / \ // // | * \__ / * \ // // | \ / \ // // p-------b5---------P------b7-------p // // | __/|\ // // | * __/ | \ // // | / | \ NO FACE // // | bsplice | \ // // | __/ b2 b3 // // p__/ | * \ // // NO FACE | \ // // p-------p // // Self * first = this; // Making sure point adjacency is correct: if (first->GetOrigin() != second->GetOrigin()) { itkQEDebugMacro("Edges not adjacent at same point!"); return (false); } if (first->GetOnext() == second) { return (true); } if (first->IsLeftSet()) { itkQEDebugMacro("First should NOT have a left face."); return (false); } // Second is an internal edge. if (second->IsInternal()) { return (false); } Self * bsplice; // Does not require initialisation; // Disconnect the triangles containing second: if (second->IsLeftSet()) { bsplice = second->GetNextBorderEdgeWithUnsetLeft(); second->GetOprev()->Splice(bsplice); } else { // Orientation is locally clockwise: bsplice = second; second->GetOprev()->Splice(bsplice); } // Reconnect second after first: first->Splice(bsplice); return (true); } // --------------------------------------------------------------------- template void GeometricalQuadEdge::Disconnect() { if (this->IsDisconnected()) { return; } // Update faces if the edge isn't a wire if (this->IsAtBorder()) { Self * e = (this->IsRightSet()) ? this->GetSym() : this; IteratorGeom it = e->BeginGeomLnext(); while (it != e->EndGeomLnext()) { it.Value()->UnsetLeft(); ++it; } } else if (this->IsInternal()) { // Consolidate face DualOriginRefType face = this->GetRight(); for (IteratorGeom it = this->BeginGeomLnext(); it != this->EndGeomLnext(); ++it) { it.Value()->SetLeft(face); } } // Hint edges Self * e0 = this->GetOprev(); Self * e1 = this->GetLnext(); // Disconnect entries if (!this->IsOriginDisconnected()) { e0->Splice(this); } if (!this->IsDestinationDisconnected()) { e1->Splice(this->GetSym()); } // Normally, this edge is converted to a simple wire this->UnsetOrigin(); this->UnsetDestination(); this->UnsetLeft(); this->UnsetRight(); } // --------------------------------------------------------------------- template bool GeometricalQuadEdge::IsOriginSet() const { return (this->m_Origin != m_NoPoint); } // --------------------------------------------------------------------- template bool GeometricalQuadEdge::IsDestinationSet() const { const Self * p1 = this->GetSym(); if (p1 == nullptr) { return false; // FIXME: Is this the right answer ? } return p1->IsOriginSet(); } // --------------------------------------------------------------------- template bool GeometricalQuadEdge::IsRightSet() const { const DualType * p1 = this->GetRot(); if (p1 == nullptr) { return false; // FIXME: Is this the right answer ? } return p1->IsOriginSet(); } // --------------------------------------------------------------------- template bool GeometricalQuadEdge::IsLeftSet() const { const DualType * p1 = this->GetInvRot(); if (p1 == nullptr) { return false; // FIXME: Is this the right answer ? } return p1->IsOriginSet(); } } // end of namespace itk #endif