// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2024 Werner Mayer * * * * This file is part of FreeCAD. * * * * FreeCAD 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. * * * * FreeCAD 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 FreeCAD. If not, see * * . * * * **************************************************************************/ #include #include #include "BRepMesh.h" #include using namespace Part; namespace { struct MeshVertex { Base::Vector3d p; std::size_t i = 0; explicit MeshVertex(const Base::Vector3d& p) : p(p) {} Base::Vector3d toPoint() const { return p; } bool operator<(const MeshVertex& v) const { if (p.x != v.p.x) { return p.x < v.p.x; } if (p.y != v.p.y) { return p.y < v.p.y; } if (p.z != v.p.z) { return p.z < v.p.z; } // points are equal return false; } }; class MergeVertex { public: using Facet = BRepMesh::Facet; MergeVertex(std::vector points, std::vector faces, double tolerance) : points {std::move(points)} , faces {std::move(faces)} , tolerance {tolerance} { setDefaultMap(); check(); } bool hasDuplicatedPoints() const { return duplicatedPoints > 0; } void mergeDuplicatedPoints() { if (!hasDuplicatedPoints()) { return; } redirectPointIndex(); auto degreeMap = getPointDegrees(); decrementPointIndex(degreeMap); removeUnusedPoints(degreeMap); reset(); } std::vector getPoints() const { return points; } std::vector getFacets() const { return faces; } private: void setDefaultMap() { // by default map point index to itself mapPointIndex.resize(points.size()); std::generate(mapPointIndex.begin(), mapPointIndex.end(), Base::iotaGen(0)); } void reset() { mapPointIndex.clear(); duplicatedPoints = 0; } void check() { using VertexIterator = std::vector::const_iterator; double tol3d = tolerance; auto vertexLess = [tol3d](const VertexIterator& v1, const VertexIterator& v2) { if (fabs(v1->x - v2->x) >= tol3d) { return v1->x < v2->x; } if (fabs(v1->y - v2->y) >= tol3d) { return v1->y < v2->y; } if (fabs(v1->z - v2->z) >= tol3d) { return v1->z < v2->z; } return false; // points are considered to be equal }; auto vertexEqual = [&](const VertexIterator& v1, const VertexIterator& v2) { if (vertexLess(v1, v2)) { return false; } if (vertexLess(v2, v1)) { return false; } return true; }; std::vector vertices; vertices.reserve(points.size()); for (auto it = points.cbegin(); it != points.cend(); ++it) { vertices.push_back(it); } std::sort(vertices.begin(), vertices.end(), vertexLess); auto next = vertices.begin(); while (next != vertices.end()) { next = std::adjacent_find(next, vertices.end(), vertexEqual); if (next != vertices.end()) { auto first = next; std::size_t first_index = *first - points.begin(); ++next; while (next != vertices.end() && vertexEqual(*first, *next)) { std::size_t next_index = *next - points.begin(); mapPointIndex[next_index] = first_index; ++duplicatedPoints; ++next; } } } } void redirectPointIndex() { for (auto& face : faces) { face.I1 = int(mapPointIndex[face.I1]); face.I2 = int(mapPointIndex[face.I2]); face.I3 = int(mapPointIndex[face.I3]); } } std::vector getPointDegrees() const { std::vector degreeMap; degreeMap.resize(points.size()); for (const auto& face : faces) { degreeMap[face.I1]++; degreeMap[face.I2]++; degreeMap[face.I3]++; } return degreeMap; } void decrementPointIndex(const std::vector& degreeMap) { std::vector decrements; decrements.resize(points.size()); std::size_t decr = 0; for (std::size_t pos = 0; pos < points.size(); pos++) { decrements[pos] = decr; if (degreeMap[pos] == 0) { decr++; } } for (auto& face : faces) { face.I1 -= int(decrements[face.I1]); face.I2 -= int(decrements[face.I2]); face.I3 -= int(decrements[face.I3]); } } void removeUnusedPoints(const std::vector& degreeMap) { // remove unreferenced points std::vector new_points; new_points.reserve(points.size() - duplicatedPoints); for (std::size_t pos = 0; pos < points.size(); ++pos) { if (degreeMap[pos] > 0) { new_points.push_back(points[pos]); } } points.swap(new_points); } private: std::vector points; std::vector faces; double tolerance = 0.0; std::size_t duplicatedPoints = 0; std::vector mapPointIndex; }; } // namespace void BRepMesh::getFacesFromDomains( const std::vector& domains, std::vector& points, std::vector& faces ) { std::size_t numFaces = 0; for (const auto& it : domains) { numFaces += it.facets.size(); } faces.reserve(numFaces); std::set vertices; auto addVertex = [&vertices](const Base::Vector3d& pnt, uint32_t& pointIndex) { MeshVertex vertex(pnt); vertex.i = vertices.size(); auto it = vertices.insert(vertex); pointIndex = it.first->i; }; for (const auto& domain : domains) { std::size_t numDomainFaces = 0; for (const Facet& df : domain.facets) { Facet face; // 1st vertex addVertex(domain.points[df.I1], face.I1); // 2nd vertex addVertex(domain.points[df.I2], face.I2); // 3rd vertex addVertex(domain.points[df.I3], face.I3); // make sure that we don't insert invalid facets if (face.I1 != face.I2 && face.I2 != face.I3 && face.I3 != face.I1) { faces.push_back(face); numDomainFaces++; } } domainSizes.push_back(numDomainFaces); } std::vector meshPoints; meshPoints.resize(vertices.size()); for (const auto& vertex : vertices) { meshPoints[vertex.i] = vertex.toPoint(); } points.swap(meshPoints); MergeVertex merge(points, faces, Precision::Confusion()); if (merge.hasDuplicatedPoints()) { merge.mergeDuplicatedPoints(); points = merge.getPoints(); faces = merge.getFacets(); } } std::vector BRepMesh::createSegments() const { std::size_t numMeshFaces = 0; std::vector segm; for (size_t numDomainFaces : domainSizes) { Segment segment(numDomainFaces); std::generate(segment.begin(), segment.end(), Base::iotaGen(numMeshFaces)); numMeshFaces += numDomainFaces; segm.push_back(segment); } return segm; }