/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2019 OpenFOAM Foundation Copyright (C) 2019 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. OpenFOAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenFOAM 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 General Public License for more details. You should have received a copy of the GNU General Public License along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. \*---------------------------------------------------------------------------*/ #include "isoSurfaceTopo.H" #include "isoSurface.H" #include "polyMesh.H" #include "tetMatcher.H" #include "tetPointRef.H" #include "DynamicField.H" #include "syncTools.H" #include "polyMeshTetDecomposition.H" #include "cyclicACMIPolyPatch.H" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // namespace Foam { defineTypeNameAndDebug(isoSurfaceTopo, 0); } // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // namespace Foam { // Does any edge of triangle cross iso value? inline static bool isTriCut ( const label a, const label b, const label c, const scalarField& pointValues, const scalar isoValue ) { const bool aLower = (pointValues[a] < isoValue); const bool bLower = (pointValues[b] < isoValue); const bool cLower = (pointValues[c] < isoValue); return !(aLower == bLower && aLower == cLower); } } // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // Foam::isoSurfaceTopo::cellCutType Foam::isoSurfaceTopo::calcCutType ( const bool isTet, const label celli ) const { if (ignoreCells_.test(celli)) { return NOTCUT; } const cell& cFaces = mesh_.cells()[celli]; if (isTet) { for (const label facei : cFaces) { if ( !mesh_.isInternalFace(facei) && ignoreBoundaryFaces_.test(facei-mesh_.nInternalFaces()) ) { continue; } const face& f = mesh_.faces()[facei]; for (label fp = 1; fp < f.size() - 1; ++fp) { if (isTriCut(f[0], f[fp], f[f.fcIndex(fp)], pVals_, iso_)) { return CUT; } } } return NOTCUT; } const bool cellLower = (cVals_[celli] < iso_); // First check if there is any cut in cell bool edgeCut = false; for (const label facei : cFaces) { if ( !mesh_.isInternalFace(facei) && ignoreBoundaryFaces_.test(facei-mesh_.nInternalFaces()) ) { continue; } const face& f = mesh_.faces()[facei]; // Check pyramids cut for (const label pointi : f) { if ((pVals_[pointi] < iso_) != cellLower) { edgeCut = true; break; } } if (edgeCut) { break; } // Check (triangulated) face edges // With fallback for problem decompositions const label fp0 = (tetBasePtIs_[facei] < 0 ? 0 : tetBasePtIs_[facei]); label fp = f.fcIndex(fp0); for (label i = 2; i < f.size(); ++i) { const label nextFp = f.fcIndex(fp); if (isTriCut(f[fp0], f[fp], f[nextFp], pVals_, iso_)) { edgeCut = true; break; } fp = nextFp; } if (edgeCut) { break; } } if (edgeCut) { // Count actual cuts (expensive since addressing needed) // Note: not needed if you don't want to preserve maxima/minima // centred around cellcentre. In that case just always return CUT const labelList& cPoints = mesh_.cellPoints(celli); label nPyrCuts = 0; for (const label pointi : cPoints) { if ((pVals_[pointi] < iso_) != cellLower) { ++nPyrCuts; } } if (nPyrCuts == cPoints.size()) { return SPHERE; } else if (nPyrCuts) { return CUT; } } return NOTCUT; } Foam::label Foam::isoSurfaceTopo::calcCutTypes ( tetMatcher& tet, List<cellCutType>& cellCutTypes ) { cellCutTypes.setSize(mesh_.nCells()); label nCutCells = 0; forAll(cellCutTypes, celli) { cellCutTypes[celli] = calcCutType(tet.isA(mesh_, celli), celli); if (cellCutTypes[celli] == CUT) { ++nCutCells; } } if (debug) { Pout<< "isoSurfaceCell : candidate cut cells " << nCutCells << " / " << mesh_.nCells() << endl; } return nCutCells; } Foam::scalar Foam::isoSurfaceTopo::minTetQ ( const label facei, const label faceBasePtI ) const { scalar q = polyMeshTetDecomposition::minQuality ( mesh_, mesh_.cellCentres()[mesh_.faceOwner()[facei]], facei, true, faceBasePtI ); if (mesh_.isInternalFace(facei)) { q = min ( q, polyMeshTetDecomposition::minQuality ( mesh_, mesh_.cellCentres()[mesh_.faceNeighbour()[facei]], facei, false, faceBasePtI ) ); } return q; } void Foam::isoSurfaceTopo::fixTetBasePtIs() { // Determine points used by two faces on the same cell const cellList& cells = mesh_.cells(); const faceList& faces = mesh_.faces(); const labelList& faceOwner = mesh_.faceOwner(); const labelList& faceNeighbour = mesh_.faceNeighbour(); // Get face triangulation base point tetBasePtIs_ = mesh_.tetBasePtIs(); // Pre-filter: mark all cells with illegal base points bitSet problemCells(cells.size()); forAll(tetBasePtIs_, facei) { if (tetBasePtIs_[facei] == -1) { problemCells.set(faceOwner[facei]); if (mesh_.isInternalFace(facei)) { problemCells.set(faceNeighbour[facei]); } } } // Mark all points that are shared by just two faces within an adjacent // problem cell as problematic bitSet problemPoints(mesh_.points().size()); { // Number of times a point occurs in a cell. // Used to detect dangling vertices (count = 2) Map<label> pointCount; // Analyse problem cells for points shared by two faces only for (const label celli : problemCells) { pointCount.clear(); for (const label facei : cells[celli]) { for (const label pointi : faces[facei]) { ++pointCount(pointi); } } forAllConstIters(pointCount, iter) { if (iter.val() == 1) { FatalErrorInFunction << "point:" << iter.key() << " at:" << mesh_.points()[iter.key()] << " only used by one face" << nl << exit(FatalError); } else if (iter.val() == 2) { problemPoints.set(iter.key()); } } } } // For all faces which form a part of a problem-cell, check if the base // point is adjacent to any problem points. If it is, re-calculate the base // point so that it is not. label nAdapted = 0; forAll(tetBasePtIs_, facei) { if ( problemCells[faceOwner[facei]] || (mesh_.isInternalFace(facei) && problemCells[faceNeighbour[facei]]) ) { const face& f = faces[facei]; // Check if either of the points adjacent to the base point is a // problem point. If not, the existing base point can be retained. const label fp0 = tetBasePtIs_[facei] < 0 ? 0 : tetBasePtIs_[facei]; if ( !problemPoints[f[f.rcIndex(fp0)]] && !problemPoints[f[f.fcIndex(fp0)]] ) { continue; } // A new base point is required. Pick the point that results in the // least-worst tet and which is not adjacent to any problem points. scalar maxQ = -GREAT; label maxFp = -1; forAll(f, fp) { if ( !problemPoints[f[f.rcIndex(fp)]] && !problemPoints[f[f.fcIndex(fp)]] ) { const scalar q = minTetQ(facei, fp); if (q > maxQ) { maxQ = q; maxFp = fp; } } } if (maxFp != -1) { // Success! Set the new base point tetBasePtIs_[facei] = maxFp; } else { // No point was found on face that would not result in some // duplicate triangle. Do what? Continue and hope? Spit an // error? Silently or noisily reduce the filtering level? tetBasePtIs_[facei] = 0; } ++nAdapted; } } if (debug) { Pout<< "isoSurface : adapted starting point of triangulation on " << nAdapted << " faces." << endl; } syncTools::syncFaceList(mesh_, tetBasePtIs_, maxEqOp<label>()); } Foam::label Foam::isoSurfaceTopo::generatePoint ( const label facei, const bool edgeIsDiag, const edge& vertices, DynamicList<edge>& pointToVerts, DynamicList<label>& pointToFace, DynamicList<bool>& pointFromDiag, EdgeMap<label>& vertsToPoint ) const { const auto edgeFnd = vertsToPoint.cfind(vertices); if (edgeFnd.found()) { return edgeFnd.val(); } // Generate new point label pointi = pointToVerts.size(); pointToVerts.append(vertices); pointToFace.append(facei); pointFromDiag.append(edgeIsDiag); vertsToPoint.insert(vertices, pointi); return pointi; } void Foam::isoSurfaceTopo::generateTriPoints ( const label facei, const FixedList<scalar, 4>& s, const FixedList<point, 4>& p, const FixedList<label, 4>& pIndex, const FixedList<bool, 6>& edgeIsDiag,// Per tet edge whether is face diag DynamicList<edge>& pointToVerts, DynamicList<label>& pointToFace, DynamicList<bool>& pointFromDiag, EdgeMap<label>& vertsToPoint, DynamicList<label>& verts // Every three verts is new triangle ) const { int triIndex = 0; if (s[0] < iso_) { triIndex |= 1; } if (s[1] < iso_) { triIndex |= 2; } if (s[2] < iso_) { triIndex |= 4; } if (s[3] < iso_) { triIndex |= 8; } // Form the vertices of the triangles for each case switch (triIndex) { case 0x00: case 0x0F: break; case 0x01: case 0x0E: { verts.append ( generatePoint ( facei, edgeIsDiag[0], edge(pIndex[0], pIndex[1]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[1], edge(pIndex[0], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[2], edge(pIndex[0], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); if (triIndex == 0x0E) { // Flip normals label sz = verts.size(); Swap(verts[sz-2], verts[sz-1]); } } break; case 0x02: case 0x0D: { verts.append ( generatePoint ( facei, edgeIsDiag[0], edge(pIndex[1], pIndex[0]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[3], edge(pIndex[1], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[4], edge(pIndex[1], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); if (triIndex == 0x0D) { // Flip normals label sz = verts.size(); Swap(verts[sz-2], verts[sz-1]); } } break; case 0x03: case 0x0C: { label p0p2 ( generatePoint ( facei, edgeIsDiag[1], edge(pIndex[0], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); label p1p3 ( generatePoint ( facei, edgeIsDiag[3], edge(pIndex[1], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[2], edge(pIndex[0], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p1p3); verts.append(p0p2); verts.append(p1p3); verts.append ( generatePoint ( facei, edgeIsDiag[4], edge(pIndex[1], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p0p2); if (triIndex == 0x0C) { // Flip normals label sz = verts.size(); Swap(verts[sz-5], verts[sz-4]); Swap(verts[sz-2], verts[sz-1]); } } break; case 0x04: case 0x0B: { verts.append ( generatePoint ( facei, edgeIsDiag[1], edge(pIndex[2], pIndex[0]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[4], edge(pIndex[2], pIndex[1]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[5], edge(pIndex[2], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); if (triIndex == 0x0B) { // Flip normals label sz = verts.size(); Swap(verts[sz-2], verts[sz-1]); } } break; case 0x05: case 0x0A: { label p0p1 ( generatePoint ( facei, edgeIsDiag[0], edge(pIndex[0], pIndex[1]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); label p2p3 ( generatePoint ( facei, edgeIsDiag[5], edge(pIndex[2], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p0p1); verts.append(p2p3); verts.append ( generatePoint ( facei, edgeIsDiag[2], edge(pIndex[0], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p0p1); verts.append ( generatePoint ( facei, edgeIsDiag[4], edge(pIndex[1], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p2p3); if (triIndex == 0x0A) { // Flip normals label sz = verts.size(); Swap(verts[sz-5], verts[sz-4]); Swap(verts[sz-2], verts[sz-1]); } } break; case 0x06: case 0x09: { label p0p1 ( generatePoint ( facei, edgeIsDiag[0], edge(pIndex[0], pIndex[1]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); label p2p3 ( generatePoint ( facei, edgeIsDiag[5], edge(pIndex[2], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p0p1); verts.append ( generatePoint ( facei, edgeIsDiag[3], edge(pIndex[1], pIndex[3]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append(p2p3); verts.append(p0p1); verts.append(p2p3); verts.append ( generatePoint ( facei, edgeIsDiag[1], edge(pIndex[0], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); if (triIndex == 0x09) { // Flip normals label sz = verts.size(); Swap(verts[sz-5], verts[sz-4]); Swap(verts[sz-2], verts[sz-1]); } } break; case 0x08: case 0x07: { verts.append ( generatePoint ( facei, edgeIsDiag[2], edge(pIndex[3], pIndex[0]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[5], edge(pIndex[3], pIndex[2]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); verts.append ( generatePoint ( facei, edgeIsDiag[3], edge(pIndex[3], pIndex[1]), pointToVerts, pointToFace, pointFromDiag, vertsToPoint ) ); if (triIndex == 0x07) { // Flip normals label sz = verts.size(); Swap(verts[sz-2], verts[sz-1]); } } break; } } void Foam::isoSurfaceTopo::generateTriPoints ( const label celli, const bool isTet, DynamicList<edge>& pointToVerts, DynamicList<label>& pointToFace, DynamicList<bool>& pointFromDiag, EdgeMap<label>& vertsToPoint, DynamicList<label>& verts, DynamicList<label>& faceLabels ) const { const faceList& faces = mesh_.faces(); const labelList& faceOwner = mesh_.faceOwner(); const pointField& points = mesh_.points(); const cell& cFaces = mesh_.cells()[celli]; if (isTet) { // For tets don't do cell-centre decomposition, just use the // tet points and values label facei = cFaces[0]; const face& f0 = faces[facei]; // Get the other point from f1. Tbd: check if not duplicate face // (ACMI / ignoreBoundaryFaces_). const face& f1 = faces[cFaces[1]]; label oppositeI = -1; forAll(f1, fp) { oppositeI = f1[fp]; if (!f0.found(oppositeI)) { break; } } label p0 = f0[0]; label p1 = f0[1]; label p2 = f0[2]; FixedList<bool, 6> edgeIsDiag(false); if (faceOwner[facei] == celli) { Swap(p1, p2); } tetPointRef tet ( points[p0], points[p1], points[p2], points[oppositeI] ); label startTrii = verts.size(); generateTriPoints ( facei, FixedList<scalar, 4> ({ pVals_[p0], pVals_[p1], pVals_[p2], pVals_[oppositeI] }), FixedList<point, 4> ({ points[p0], points[p1], points[p2], points[oppositeI] }), FixedList<label, 4> ({ p0, p1, p2, oppositeI }), edgeIsDiag, pointToVerts, pointToFace, pointFromDiag, vertsToPoint, verts // Every three verts is new triangle ); label nTris = (verts.size()-startTrii)/3; for (label i = 0; i < nTris; ++i) { faceLabels.append(facei); } } else { const pointField& cellCentres = mesh_.cellCentres(); for (const label facei : cFaces) { if ( !mesh_.isInternalFace(facei) && ignoreBoundaryFaces_.test(facei-mesh_.nInternalFaces()) ) { continue; } const face& f = faces[facei]; label fp0 = tetBasePtIs_[facei]; label startTrii = verts.size(); // Fallback if (fp0 < 0) { fp0 = 0; } label fp = f.fcIndex(fp0); for (label i = 2; i < f.size(); ++i) { label nextFp = f.fcIndex(fp); FixedList<bool, 6> edgeIsDiag(false); label p0 = f[fp0]; label p1 = f[fp]; label p2 = f[nextFp]; if (faceOwner[facei] == celli) { Swap(p1, p2); if (i != 2) edgeIsDiag[1] = true; if (i != f.size()-1) edgeIsDiag[0] = true; } else { if (i != 2) edgeIsDiag[0] = true; if (i != f.size()-1) edgeIsDiag[1] = true; } tetPointRef tet ( points[p0], points[p1], points[p2], cellCentres[celli] ); generateTriPoints ( facei, FixedList<scalar, 4> ({ pVals_[p0], pVals_[p1], pVals_[p2], cVals_[celli] }), FixedList<point, 4> ({ points[p0], points[p1], points[p2], cellCentres[celli] }), FixedList<label, 4> ({ p0, p1, p2, mesh_.nPoints()+celli }), edgeIsDiag, pointToVerts, pointToFace, pointFromDiag, vertsToPoint, verts // Every three verts is new triangle ); fp = nextFp; } label nTris = (verts.size()-startTrii)/3; for (label i = 0; i < nTris; ++i) { faceLabels.append(facei); } } } } void Foam::isoSurfaceTopo::triangulateOutside ( const bool filterDiag, const primitivePatch& pp, const boolList& pointFromDiag, const labelList& pointToFace, const label cellID, DynamicList<face>& compactFaces, DynamicList<label>& compactCellIDs ) const { // We can form pockets: // - 1. triangle on face // - 2. multiple triangles on interior (from diag edges) // - the edge loop will be pocket since it is only the diag // edges that give it volume? // Retriangulate the exterior loops const labelListList& edgeLoops = pp.edgeLoops(); const labelList& mp = pp.meshPoints(); for (const labelList& loop : edgeLoops) { if (loop.size() > 2) { compactFaces.append(face(0)); face& f = compactFaces.last(); f.setSize(loop.size()); label fpi = 0; forAll(f, i) { label pointi = mp[loop[i]]; if (filterDiag && pointFromDiag[pointi]) { label prevPointi = mp[loop[loop.fcIndex(i)]]; if ( pointFromDiag[prevPointi] && (pointToFace[pointi] != pointToFace[prevPointi]) ) { f[fpi++] = pointi; } else { // Filter out diagonal point } } else { f[fpi++] = pointi; } } if (fpi > 2) { f.setSize(fpi); } else { // Keep original face forAll(f, i) { label pointi = mp[loop[i]]; f[i] = pointi; } } compactCellIDs.append(cellID); } } } Foam::MeshedSurface<Foam::face> Foam::isoSurfaceTopo::removeInsidePoints ( const bool filterDiag, const MeshStorage& s, const boolList& pointFromDiag, const labelList& pointToFace, const labelList& start, // Per cell the starting triangle DynamicList<label>& pointCompactMap, // Per returned point the original DynamicList<label>& compactCellIDs // Per returned tri the cellID ) const { const pointField& points = s.points(); pointCompactMap.clear(); compactCellIDs.clear(); DynamicList<face> compactFaces(s.size()/8); for (label celli = 0; celli < start.size()-1; ++celli) { // All triangles of the current cell label nTris = start[celli+1]-start[celli]; if (nTris) { const SubList<face> cellFaces(s, nTris, start[celli]); const primitivePatch pp(cellFaces, points); triangulateOutside ( filterDiag, pp, pointFromDiag, pointToFace, //protectedFace, celli, compactFaces, compactCellIDs ); } } // Compact out unused points // Pick up the used vertices labelList oldToCompact(points.size(), -1); DynamicField<point> compactPoints(points.size()); pointCompactMap.clear(); for (face& f : compactFaces) { forAll(f, fp) { label pointi = f[fp]; label compacti = oldToCompact[pointi]; if (compacti == -1) { compacti = compactPoints.size(); oldToCompact[pointi] = compacti; compactPoints.append(points[pointi]); pointCompactMap.append(pointi); } f[fp] = compacti; } } MeshStorage cpSurface ( std::move(compactPoints), std::move(compactFaces), s.surfZones() ); return cpSurface; } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::isoSurfaceTopo::isoSurfaceTopo ( const polyMesh& mesh, const scalarField& cVals, const scalarField& pVals, const scalar iso, const filterType filter, const boundBox& bounds, const bitSet& ignoreCells ) : isoSurfaceBase(iso, bounds), mesh_(mesh), cVals_(cVals), pVals_(pVals), ignoreCells_(ignoreCells) { if (debug) { Pout<< "isoSurfaceTopo::" << " cell min/max : " << min(cVals_) << " / " << max(cVals_) << nl << " point min/max : " << min(pVals_) << " / " << max(pVals_) << nl << " isoValue : " << iso << nl << " filter : " << isoSurfaceTopo::filterNames[filter] << nl << " mesh span : " << mesh.bounds().mag() << nl << " ignoreCells : " << ignoreCells_.count() << " / " << cVals_.size() << nl << endl; } // Determine boundary pyramids to ignore (since originating from ACMI faces) // Maybe needs to be optional argument or more general detect colocated // faces. { const polyBoundaryMesh& pbm = mesh_.boundaryMesh(); forAll(pbm, patchi) { const polyPatch& pp = pbm[patchi]; if (isA<cyclicACMIPolyPatch>(pp)) { ignoreBoundaryFaces_.setSize(mesh_.nBoundaryFaces()); forAll(pp, i) { label facei = pp.start()+i; ignoreBoundaryFaces_.set(facei-mesh_.nInternalFaces()); } } } } fixTetBasePtIs(); tetMatcher tet; // Determine if any cut through cell List<cellCutType> cellCutTypes; const label nCutCells = calcCutTypes(tet, cellCutTypes); // Per cell: 5 pyramids cut, each generating 2 triangles // - pointToVerts : from generated iso point to originating mesh verts DynamicList<edge> pointToVerts(10*nCutCells); // - pointToFace : from generated iso point to originating mesh face DynamicList<label> pointToFace(10*nCutCells); // - pointToFace : from generated iso point whether is on face diagonal DynamicList<bool> pointFromDiag(10*nCutCells); // Per cell: number of intersected edges: // - four faces cut so 4 mesh edges + 4 face-diagonal edges // - 4 of the pyramid edges EdgeMap<label> vertsToPoint(12*nCutCells); DynamicList<label> verts(12*nCutCells); // Per cell: 5 pyramids cut (since only one pyramid not cut) DynamicList<label> faceLabels(5*nCutCells); DynamicList<label> cellLabels(5*nCutCells); labelList startTri(mesh_.nCells()+1, Zero); for (label celli = 0; celli < mesh_.nCells(); ++celli) { startTri[celli] = faceLabels.size(); if (cellCutTypes[celli] != NOTCUT) { generateTriPoints ( celli, tet.isA(mesh_, celli), pointToVerts, pointToFace, pointFromDiag, vertsToPoint, verts, faceLabels ); for (label i = startTri[celli]; i < faceLabels.size(); ++i) { cellLabels.append(celli); } } } startTri[mesh_.nCells()] = faceLabels.size(); pointToVerts_.transfer(pointToVerts); meshCells_.transfer(cellLabels); pointToFace_.transfer(pointToFace); tmp<pointField> allPoints ( interpolate ( mesh_.cellCentres(), mesh_.points() ) ); // Assign to MeshedSurface faceList allTris(faceLabels.size()); label verti = 0; for (face& allTri : allTris) { allTri.setSize(3); allTri[0] = verts[verti++]; allTri[1] = verts[verti++]; allTri[2] = verts[verti++]; } surfZoneList allZones(one(), surfZone("allFaces", allTris.size())); MeshStorage::clear(); MeshStorage updated ( std::move(allPoints), std::move(allTris), std::move(allZones) ); MeshStorage::transfer(updated); // Now: // - generated faces and points are assigned to *this // - per point we know: // - pointOnDiag: whether it is on a face-diagonal edge // - pointToFace_: from what pyramid (cell+face) it was produced // (note that the pyramid faces are shared between multiple mesh faces) // - pointToVerts_ : originating mesh vertex or cell centre if (debug) { Pout<< "isoSurfaceTopo : generated " << size() << " faces." << endl; } if (filter != filterType::NONE) { // Triangulate outside (filter edges to cell centres and optionally // face diagonals) DynamicList<label> pointCompactMap(size()); // Back to original point DynamicList<label> compactCellIDs(size()); // Per tri the cell MeshStorage::operator= ( removeInsidePoints ( (filter == filterType::DIAGCELL ? true : false), *this, pointFromDiag, pointToFace_, startTri, pointCompactMap, compactCellIDs ) ); pointToVerts_ = UIndirectList<edge>(pointToVerts_, pointCompactMap)(); pointToFace_ = UIndirectList<label>(pointToFace_, pointCompactMap)(); pointFromDiag = UIndirectList<bool>(pointFromDiag, pointCompactMap)(); meshCells_.transfer(compactCellIDs); if (debug) { Pout<< "isoSurfaceTopo :" << " after removing cell centre and face-diag triangles : " << size() << endl; } if (filter == filterType::DIAGCELL) { // We remove verts on face diagonals. This is in fact just // straightening the edges of the face through the cell. This can // close off 'pockets' of triangles and create open or // multiply-connected triangles // Solved by eroding open-edges // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Mark points on mesh outside. Note that we extend with nCells // so we can easily index with pointToVerts_. bitSet isBoundaryPoint(mesh.nPoints() + mesh.nCells()); for ( label facei = mesh.nInternalFaces(); facei < mesh.nFaces(); ++facei ) { isBoundaryPoint.set(mesh.faces()[facei]); } // Include faces that would be exposed from mesh subset if (!ignoreCells_.empty()) { const labelList& faceOwner = mesh_.faceOwner(); const labelList& faceNeighbour = mesh_.faceNeighbour(); label nMore = 0; for ( label facei = 0; facei < mesh.nInternalFaces(); ++facei ) { int n = 0; if (ignoreCells_.test(faceOwner[facei])) ++n; if (ignoreCells_.test(faceNeighbour[facei])) ++n; // Only one of the cells is being ignored. // That means it is an exposed subMesh face. if (n == 1) { isBoundaryPoint.set(mesh.faces()[facei]); ++nMore; } } } bitSet faceSelection; while (true) { faceSelection.clear(); faceSelection.resize(this->size()); const labelList& mp = meshPoints(); const labelListList& edgeFaces = MeshStorage::edgeFaces(); forAll(edgeFaces, edgei) { const labelList& eFaces = edgeFaces[edgei]; if (eFaces.size() == 1) { // Open edge. Check that vertices do not originate // from a boundary face const edge& e = edges()[edgei]; const edge& verts0 = pointToVerts_[mp[e[0]]]; const edge& verts1 = pointToVerts_[mp[e[1]]]; if ( isBoundaryPoint[verts0[0]] && isBoundaryPoint[verts0[1]] && isBoundaryPoint[verts1[0]] && isBoundaryPoint[verts1[1]] ) { // Open edge on boundary face. Keep } else { // Open edge. Mark for erosion faceSelection.set(eFaces[0]); } } } if (debug) { Pout<< "isoSurfaceTopo :" << " removing " << faceSelection.count() << " faces since on open edges" << endl; } if (returnReduce(faceSelection.none(), andOp<bool>())) { break; } // Flip from remove face -> keep face faceSelection.flip(); labelList pointMap; labelList faceMap; MeshStorage filteredSurf ( MeshStorage::subsetMesh ( faceSelection, pointMap, faceMap ) ); MeshStorage::transfer(filteredSurf); pointToVerts_ = UIndirectList<edge>(pointToVerts_, pointMap)(); pointToFace_ = UIndirectList<label>(pointToFace_, pointMap)(); pointFromDiag = UIndirectList<bool>(pointFromDiag, pointMap)(); meshCells_ = UIndirectList<label>(meshCells_, faceMap)(); } } } } // ************************************************************************* //