// Copyright (c) 2015-2021 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #ifndef _Poly_MergeNodesTool_HeaderFile #define _Poly_MergeNodesTool_HeaderFile #include #include #include #include //! Auxiliary tool for merging triangulation nodes for visualization purposes. //! Tool tries to merge all nodes within input triangulation, but split the ones on sharp corners at specified angle. class Poly_MergeNodesTool : public Standard_Transient { DEFINE_STANDARD_RTTIEXT(Poly_MergeNodesTool, Standard_Transient) public: //! Merge nodes of existing mesh and return the new mesh. //! @param[in] theTris triangulation to add //! @param[in] theTrsf transformation to apply //! @param[in] theToReverse reverse triangle nodes order //! @param[in] theSmoothAngle merge angle in radians //! @param[in] theMergeTolerance linear merge tolerance //! @param[in] theToForce return merged triangulation even if it's statistics is equal to input one //! @return merged triangulation or NULL on no result Standard_EXPORT static Handle(Poly_Triangulation) MergeNodes (const Handle(Poly_Triangulation)& theTris, const gp_Trsf& theTrsf, const Standard_Boolean theToReverse, const double theSmoothAngle, const double theMergeTolerance = 0.0, const bool theToForce = true); public: //! Constructor //! @param[in] theSmoothAngle smooth angle in radians or 0.0 to disable merging by angle //! @param[in] theMergeTolerance node merging maximum distance //! @param[in] theNbFacets estimated number of facets for map preallocation Standard_EXPORT Poly_MergeNodesTool (const double theSmoothAngle, const double theMergeTolerance = 0.0, const int theNbFacets = -1); //! Return merge tolerance; 0.0 by default (only 3D points with exactly matching coordinates are merged). double MergeTolerance() const { return myNodeIndexMap.MergeTolerance(); } //! Set merge tolerance. void SetMergeTolerance (double theTolerance) { myNodeIndexMap.SetMergeTolerance (theTolerance); } //! Return merge angle in radians; 0.0 by default (normals with non-exact directions are not merged). double MergeAngle() const { return myNodeIndexMap.MergeAngle(); } //! Set merge angle. void SetMergeAngle (double theAngleRad) { myNodeIndexMap.SetMergeAngle (theAngleRad); } //! Return TRUE if nodes with opposite normals should be merged; FALSE by default. bool ToMergeOpposite() const { return myNodeIndexMap.ToMergeOpposite(); } //! Set if nodes with opposite normals should be merged. void SetMergeOpposite (bool theToMerge) { myNodeIndexMap.SetMergeOpposite (theToMerge); } //! Setup unit factor. void SetUnitFactor (double theUnitFactor) { myUnitFactor = theUnitFactor; } //! Return TRUE if degenerate elements should be discarded; TRUE by default. bool ToDropDegenerative() const { return myToDropDegenerative; } //! Set if degenerate elements should be discarded. void SetDropDegenerative (bool theToDrop) { myToDropDegenerative = theToDrop; } //! Return TRUE if equal elements should be filtered; FALSE by default. bool ToMergeElems() const { return myToMergeElems; } //! Set if equal elements should be filtered. void SetMergeElems (bool theToMerge) { myToMergeElems = theToMerge; } //! Compute normal for the mesh element. NCollection_Vec3 computeTriNormal() const { const gp_XYZ aVec01 = myPlaces[1] - myPlaces[0]; const gp_XYZ aVec02 = myPlaces[2] - myPlaces[0]; const gp_XYZ aCross = aVec01 ^ aVec02; NCollection_Vec3 aNorm ((float )aCross.X(), (float )aCross.Y(), (float )aCross.Z()); return aNorm.Normalized(); } public: //! Add another triangulation to created one. //! @param[in] theTris triangulation to add //! @param[in] theTrsf transformation to apply //! @param[in] theToReverse reverse triangle nodes order Standard_EXPORT virtual void AddTriangulation (const Handle(Poly_Triangulation)& theTris, const gp_Trsf& theTrsf = gp_Trsf(), const Standard_Boolean theToReverse = false); //! Prepare and return result triangulation (temporary data will be truncated to result size). Standard_EXPORT Handle(Poly_Triangulation) Result(); public: //! Add new triangle. //! @param[in] theElemNodes 3 element nodes void AddTriangle (const gp_XYZ theElemNodes[3]) { AddElement (theElemNodes, 3); } //! Add new quad. //! @param[in] theElemNodes 4 element nodes void AddQuad (const gp_XYZ theElemNodes[4]) { AddElement (theElemNodes, 4); } //! Add new triangle or quad. //! @param[in] theElemNodes element nodes //! @param[in] theNbNodes number of element nodes, should be 3 or 4 Standard_EXPORT void AddElement (const gp_XYZ* theElemNodes, int theNbNodes); //! Change node coordinates of element to be pushed. //! @param[in] theIndex node index within current element, in 0..3 range gp_XYZ& ChangeElementNode (int theIndex) { return myPlaces[theIndex]; } //! Add new triangle or quad with nodes specified by ChangeElementNode(). Standard_EXPORT void PushLastElement (int theNbNodes); //! Add new triangle with nodes specified by ChangeElementNode(). void PushLastTriangle() { PushLastElement (3); } //! Add new quad with nodes specified by ChangeElementNode(). void PushLastQuad() { PushLastElement (4); } //! Return current element node index defined by PushLastElement(). Standard_Integer ElementNodeIndex (int theIndex) const { return myNodeInds[theIndex]; } //! Return number of nodes. int NbNodes() const { return myNbNodes; } //! Return number of elements. int NbElements() const { return myNbElems; } //! Return number of discarded degenerate elements. int NbDegenerativeElems() const { return myNbDegenElems; } //! Return number of merged equal elements. int NbMergedElems() const { return myNbMergedElems; } //! Setup output triangulation for modifications. //! When set to NULL, the tool could be used as a merge map for filling in external mesh structure. Handle(Poly_Triangulation)& ChangeOutput() { return myPolyData; } private: //! Push triangle node with normal angle comparison. void pushNodeCheck (bool& theIsOpposite, const int theTriNode) { int aNodeIndex = myNbNodes; const gp_XYZ& aPlace = myPlaces[theTriNode]; const NCollection_Vec3 aVec3 ((float )aPlace.X(), (float )aPlace.Y(), (float )aPlace.Z()); if (myNodeIndexMap.Bind (aNodeIndex, theIsOpposite, aVec3, myTriNormal)) { ++myNbNodes; if (!myPolyData.IsNull()) { if (myPolyData->NbNodes() < myNbNodes) { myPolyData->ResizeNodes (myNbNodes * 2, true); } myPolyData->SetNode (myNbNodes, aPlace * myUnitFactor); } } myNodeInds[theTriNode] = aNodeIndex; } //! Push triangle node without merging vertices. inline void pushNodeNoMerge (const int theTriNode) { int aNodeIndex = myNbNodes; const gp_XYZ aPlace = myPlaces[theTriNode] * myUnitFactor; ++myNbNodes; if (!myPolyData.IsNull()) { if (myPolyData->NbNodes() < myNbNodes) { myPolyData->ResizeNodes (myNbNodes * 2, true); } myPolyData->SetNode (myNbNodes, aPlace); } myNodeInds[theTriNode] = aNodeIndex; } private: //! Pair holding Vec3 and Normal to the triangle struct Vec3AndNormal { NCollection_Vec3 Pos; //!< position NCollection_Vec3 Norm; //!< normal to the element Vec3AndNormal (const NCollection_Vec3& thePos, const NCollection_Vec3& theNorm) : Pos (thePos), Norm (theNorm) {} }; //! Custom map class with key as Node + element normal and value as Node index. //! NCollection_DataMap is not used, as it requires Hasher to be defined as class template and not class field. class MergedNodesMap : public NCollection_BaseMap { public: typedef NCollection_Vec3 CellVec3i; public: //! Main constructor. Standard_EXPORT MergedNodesMap (const int theNbBuckets); //! Return merge angle in radians; double MergeAngle() const { return myAngle; } //! Set merge angle. void SetMergeAngle (double theAngleRad) { myAngle = (float )theAngleRad; myAngleCos = (float )Cos (theAngleRad); } //! Return TRUE if merge angle is non-zero. //! 0 angle means angles should much without a tolerance. bool HasMergeAngle() const { return myAngle > 0.0f; } //! Return TRUE if merge angle comparison can be skipped (angle is close to 90 degrees). bool ToMergeAnyAngle() const { return myAngleCos <= 0.01f; } //! Return TRUE if nodes with opposite normals should be merged; FALSE by default. bool ToMergeOpposite() const { return myToMergeOpposite; } //! Set if nodes with opposite normals should be merged. void SetMergeOpposite (bool theToMerge) { myToMergeOpposite = theToMerge; } //! Return merge tolerance. double MergeTolerance() const { return myTolerance; } //! Set merge tolerance. Standard_EXPORT void SetMergeTolerance (double theTolerance); //! Return TRUE if merge tolerance is non-zero. bool HasMergeTolerance() const { return myTolerance > 0.0f; } //! Bind node to the map or find existing one. //! @param theIndex [in,out] index of new key to add, or index of existing key, if already bound //! @param theIsOpposite [out] flag indicating that existing (already bound) node has opposite direction //! @param thePos [in] node position to add or find //! @param theNorm [in] element normal for equality check //! @return TRUE if node was not bound already Standard_EXPORT bool Bind (int& theIndex, bool& theIsOpposite, const NCollection_Vec3& thePos, const NCollection_Vec3& theNorm); //! ReSize the map. Standard_EXPORT void ReSize (const int theSize); private: //! Return cell index for specified 3D point and inverted cell size. CellVec3i vec3ToCell (const NCollection_Vec3& thePnt) const { return CellVec3i (thePnt * myInvTol); } //! Hash code for integer vec3. Standard_EXPORT static int vec3iHashCode (const Poly_MergeNodesTool::MergedNodesMap::CellVec3i& theVec, const int theUpper); //! Compute hash code. Standard_EXPORT int hashCode (const NCollection_Vec3& thePos, const NCollection_Vec3& theNorm, const int theUpper) const; //! Compute hash code. int hashCode (const Vec3AndNormal& theKey, const int theUpper) const { return hashCode (theKey.Pos, theKey.Norm, theUpper); } //! Compare two vectors with inversed tolerance. Standard_EXPORT bool vec3AreEqual (const NCollection_Vec3& theKey1, const NCollection_Vec3& theKey2) const; //! Compare two nodes. Standard_EXPORT bool isEqual (const Vec3AndNormal& theKey1, const NCollection_Vec3& thePos2, const NCollection_Vec3& theNorm2, bool& theIsOpposite) const; private: //! Map node. class DataMapNode; private: float myTolerance; //!< linear tolerance for comparison float myInvTol; //!< inversed linear tolerance for comparison float myAngle; //!< angle for comparison float myAngleCos; //!< angle cosine for comparison bool myToMergeOpposite; //!< merge nodes with opposite normals }; //! Hasher for merging equal elements (with pre-sorted indexes). struct MergedElemHasher { static int HashCode (const NCollection_Vec4& theVec, const int theUpper) { unsigned int aHashCode = 0; aHashCode = aHashCode ^ ::HashCode (theVec[0], theUpper); aHashCode = aHashCode ^ ::HashCode (theVec[1], theUpper); aHashCode = aHashCode ^ ::HashCode (theVec[2], theUpper); aHashCode = aHashCode ^ ::HashCode (theVec[3], theUpper); return ((aHashCode & 0x7fffffff) % theUpper) + 1; } static bool IsEqual (const NCollection_Vec4& theKey1, const NCollection_Vec4& theKey2) { return theKey1.IsEqual (theKey2); } }; private: Handle(Poly_Triangulation) myPolyData; //!< output triangulation MergedNodesMap myNodeIndexMap; //!< map of merged nodes NCollection_Map, MergedElemHasher> myElemMap; //!< map of elements NCollection_Vec4 myNodeInds; //!< current element indexes NCollection_Vec3 myTriNormal; //!< current triangle normal gp_XYZ myPlaces[4]; //!< current triangle/quad coordinates to push Standard_Real myUnitFactor; //!< scale factor to apply Standard_Integer myNbNodes; //!< number of output nodes Standard_Integer myNbElems; //!< number of output elements Standard_Integer myNbDegenElems; //!< number of degenerated elements Standard_Integer myNbMergedElems; //!< number of merged elements Standard_Boolean myToDropDegenerative; //!< flag to filter our degenerate elements Standard_Boolean myToMergeElems; //!< flag to merge elements }; #endif // _Poly_MergeNodesTool_HeaderFile