// Copyright (C) 2007-2021 CEA/DEN, EDF R&D, OPEN CASCADE // // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com // // File : SMESH_Triangulate.cxx // Created : Thu Jan 18 18:00:13 2018 // Author : Edward AGAPOV (eap) // Extracted from ../DriverSTL/DriverSTL_W_SMDS_Mesh.cxx #include "SMESH_MeshAlgos.hxx" #include #include #include #include using namespace SMESH_MeshAlgos; namespace { struct Node // node of a triangle { size_t _triaIndex; // triangle index == index of the 1st triangle node in triangulation array size_t _nodeIndex; // node index within triangle [0-2] //! return node index within the node array size_t Index() const { return _triaIndex + _nodeIndex; } //! return local 3-d index [0-2] static size_t ThirdIndex( size_t i1, size_t i2 ) { size_t i3 = ( i2 + 1 ) % 3; if ( i3 == i1 ) i3 = ( i2 + 2 ) % 3; return i3; } //! return 3-d node index within the node array static size_t ThirdIndex( const Node& n1, const Node& n2 ) { return n1._triaIndex + ThirdIndex( n1._nodeIndex, n2._nodeIndex ); } bool operator<(const Node& other) const { return _triaIndex < other._triaIndex; } }; typedef boost::container::flat_set< Node > TriaNodeSet; } /*! * \brief Vertex of a polygon. Together with 2 neighbor Vertices represents a triangle */ struct Triangulate::PolyVertex { SMESH_NodeXYZ _nxyz; size_t _index; gp_XY _xy; PolyVertex* _prev; PolyVertex* _next; void SetNodeAndNext( const SMDS_MeshNode* n, PolyVertex& v, size_t index ); void GetTriaNodes( const SMDS_MeshNode** nodes, size_t* nodeIndices) const; double TriaArea() const; bool IsInsideTria( const PolyVertex* v ); PolyVertex* Delete(); // compare PolyVertex'es by node bool operator()(const PolyVertex* a, const PolyVertex* b) const { return ( a->_nxyz.Node() < b->_nxyz.Node() ); } // set of PolyVertex sorted by mesh node typedef boost::container::flat_set< PolyVertex*, PolyVertex > PVSet; }; struct Triangulate::Data { std::vector< PolyVertex > _pv; std::vector< size_t > _nodeIndex; PolyVertex::PVSet _uniqueNodePV; }; struct Triangulate::Optimizer { std::vector< TriaNodeSet > _nodeUsage; // inclusions of a node in triangles //================================================================================ /*! * \brief Optimize triangles by edge swapping * \param [inout] nodes - polygon triangulation, i.e. connectivity of all triangles to optimize * \param [in] points - coordinates of nodes of the input polygon * \param [in] nodeIndices - indices of triangulation nodes within the input polygon */ //================================================================================ void optimize( std::vector< const SMDS_MeshNode*>& nodes, std::vector< PolyVertex > & points, std::vector< size_t > & nodeIndices) { // for each node of the polygon, remember triangles using it _nodeUsage.resize( points.size() ); for ( size_t i = 0; i < points.size(); ++i ) // clear old data { _nodeUsage[ i ].clear(); } for ( size_t i = 0, iTria = 0; i < nodeIndices.size(); ++iTria ) { _nodeUsage[ nodeIndices[ i++ ]].insert({ iTria * 3, 0 }); _nodeUsage[ nodeIndices[ i++ ]].insert({ iTria * 3, 1 }); _nodeUsage[ nodeIndices[ i++ ]].insert({ iTria * 3, 2 }); } // optimization for ( size_t iTria = 0; iTria < nodeIndices.size(); iTria += 3 ) { double badness1 = computeBadness( nodeIndices[ iTria + 0 ], nodeIndices[ iTria + 1 ], nodeIndices[ iTria + 2 ], points ); for ( size_t i = 0; i < 3; ++i ) // loop on triangle edges to find a neighbor triangle { size_t i1 = iTria + i; // node index in nodeIndices size_t i2 = iTria + ( i + 1 ) % 3; size_t ind1 = nodeIndices[ i1 ]; // node index in points size_t ind2 = nodeIndices[ i2 ]; TriaNodeSet & usage1 = _nodeUsage[ ind1 ]; // triangles using a node TriaNodeSet & usage2 = _nodeUsage[ ind2 ]; if ( usage1.size() < 2 || usage2.size() < 2 ) continue; // look for another triangle using two nodes TriaNodeSet::iterator usIt1 = usage1.begin(); for ( ; usIt1 != usage1.end(); ++usIt1 ) { if ( usIt1->_triaIndex == iTria ) continue; // current triangle TriaNodeSet::iterator usIt2 = usage2.find( *usIt1 ); if ( usIt2 == usage2.end() ) continue; // no common _triaIndex in two usages size_t i3 = iTria + ( i + 2 ) % 3; size_t i4 = Node::ThirdIndex( *usIt1, *usIt2 ); // 4th node of quadrangle size_t ind3 = nodeIndices[ i3 ]; size_t ind4 = nodeIndices[ i4 ]; double badness2 = computeBadness( ind2, ind1, ind4, points ); double badness3 = computeBadness( ind1, ind4, ind3, points, /*checkArea=*/true ); double badness4 = computeBadness( ind2, ind3, ind4, points, /*checkArea=*/true ); if ( Max( badness1, badness2 ) < Max( badness3, badness4 )) continue; // swap edge by modifying nodeIndices nodeIndices[ i2 ] = ind4; _nodeUsage[ ind4 ].insert({ iTria, i2 - iTria }); _nodeUsage[ ind2 ].erase ({ iTria, i2 - iTria }); i1 = usIt1->Index(); nodeIndices[ i1 ] = ind3; _nodeUsage[ ind3 ].insert( *usIt1 ); _nodeUsage[ ind1 ].erase ( *usIt1 ); --i; // to re-check a current edge badness1 = badness3; break; } } } // update nodes by updated nodeIndices for ( size_t i = 0; i < nodeIndices.size(); ++i ) nodes[ i ] = points[ nodeIndices[ i ]]._nxyz.Node(); return; } //================================================================================ /*! * \brief Return 1./area. Initially: max cos^2 of triangle angles */ //================================================================================ double computeBadness( size_t i1, size_t i2, size_t i3, std::vector< PolyVertex > & points, bool checkArea = false ) { if ( checkArea ) { points[ i2 ]._prev = & points[ i1 ]; points[ i2 ]._next = & points[ i3 ]; double a = points[ i2 ].TriaArea(); // if ( a < 0 ) // return std::numeric_limits::max(); // return 1. / a; if ( a < 0 ) return 2; } const gp_XY & p1 = points[ i1 ]._xy; const gp_XY & p2 = points[ i2 ]._xy; const gp_XY & p3 = points[ i3 ]._xy; gp_XY vec[3] = { p2 - p1, p3 - p2, p1 - p3 }; double len[3] = { vec[0].SquareModulus(), vec[1].SquareModulus(), vec[2].SquareModulus() }; if ( len[0] < gp::Resolution() || len[1] < gp::Resolution() || len[2] < gp::Resolution() ) return 2; double maxCos2 = 0; for ( int i = 0; i < 3; ++i ) { int i2 = ( i+1 ) % 3; double dot = -vec[ i ] * vec[ i2 ]; if ( dot > 0 ) maxCos2 = Max( maxCos2, dot * dot / len[ i ] / len[ i2 ] ); } return maxCos2; } }; //================================================================================ /*! * \brief Initialization */ //================================================================================ void Triangulate::PolyVertex::SetNodeAndNext( const SMDS_MeshNode* n, PolyVertex& v, size_t index ) { _nxyz.Set( n ); _next = &v; v._prev = this; _index = index; } //================================================================================ /*! * \brief Remove self from a polygon */ //================================================================================ Triangulate::PolyVertex* Triangulate::PolyVertex::Delete() { _prev->_next = _next; _next->_prev = _prev; return _next; } //================================================================================ /*! * \brief Return nodes of a triangle */ //================================================================================ void Triangulate::PolyVertex::GetTriaNodes( const SMDS_MeshNode** nodes, size_t* nodeIndices) const { nodes[0] = _prev->_nxyz._node; nodes[1] = this->_nxyz._node; nodes[2] = _next->_nxyz._node; nodeIndices[0] = _prev->_index; nodeIndices[1] = this->_index; nodeIndices[2] = _next->_index; } //================================================================================ /*! * \brief Compute triangle area */ //================================================================================ inline static double Area( const gp_XY& xy0, const gp_XY& xy1, const gp_XY& xy2 ) { gp_XY vPrev = xy0 - xy1; gp_XY vNext = xy2 - xy1; return vNext ^ vPrev; } //================================================================================ /*! * \brief Compute triangle area */ //================================================================================ double Triangulate::PolyVertex::TriaArea() const { return Area( _prev->_xy, this->_xy, _next->_xy ); } //================================================================================ /*! * \brief Check if a vertex is inside a triangle */ //================================================================================ bool Triangulate::PolyVertex::IsInsideTria( const PolyVertex* v ) { if ( this ->_nxyz == v->_nxyz || _prev->_nxyz == v->_nxyz || _next->_nxyz == v->_nxyz ) return false; gp_XY p = _prev->_xy - v->_xy; gp_XY t = this->_xy - v->_xy; gp_XY n = _next->_xy - v->_xy; const double tol = -1e-7; return (( p ^ t ) >= tol && ( t ^ n ) >= tol && ( n ^ p ) >= tol ); // return ( Area( _prev, this, v ) > 0 && // Area( this, _next, v ) > 0 && // Area( _next, _prev, v ) > 0 ); } //================================================================================ /*! * \brief Triangulate a polygon. Assure correct orientation for concave polygons */ //================================================================================ bool Triangulate::triangulate( std::vector< const SMDS_MeshNode*>& nodes, const size_t nbNodes) { std::vector< PolyVertex >& _pv = _data->_pv; std::vector< size_t >& _nodeIndex = _data->_nodeIndex; PolyVertex::PVSet& _uniqueNodePV = _data->_uniqueNodePV; // connect nodes into a ring _pv.resize( nbNodes ); for ( size_t i = 1; i < nbNodes; ++i ) _pv[i-1].SetNodeAndNext( nodes[i-1], _pv[i], /*index=*/i-1 ); _pv[ nbNodes-1 ].SetNodeAndNext( nodes[ nbNodes-1 ], _pv[0], nbNodes-1 ); // assure correctness of PolyVertex::_index as a node can encounter more than once // within a polygon boundary if ( _optimizer && nbNodes > 4 ) { _uniqueNodePV.clear(); for ( size_t i = 0; i < nbNodes; ++i ) { PolyVertex::PVSet::iterator pv = _uniqueNodePV.insert( &_pv[i] ).first; _pv[i]._index = (*pv)->_index; } } // get a polygon normal gp_XYZ normal(0,0,0), p0,v01,v02; p0 = _pv[0]._nxyz; v01 = _pv[1]._nxyz - p0; for ( size_t i = 2; i < nbNodes; ++i ) { v02 = _pv[i]._nxyz - p0; normal += v01 ^ v02; v01 = v02; } // project nodes to the found plane gp_Ax2 axes; try { axes = gp_Ax2( p0, normal, v01 ); } catch ( Standard_Failure& ) { return false; } double factor = 1.0, modulus = normal.Modulus(); if ( modulus < 1e-2 ) factor = 1. / sqrt( modulus ); for ( size_t i = 0; i < nbNodes; ++i ) { gp_XYZ p = _pv[i]._nxyz - p0; _pv[i]._xy.SetX( axes.XDirection().XYZ() * p * factor); _pv[i]._xy.SetY( axes.YDirection().XYZ() * p * factor ); } // compute minimal triangle area double sumArea = 0; if ( factor == 1.0 ) sumArea = modulus; else for ( size_t i = 0; i < nbNodes; ++i ) sumArea += _pv[i].TriaArea(); const double minArea = 1e-6 * sumArea / ( nbNodes - 2 ); // in a loop, find triangles with positive area and having no vertices inside int iN = 0, nbTria = nbNodes - 2; nodes.resize( nbTria * 3 ); _nodeIndex.resize( nbTria * 3 ); PolyVertex* v = &_pv[0], *vi; int nbVertices = nbNodes, nbBadTria = 0, isGoodTria; while ( nbBadTria < nbVertices ) { if (( isGoodTria = v->TriaArea() > minArea )) { for ( vi = v->_next->_next; vi != v->_prev; vi = vi->_next ) { if ( v->IsInsideTria( vi )) break; } isGoodTria = ( vi == v->_prev ); } if ( isGoodTria ) { v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); iN += 3; v = v->Delete(); if ( --nbVertices == 3 ) { // last triangle remains v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); if ( _optimizer ) _optimizer->optimize( nodes, _pv, _nodeIndex ); return true; } nbBadTria = 0; } else { v = v->_next; ++nbBadTria; } } // the polygon is invalid; add triangles with positive area nbBadTria = 0; while ( nbBadTria < nbVertices ) { isGoodTria = v->TriaArea() > minArea; if ( isGoodTria ) { v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); iN += 3; v = v->Delete(); if ( --nbVertices == 3 ) { // last triangle remains v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); return true; } nbBadTria = 0; } else { v = v->_next; ++nbBadTria; } } // add all the rest triangles while ( nbVertices >= 3 ) { v->GetTriaNodes( &nodes[ iN ], &_nodeIndex[ iN ] ); iN += 3; v = v->Delete(); --nbVertices; } return true; } // triangulate() //================================================================================ /*! * \brief Constructor */ //================================================================================ Triangulate::Triangulate( bool optimize ): _optimizer(0) { _data = new Data; if ( optimize ) _optimizer = new Optimizer; } //================================================================================ /*! * \brief Destructor */ //================================================================================ Triangulate::~Triangulate() { delete _data; delete _optimizer; _optimizer = 0; } //================================================================================ /*! * \brief Return nb triangles in a decomposed mesh face * \retval int - number of triangles */ //================================================================================ int Triangulate::GetNbTriangles( const SMDS_MeshElement* face ) { // WARNING: counting triangles must be coherent with GetTriangles() switch ( face->GetEntityType() ) { case SMDSEntity_BiQuad_Triangle: case SMDSEntity_BiQuad_Quadrangle: return face->NbNodes() - 1; // case SMDSEntity_Triangle: // case SMDSEntity_Quad_Triangle: // case SMDSEntity_Quadrangle: // case SMDSEntity_Quad_Quadrangle: // case SMDSEntity_Polygon: // case SMDSEntity_Quad_Polygon: default: return face->NbNodes() - 2; } return 0; } //================================================================================ /*! * \brief Decompose a mesh face into triangles * \retval int - number of triangles */ //================================================================================ int Triangulate::GetTriangles( const SMDS_MeshElement* face, std::vector< const SMDS_MeshNode*>& nodes) { if ( face->GetType() != SMDSAbs_Face ) return 0; // WARNING: decomposing into triangles must be coherent with getNbTriangles() int nbTria, i = 0, nbNodes = face->NbNodes(); SMDS_NodeIteratorPtr nIt = face->interlacedNodesIterator(); nodes.resize( nbNodes * 3 ); nodes[ i++ ] = nIt->next(); nodes[ i++ ] = nIt->next(); const SMDSAbs_EntityType type = face->GetEntityType(); switch ( type ) { case SMDSEntity_BiQuad_Triangle: case SMDSEntity_BiQuad_Quadrangle: nbTria = ( type == SMDSEntity_BiQuad_Triangle ) ? 6 : 8; nodes[ i++ ] = face->GetNode( nbTria ); for ( i = 3; i < 3*(nbTria-1); i += 3 ) { nodes[ i+0 ] = nodes[ i-2 ]; nodes[ i+1 ] = nIt->next(); nodes[ i+2 ] = nodes[ 2 ]; } nodes[ i+0 ] = nodes[ i-2 ]; nodes[ i+1 ] = nodes[ 0 ]; nodes[ i+2 ] = nodes[ 2 ]; break; case SMDSEntity_Triangle: nbTria = 1; nodes[ i++ ] = nIt->next(); break; default: // case SMDSEntity_Quad_Triangle: // case SMDSEntity_Quadrangle: // case SMDSEntity_Quad_Quadrangle: // case SMDSEntity_Polygon: // case SMDSEntity_Quad_Polygon: nbTria = nbNodes - 2; while ( nIt->more() ) nodes[ i++ ] = nIt->next(); if ( nbTria > 1 && !triangulate( nodes, nbNodes )) { nIt = face->interlacedNodesIterator(); nodes[ 0 ] = nIt->next(); nodes[ 1 ] = nIt->next(); nodes[ 2 ] = nIt->next(); for ( i = 3; i < 3*nbTria; i += 3 ) { nodes[ i+0 ] = nodes[ 0 ]; nodes[ i+1 ] = nodes[ i-1 ]; nodes[ i+2 ] = nIt->next(); } } } return nbTria; }