// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2005 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include "Approximation.h" #include "MeshKernel.h" #include "Triangulation.h" using namespace MeshCore; bool TriangulationVerifier::Accept( const Base::Vector3f& n, const Base::Vector3f& p1, const Base::Vector3f& p2, const Base::Vector3f& p3 ) const { float ref_dist = (p2 - p1) * n; float tri_dist = (p3 - p1) * n; return (ref_dist * tri_dist <= 0.0F); } bool TriangulationVerifier::MustFlip(const Base::Vector3f& n1, const Base::Vector3f& n2) const { return n1.Dot(n2) <= 0.0F; } bool TriangulationVerifierV2::Accept( const Base::Vector3f& n, const Base::Vector3f& p1, const Base::Vector3f& p2, const Base::Vector3f& p3 ) const { float ref_dist = (p2 - p1) * n; float tri_dist = (p3 - p1) * n; float prod = ref_dist * tri_dist; (void)prod; return true; } bool TriangulationVerifierV2::MustFlip(const Base::Vector3f& n1, const Base::Vector3f& n2) const { float dot = n1.Dot(n2); (void)dot; return false; } // ---------------------------------------------------------------------------- AbstractPolygonTriangulator::AbstractPolygonTriangulator() : _discard {false} , _verifier {new TriangulationVerifier()} {} AbstractPolygonTriangulator::~AbstractPolygonTriangulator() { delete _verifier; } TriangulationVerifier* AbstractPolygonTriangulator::GetVerifier() const { return _verifier; } void AbstractPolygonTriangulator::SetVerifier(TriangulationVerifier* v) { delete _verifier; _verifier = v; } void AbstractPolygonTriangulator::SetPolygon(const std::vector& raclPoints) { this->_points = raclPoints; if (!this->_points.empty()) { if (this->_points.front() == this->_points.back()) { this->_points.pop_back(); } } } std::vector AbstractPolygonTriangulator::GetPolygon() const { return _points; } float AbstractPolygonTriangulator::GetLength() const { float len = 0.0F; if (_points.size() > 2) { for (auto it = _points.begin(); it != _points.end(); ++it) { std::vector::const_iterator jt = it + 1; if (jt == _points.end()) { jt = _points.begin(); } len += Base::Distance(*it, *jt); } } return len; } std::vector AbstractPolygonTriangulator::AddedPoints() const { // Apply the inverse transformation to project back to world coordinates std::vector added; added.reserve(_newpoints.size()); for (auto point : _newpoints) { added.push_back(_inverse * point); } return added; } Base::Matrix4D AbstractPolygonTriangulator::GetTransformToFitPlane() const { PlaneFit planeFit; for (auto point : _points) { planeFit.AddPoint(point); } if (planeFit.Fit() >= std::numeric_limits::max()) { throw Base::RuntimeError("Plane fit failed"); } Base::Vector3f bs = planeFit.GetBase(); Base::Vector3f ex = planeFit.GetDirU(); Base::Vector3f ey = planeFit.GetDirV(); Base::Vector3f ez = planeFit.GetNormal(); // build the matrix for the inverse transformation Base::Matrix4D rInverse; rInverse.setToUnity(); rInverse[0][0] = static_cast(ex.x); rInverse[0][1] = static_cast(ey.x); rInverse[0][2] = static_cast(ez.x); rInverse[0][3] = static_cast(bs.x); rInverse[1][0] = static_cast(ex.y); rInverse[1][1] = static_cast(ey.y); rInverse[1][2] = static_cast(ez.y); rInverse[1][3] = static_cast(bs.y); rInverse[2][0] = static_cast(ex.z); rInverse[2][1] = static_cast(ey.z); rInverse[2][2] = static_cast(ez.z); rInverse[2][3] = static_cast(bs.z); return rInverse; } std::vector AbstractPolygonTriangulator::ProjectToFitPlane() { std::vector proj = _points; _inverse = GetTransformToFitPlane(); Base::Vector3f bs( static_cast(_inverse[0][3]), static_cast(_inverse[1][3]), static_cast(_inverse[2][3]) ); Base::Vector3f ex( static_cast(_inverse[0][0]), static_cast(_inverse[1][0]), static_cast(_inverse[2][0]) ); Base::Vector3f ey( static_cast(_inverse[0][1]), static_cast(_inverse[1][1]), static_cast(_inverse[2][1]) ); for (auto& jt : proj) { jt.TransformToCoordinateSystem(bs, ex, ey); } return proj; } void AbstractPolygonTriangulator::PostProcessing(const std::vector& points) { // For a good approximation we should have enough points, i.e. for 9 parameters // for the fit function we should have at least 50 points. unsigned int uMinPts = 50; PolynomialFit polyFit; Base::Vector3f bs( static_cast(_inverse[0][3]), static_cast(_inverse[1][3]), static_cast(_inverse[2][3]) ); Base::Vector3f ex( static_cast(_inverse[0][0]), static_cast(_inverse[1][0]), static_cast(_inverse[2][0]) ); Base::Vector3f ey( static_cast(_inverse[0][1]), static_cast(_inverse[1][1]), static_cast(_inverse[2][1]) ); for (auto pt : points) { pt.TransformToCoordinateSystem(bs, ex, ey); polyFit.AddPoint(pt); } if (polyFit.CountPoints() >= uMinPts && polyFit.Fit() < std::numeric_limits::max()) { for (auto& newpoint : _newpoints) { newpoint.z = static_cast(polyFit.Value(newpoint.x, newpoint.y)); } } } MeshGeomFacet AbstractPolygonTriangulator::GetTriangle( const MeshPointArray& points, const MeshFacet& facet ) const { MeshGeomFacet triangle; triangle._aclPoints[0] = points[facet._aulPoints[0]]; triangle._aclPoints[1] = points[facet._aulPoints[1]]; triangle._aclPoints[2] = points[facet._aulPoints[2]]; return triangle; } bool AbstractPolygonTriangulator::TriangulatePolygon() { try { if (!this->_indices.empty() && this->_points.size() != this->_indices.size()) { Base::Console() .log("Triangulation: %d points <> %d indices\n", _points.size(), _indices.size()); return false; } bool ok = Triangulate(); if (ok) { Done(); } return ok; } catch (const Base::Exception& e) { Base::Console().log("Triangulation: %s\n", e.what()); return false; } catch (const std::exception& e) { Base::Console().log("Triangulation: %s\n", e.what()); return false; } catch (...) { return false; } } std::vector AbstractPolygonTriangulator::GetInfo() const { return _info; } void AbstractPolygonTriangulator::Discard() { if (!_discard) { _discard = true; _info.pop_back(); } } void AbstractPolygonTriangulator::Reset() {} void AbstractPolygonTriangulator::Done() { _info.push_back(_points.size()); _discard = false; } // ------------------------------------------------------------- EarClippingTriangulator::EarClippingTriangulator() = default; bool EarClippingTriangulator::Triangulate() { _facets.clear(); _triangles.clear(); std::vector pts = ProjectToFitPlane(); std::vector result; // Invoke the triangulator to triangulate this polygon. Triangulate::Process(pts, result); // print out the results. size_t tcount = result.size() / 3; bool ok = tcount + 2 == _points.size(); if (tcount > _points.size()) { return false; // no valid triangulation } MeshGeomFacet clFacet; MeshFacet clTopFacet; for (size_t i = 0; i < tcount; i++) { if (Triangulate::_invert) { clFacet._aclPoints[0] = _points[result[i * 3 + 0]]; clFacet._aclPoints[2] = _points[result[i * 3 + 1]]; clFacet._aclPoints[1] = _points[result[i * 3 + 2]]; clTopFacet._aulPoints[0] = result[i * 3 + 0]; clTopFacet._aulPoints[2] = result[i * 3 + 1]; clTopFacet._aulPoints[1] = result[i * 3 + 2]; } else { clFacet._aclPoints[0] = _points[result[i * 3 + 0]]; clFacet._aclPoints[1] = _points[result[i * 3 + 1]]; clFacet._aclPoints[2] = _points[result[i * 3 + 2]]; clTopFacet._aulPoints[0] = result[i * 3 + 0]; clTopFacet._aulPoints[1] = result[i * 3 + 1]; clTopFacet._aulPoints[2] = result[i * 3 + 2]; } _triangles.push_back(clFacet); _facets.push_back(clTopFacet); } return ok; } float EarClippingTriangulator::Triangulate::Area(const std::vector& contour) { int n = contour.size(); float A = 0.0F; for (int p = n - 1, q = 0; q < n; p = q++) { A += contour[p].x * contour[q].y - contour[q].x * contour[p].y; } return A * 0.5F; } /* InsideTriangle decides if a point P is Inside of the triangle defined by A, B, C. */ bool EarClippingTriangulator::Triangulate::InsideTriangle( float Ax, float Ay, float Bx, float By, float Cx, float Cy, float Px, float Py ) { float ax {}, ay {}, bx {}, by {}, cx {}, cy {}, apx {}, apy {}, bpx {}, bpy {}, cpx {}, cpy {}; float cCROSSap {}, bCROSScp {}, aCROSSbp {}; ax = Cx - Bx; ay = Cy - By; bx = Ax - Cx; by = Ay - Cy; cx = Bx - Ax; cy = By - Ay; apx = Px - Ax; apy = Py - Ay; bpx = Px - Bx; bpy = Py - By; cpx = Px - Cx; cpy = Py - Cy; aCROSSbp = ax * bpy - ay * bpx; cCROSSap = cx * apy - cy * apx; bCROSScp = bx * cpy - by * cpx; return ( (aCROSSbp >= std::numeric_limits::epsilon()) && (bCROSScp >= std::numeric_limits::epsilon()) && (cCROSSap >= std::numeric_limits::epsilon()) ); } bool EarClippingTriangulator::Triangulate::Snip( const std::vector& contour, int u, int v, int w, int n, int* V ) { int p {}; float Ax {}, Ay {}, Bx {}, By {}, Cx {}, Cy {}, Px {}, Py {}; Ax = contour[V[u]].x; Ay = contour[V[u]].y; Bx = contour[V[v]].x; By = contour[V[v]].y; Cx = contour[V[w]].x; Cy = contour[V[w]].y; constexpr float eps = std::numeric_limits::epsilon(); if (eps > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) { return false; } for (p = 0; p < n; p++) { if ((p == u) || (p == v) || (p == w)) { continue; } Px = contour[V[p]].x; Py = contour[V[p]].y; if (InsideTriangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py)) { return false; } } return true; } bool EarClippingTriangulator::Triangulate::_invert = false; bool EarClippingTriangulator::Triangulate::Process( const std::vector& contour, std::vector& result ) { /* allocate and initialize list of Vertices in polygon */ int n = contour.size(); if (n < 3) { return false; } int* V = new int[n]; /* we want a counter-clockwise polygon in V */ if (0.0F < Area(contour)) { for (int v = 0; v < n; v++) { V[v] = v; } _invert = true; } // for(int v=0; v 2;) { /* if we loop, it is probably a non-simple polygon */ if (0 >= (count--)) { //** Triangulate: ERROR - probable bad polygon! delete[] V; return false; } /* three consecutive vertices in current polygon, */ int u = v; if (nv <= u) { u = 0; /* previous */ } v = u + 1; if (nv <= v) { v = 0; /* new v */ } int w = v + 1; if (nv <= w) { w = 0; /* next */ } if (Snip(contour, u, v, w, nv, V)) { int a {}, b {}, c {}, s {}, t {}; /* true names of the vertices */ a = V[u]; b = V[v]; c = V[w]; /* output Triangle */ result.push_back(a); result.push_back(b); result.push_back(c); /* remove v from remaining polygon */ for (s = v, t = v + 1; t < nv; s++, t++) { V[s] = V[t]; } nv--; /* reset error detection counter */ count = 2 * nv; } } delete[] V; return true; } // ------------------------------------------------------------- QuasiDelaunayTriangulator::QuasiDelaunayTriangulator() = default; bool QuasiDelaunayTriangulator::Triangulate() { if (!EarClippingTriangulator::Triangulate()) { return false; // no valid triangulation } // For each internal edge get the adjacent facets. When doing an edge swap we must update // this structure. std::map, std::vector> aEdge2Face; for (auto pI = _facets.begin(); pI != _facets.end(); ++pI) { for (int i = 0; i < 3; i++) { PointIndex ulPt0 = std::min(pI->_aulPoints[i], pI->_aulPoints[(i + 1) % 3]); PointIndex ulPt1 = std::max(pI->_aulPoints[i], pI->_aulPoints[(i + 1) % 3]); // ignore borderlines of the polygon if ((ulPt1 - ulPt0) % (_points.size() - 1) > 1) { aEdge2Face[std::pair(ulPt0, ulPt1)].push_back( pI - _facets.begin() ); } } } // fill up this list with all internal edges and perform swap edges until this list is empty std::list> aEdgeList; std::map, std::vector>::iterator pE; for (pE = aEdge2Face.begin(); pE != aEdge2Face.end(); ++pE) { aEdgeList.push_back(pE->first); } // to be sure to avoid an endless loop size_t uMaxIter = 5 * aEdge2Face.size(); // Perform a swap edge where needed while (!aEdgeList.empty() && uMaxIter > 0) { // get the first edge and remove it from the list std::pair aEdge = aEdgeList.front(); aEdgeList.pop_front(); uMaxIter--; // get the adjacent facets to this edge pE = aEdge2Face.find(aEdge); // this edge has been removed some iterations before if (pE == aEdge2Face.end()) { continue; } MeshFacet& rF1 = _facets[pE->second[0]]; MeshFacet& rF2 = _facets[pE->second[1]]; unsigned short side1 = rF1.Side(aEdge.first, aEdge.second); Base::Vector3f cP1 = _points[rF1._aulPoints[side1]]; Base::Vector3f cP2 = _points[rF1._aulPoints[(side1 + 1) % 3]]; Base::Vector3f cP3 = _points[rF1._aulPoints[(side1 + 2) % 3]]; unsigned short side2 = rF2.Side(aEdge.first, aEdge.second); Base::Vector3f cP4 = _points[rF2._aulPoints[(side2 + 2) % 3]]; MeshGeomFacet cT1(cP1, cP2, cP3); float fMax1 = cT1.MaximumAngle(); MeshGeomFacet cT2(cP2, cP1, cP4); float fMax2 = cT2.MaximumAngle(); MeshGeomFacet cT3(cP4, cP3, cP1); float fMax3 = cT3.MaximumAngle(); MeshGeomFacet cT4(cP3, cP4, cP2); float fMax4 = cT4.MaximumAngle(); float fMax12 = std::max(fMax1, fMax2); float fMax34 = std::max(fMax3, fMax4); // We must make sure that the two adjacent triangles builds a convex polygon, otherwise // the swap edge operation is illegal Base::Vector3f cU = cP2 - cP1; Base::Vector3f cV = cP4 - cP3; // build a helper plane through cP1 that must separate cP3 and cP4 Base::Vector3f cN1 = (cU % cV) % cU; if (((cP3 - cP1) * cN1) * ((cP4 - cP1) * cN1) >= 0.0F) { continue; // not convex } // build a helper plane through cP3 that must separate cP1 and cP2 Base::Vector3f cN2 = (cU % cV) % cV; if (((cP1 - cP3) * cN2) * ((cP2 - cP3) * cN2) >= 0.0F) { continue; // not convex } // ok, here we should perform a swap edge to minimize the maximum angle if (fMax12 > fMax34) { rF1._aulPoints[(side1 + 1) % 3] = rF2._aulPoints[(side2 + 2) % 3]; rF2._aulPoints[(side2 + 1) % 3] = rF1._aulPoints[(side1 + 2) % 3]; // adjust the edge list for (int i = 0; i < 3; i++) { std::map, std::vector>::iterator it; // first facet PointIndex ulPt0 = std::min(rF1._aulPoints[i], rF1._aulPoints[(i + 1) % 3]); PointIndex ulPt1 = std::max(rF1._aulPoints[i], rF1._aulPoints[(i + 1) % 3]); it = aEdge2Face.find(std::make_pair(ulPt0, ulPt1)); if (it != aEdge2Face.end()) { if (it->second[0] == pE->second[1]) { it->second[0] = pE->second[0]; } else if (it->second[1] == pE->second[1]) { it->second[1] = pE->second[0]; } aEdgeList.push_back(it->first); } // second facet ulPt0 = std::min(rF2._aulPoints[i], rF2._aulPoints[(i + 1) % 3]); ulPt1 = std::max(rF2._aulPoints[i], rF2._aulPoints[(i + 1) % 3]); it = aEdge2Face.find(std::make_pair(ulPt0, ulPt1)); if (it != aEdge2Face.end()) { if (it->second[0] == pE->second[0]) { it->second[0] = pE->second[1]; } else if (it->second[1] == pE->second[0]) { it->second[1] = pE->second[1]; } aEdgeList.push_back(it->first); } } // Now we must remove the edge and replace it through the new edge PointIndex ulPt0 = std::min( rF1._aulPoints[(side1 + 1) % 3], rF2._aulPoints[(side2 + 1) % 3] ); PointIndex ulPt1 = std::max( rF1._aulPoints[(side1 + 1) % 3], rF2._aulPoints[(side2 + 1) % 3] ); std::pair aNewEdge = std::make_pair(ulPt0, ulPt1); aEdge2Face[aNewEdge] = pE->second; aEdge2Face.erase(pE); } } return true; } // ------------------------------------------------------------- namespace MeshCore { namespace Triangulation { struct Vertex2d_Less { bool operator()(const Base::Vector3f& p, const Base::Vector3f& q) const { if (std::fabs(p.x - q.x) < MeshDefinitions::_fMinPointDistanceD1) { if (std::fabs(p.y - q.y) < MeshDefinitions::_fMinPointDistanceD1) { return false; } return p.y < q.y; } return p.x < q.x; } }; struct Vertex2d_EqualTo { bool operator()(const Base::Vector3f& p, const Base::Vector3f& q) const { if (std::fabs(p.x - q.x) < MeshDefinitions::_fMinPointDistanceD1 && std::fabs(p.y - q.y) < MeshDefinitions::_fMinPointDistanceD1) { return true; } return false; } }; } // namespace Triangulation } // namespace MeshCore DelaunayTriangulator::DelaunayTriangulator() = default; bool DelaunayTriangulator::Triangulate() { // before starting the triangulation we must make sure that all polygon // points are different std::vector aPoints = _points; // sort the points ascending x,y coordinates std::sort(aPoints.begin(), aPoints.end(), Triangulation::Vertex2d_Less()); // if there are two adjacent points whose distance is less then an epsilon if (std::adjacent_find(aPoints.begin(), aPoints.end(), Triangulation::Vertex2d_EqualTo()) < aPoints.end()) { return false; } _facets.clear(); _triangles.clear(); std::vector akVertex; akVertex.reserve(_points.size()); for (const auto& point : _points) { akVertex.emplace_back(static_cast(point.x), static_cast(point.y)); } Wm4::Delaunay2d del(static_cast(akVertex.size()), akVertex.data(), 0.001, false, Wm4::Query::QT_INT64); int iTQuantity = del.GetSimplexQuantity(); auto numFaces = static_cast(iTQuantity); std::vector aiTVertex(3 * numFaces); bool succeeded = false; if (numFaces > 0) { size_t uiSize = 3 * numFaces * sizeof(int); Wm4::System::Memcpy(aiTVertex.data(), uiSize, del.GetIndices(), uiSize); // If H is the number of hull edges and N is the number of vertices, // then the triangulation must have 2*N-2-H triangles and 3*N-3-H // edges. int iEQuantity = 0; int* aiIndex = nullptr; del.GetHull(iEQuantity, aiIndex); int iUniqueVQuantity = del.GetUniqueVertexQuantity(); int iTVerify = 2 * iUniqueVQuantity - 2 - iEQuantity; (void)iTVerify; // avoid warning in release build succeeded = (iTVerify == iTQuantity); int iEVerify = 3 * iUniqueVQuantity - 3 - iEQuantity; (void)iEVerify; // avoid warning about unused variable delete[] aiIndex; } MeshGeomFacet triangle; MeshFacet facet; for (std::size_t i = 0; i < numFaces; i++) { for (std::size_t j = 0; j < 3; j++) { auto index = static_cast(aiTVertex[3 * i + j]); facet._aulPoints[j] = static_cast(index); triangle._aclPoints[j].x = static_cast(akVertex[index].X()); triangle._aclPoints[j].y = static_cast(akVertex[index].Y()); } _triangles.push_back(triangle); _facets.push_back(facet); } return succeeded; } // ------------------------------------------------------------- FlatTriangulator::FlatTriangulator() = default; bool FlatTriangulator::Triangulate() { _newpoints.clear(); // before starting the triangulation we must make sure that all polygon // points are different std::vector aPoints = ProjectToFitPlane(); std::vector tmp = aPoints; // sort the points ascending x,y coordinates std::sort(tmp.begin(), tmp.end(), Triangulation::Vertex2d_Less()); // if there are two adjacent points whose distance is less then an epsilon if (std::adjacent_find(tmp.begin(), tmp.end(), Triangulation::Vertex2d_EqualTo()) < tmp.end()) { return false; } _facets.clear(); _triangles.clear(); // Todo: Implement algorithm for constraint delaunay triangulation QuasiDelaunayTriangulator tria; tria.SetPolygon(this->GetPolygon()); bool succeeded = tria.TriangulatePolygon(); this->_facets = tria.GetFacets(); this->_triangles = tria.GetTriangles(); return succeeded; } void FlatTriangulator::PostProcessing(const std::vector&) {} // ------------------------------------------------------------- ConstraintDelaunayTriangulator::ConstraintDelaunayTriangulator(float area) : fMaxArea(area) { // silent warning: -Wunused-private-field (void)fMaxArea; } bool ConstraintDelaunayTriangulator::Triangulate() { _newpoints.clear(); // before starting the triangulation we must make sure that all polygon // points are different std::vector aPoints = ProjectToFitPlane(); std::vector tmp = aPoints; // sort the points ascending x,y coordinates std::sort(tmp.begin(), tmp.end(), Triangulation::Vertex2d_Less()); // if there are two adjacent points whose distance is less then an epsilon if (std::adjacent_find(tmp.begin(), tmp.end(), Triangulation::Vertex2d_EqualTo()) < tmp.end()) { return false; } _facets.clear(); _triangles.clear(); // Todo: Implement algorithm for constraint delaunay triangulation QuasiDelaunayTriangulator tria; tria.SetPolygon(this->GetPolygon()); bool succeeded = tria.TriangulatePolygon(); this->_facets = tria.GetFacets(); this->_triangles = tria.GetTriangles(); return succeeded; } // ------------------------------------------------------------- #if 0 Triangulator::Triangulator(const MeshKernel& k, bool flat) : _kernel(k) { } Triangulator::~Triangulator() { } bool Triangulator::Triangulate() { return false; } MeshGeomFacet Triangulator::GetTriangle(const MeshPointArray&, const MeshFacet& facet) const { return MeshGeomFacet(); } void Triangulator::PostProcessing(const std::vector&) { } void Triangulator::Discard() { AbstractPolygonTriangulator::Discard(); } void Triangulator::Reset() { } #endif