1477 lines
38 KiB
C
1477 lines
38 KiB
C
/*---------------------------------------------------------------------------*\
|
|
========= |
|
|
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
|
|
\\ / O peration |
|
|
\\ / A nd | Copyright (C) 2011-2012 OpenFOAM Foundation
|
|
\\/ M anipulation |
|
|
-------------------------------------------------------------------------------
|
|
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/>.
|
|
|
|
Description
|
|
Collapse short edges and combines edges that are in line.
|
|
|
|
- collapse short edges. Length of edges to collapse provided as argument.
|
|
- merge two edges if they are in line. Maximum angle provided as argument.
|
|
- remove unused points.
|
|
|
|
Optionally removes cells. Can remove faces and points but does not check
|
|
for nonsense resulting topology.
|
|
|
|
When collapsing an edge with one point on the boundary it will leave
|
|
the boundary point intact. When both points inside it chooses random. When
|
|
both points on boundary random again.
|
|
|
|
Usage
|
|
- collapseEdges <edge length> <merge angle> [OPTION]
|
|
|
|
\param -allowCellCollapse \n
|
|
Allow collapsing of cells to a single point
|
|
|
|
\param -meshQuality \n
|
|
Only collapse if not exceeding user defined (in \a system/meshQualityDict)
|
|
quality settings
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
#include "argList.H"
|
|
#include "Time.H"
|
|
#include "polyTopoChange.H"
|
|
#include "fvMesh.H"
|
|
#include "mapPolyMesh.H"
|
|
#include "mathematicalConstants.H"
|
|
#include "PackedBoolList.H"
|
|
#include "unitConversion.H"
|
|
#include "globalMeshData.H"
|
|
#include "globalIndex.H"
|
|
#include "PointEdgeWave.H"
|
|
#include "pointEdgeCollapse.H"
|
|
#include "motionSmoother.H"
|
|
#include "removePoints.H"
|
|
|
|
using namespace Foam;
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
|
|
void filterFace
|
|
(
|
|
const label faceI,
|
|
face& f,
|
|
const List<pointEdgeCollapse>& allPointInfo,
|
|
const Map<DynamicList<label> >& collapseStrings
|
|
)
|
|
{
|
|
label newFp = 0;
|
|
|
|
face oldFace = f;
|
|
|
|
forAll(f, fp)
|
|
{
|
|
label pointI = f[fp];
|
|
|
|
label collapsePoint = allPointInfo[pointI].collapseIndex();
|
|
|
|
if (collapseStrings.found(collapsePoint))
|
|
{
|
|
collapsePoint = collapseStrings[collapsePoint][0];
|
|
}
|
|
|
|
if (collapsePoint == -1)
|
|
{
|
|
WarningIn
|
|
(
|
|
"filterFace"
|
|
"(const label, face&, const List<pointEdgeCollapse>&)"
|
|
)
|
|
<< "Point " << pointI << " was not visited by PointEdgeWave"
|
|
<< endl;
|
|
}
|
|
else if (collapsePoint == -2)
|
|
{
|
|
f[newFp++] = pointI;
|
|
}
|
|
else
|
|
{
|
|
if (findIndex(SubList<label>(f, newFp), collapsePoint) == -1)
|
|
{
|
|
f[newFp++] = collapsePoint;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Check for pinched face. Tries to correct
|
|
// - consecutive duplicate vertex. Removes duplicate vertex.
|
|
// - duplicate vertex with one other vertex in between (spike).
|
|
// Both of these should not really occur! and should be checked before
|
|
// collapsing edges.
|
|
|
|
const label size = newFp;
|
|
|
|
newFp = 2;
|
|
|
|
for (label fp = 2; fp < size; fp++)
|
|
{
|
|
label fp1 = fp-1;
|
|
label fp2 = fp-2;
|
|
|
|
label pointI = f[fp];
|
|
|
|
// Search for previous occurrence.
|
|
label index = findIndex(SubList<label>(f, fp), pointI);
|
|
|
|
if (index == fp1)
|
|
{
|
|
WarningIn
|
|
(
|
|
"Foam::edgeCollapser::filterFace(const label faceI, "
|
|
"face& f) const"
|
|
) << "Removing consecutive duplicate vertex in face "
|
|
<< f << endl;
|
|
// Don't store current pointI
|
|
}
|
|
else if (index == fp2)
|
|
{
|
|
WarningIn
|
|
(
|
|
"Foam::edgeCollapser::filterFace(const label faceI, "
|
|
"face& f) const"
|
|
) << "Removing non-consecutive duplicate vertex in face "
|
|
<< f << endl;
|
|
// Don't store current pointI and remove previous
|
|
newFp--;
|
|
}
|
|
else if (index != -1)
|
|
{
|
|
WarningIn
|
|
(
|
|
"Foam::edgeCollapser::filterFace(const label faceI, "
|
|
"face& f) const"
|
|
) << "Pinched face " << f << endl;
|
|
f[newFp++] = pointI;
|
|
}
|
|
else
|
|
{
|
|
f[newFp++] = pointI;
|
|
}
|
|
}
|
|
|
|
f.setSize(newFp);
|
|
}
|
|
|
|
|
|
bool setRefinement
|
|
(
|
|
const polyMesh& mesh,
|
|
polyTopoChange& meshMod,
|
|
const List<pointEdgeCollapse>& allPointInfo
|
|
)
|
|
{
|
|
const cellList& cells = mesh.cells();
|
|
const labelList& faceOwner = mesh.faceOwner();
|
|
const labelList& faceNeighbour = mesh.faceNeighbour();
|
|
const labelListList& pointFaces = mesh.pointFaces();
|
|
const pointZoneMesh& pointZones = mesh.pointZones();
|
|
|
|
globalIndex globalStrings(mesh.nPoints());
|
|
|
|
boolList removedPoints(mesh.nPoints(), false);
|
|
|
|
// Create strings of edges.
|
|
// Map from collapseIndex(=global master point) to set of points
|
|
Map<DynamicList<label> > collapseStrings;
|
|
{
|
|
// 1. Count elements per collapseIndex
|
|
Map<label> nPerIndex(mesh.nPoints()/10);
|
|
forAll(allPointInfo, pointI)
|
|
{
|
|
const label collapseIndex = allPointInfo[pointI].collapseIndex();
|
|
|
|
if (collapseIndex != -1 && collapseIndex != -2)
|
|
{
|
|
Map<label>::iterator fnd = nPerIndex.find(collapseIndex);
|
|
if (fnd != nPerIndex.end())
|
|
{
|
|
fnd()++;
|
|
}
|
|
else
|
|
{
|
|
nPerIndex.insert(collapseIndex, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Size
|
|
collapseStrings.resize(2*nPerIndex.size());
|
|
forAllConstIter(Map<label>, nPerIndex, iter)
|
|
{
|
|
collapseStrings.insert(iter.key(), DynamicList<label>(iter()));
|
|
}
|
|
|
|
// 3. Fill
|
|
forAll(allPointInfo, pointI)
|
|
{
|
|
const label collapseIndex = allPointInfo[pointI].collapseIndex();
|
|
|
|
if (collapseIndex != -1 && collapseIndex != -2)
|
|
{
|
|
collapseStrings[collapseIndex].append(pointI);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool meshChanged = false;
|
|
|
|
// Current faces (is also collapseStatus: f.size() < 3)
|
|
faceList newFaces(mesh.faces());
|
|
|
|
// Current cellCollapse status
|
|
boolList cellRemoved(mesh.nCells(), false);
|
|
|
|
label nUnvisited = 0;
|
|
label nUncollapsed = 0;
|
|
label nCollapsed = 0;
|
|
|
|
forAll(allPointInfo, pI)
|
|
{
|
|
const pointEdgeCollapse& pec = allPointInfo[pI];
|
|
|
|
if (pec.collapseIndex() == -1)
|
|
{
|
|
nUnvisited++;
|
|
}
|
|
else if (pec.collapseIndex() == -2)
|
|
{
|
|
nUncollapsed++;
|
|
}
|
|
else if (pec.collapseIndex() >= 0)
|
|
{
|
|
nCollapsed++;
|
|
}
|
|
}
|
|
|
|
label nPoints = allPointInfo.size();
|
|
|
|
reduce(nPoints, sumOp<label>());
|
|
reduce(nUnvisited, sumOp<label>());
|
|
reduce(nUncollapsed, sumOp<label>());
|
|
reduce(nCollapsed, sumOp<label>());
|
|
|
|
Info<< incrIndent;
|
|
Info<< indent << "Number of points : " << nPoints << nl
|
|
<< indent << "Not visited : " << nUnvisited << nl
|
|
<< indent << "Not collapsed : " << nUncollapsed << nl
|
|
<< indent << "Collapsed : " << nCollapsed << nl
|
|
<< endl;
|
|
Info<< decrIndent;
|
|
|
|
do
|
|
{
|
|
// Update face collapse from edge collapses
|
|
forAll(newFaces, faceI)
|
|
{
|
|
filterFace(faceI, newFaces[faceI], allPointInfo, collapseStrings);
|
|
}
|
|
|
|
// Check if faces to be collapsed cause cells to become collapsed.
|
|
label nCellCollapsed = 0;
|
|
|
|
forAll(cells, cellI)
|
|
{
|
|
if (!cellRemoved[cellI])
|
|
{
|
|
const cell& cFaces = cells[cellI];
|
|
|
|
label nFaces = cFaces.size();
|
|
|
|
forAll(cFaces, i)
|
|
{
|
|
label faceI = cFaces[i];
|
|
|
|
if (newFaces[faceI].size() < 3)
|
|
{
|
|
--nFaces;
|
|
|
|
if (nFaces < 4)
|
|
{
|
|
Pout<< "Cell:" << cellI
|
|
<< " uses faces:" << cFaces
|
|
<< " of which too many are marked for removal:"
|
|
<< endl
|
|
<< " ";
|
|
forAll(cFaces, j)
|
|
{
|
|
if (newFaces[cFaces[j]].size() < 3)
|
|
{
|
|
Pout<< ' '<< cFaces[j];
|
|
}
|
|
}
|
|
Pout<< endl;
|
|
|
|
cellRemoved[cellI] = true;
|
|
|
|
// Collapse all edges of cell to nothing
|
|
//collapseEdges(cellEdges[cellI]);
|
|
|
|
nCellCollapsed++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nCellCollapsed == 0)
|
|
{
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
|
|
// Keep track of faces that have been done already.
|
|
boolList doneFace(mesh.nFaces(), false);
|
|
|
|
{
|
|
// Mark points used.
|
|
boolList usedPoint(mesh.nPoints(), false);
|
|
|
|
forAll(cellRemoved, cellI)
|
|
{
|
|
if (cellRemoved[cellI])
|
|
{
|
|
meshMod.removeCell(cellI, -1);
|
|
}
|
|
}
|
|
|
|
// Remove faces
|
|
forAll(newFaces, faceI)
|
|
{
|
|
const face& f = newFaces[faceI];
|
|
|
|
if (f.size() < 3)
|
|
{
|
|
meshMod.removeFace(faceI, -1);
|
|
meshChanged = true;
|
|
|
|
// Mark face as been done.
|
|
doneFace[faceI] = true;
|
|
}
|
|
else
|
|
{
|
|
// Kept face. Mark vertices
|
|
forAll(f, fp)
|
|
{
|
|
usedPoint[f[fp]] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove unused vertices that have not been marked for removal already
|
|
forAll(usedPoint, pointI)
|
|
{
|
|
if (!usedPoint[pointI])
|
|
{
|
|
removedPoints[pointI] = true;
|
|
meshMod.removePoint(pointI, -1);
|
|
meshChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Modify the point location of the remaining points
|
|
forAll(allPointInfo, pointI)
|
|
{
|
|
const label collapseIndex = allPointInfo[pointI].collapseIndex();
|
|
const point& collapsePoint = allPointInfo[pointI].collapsePoint();
|
|
|
|
if
|
|
(
|
|
removedPoints[pointI] == false
|
|
&& collapseIndex != -1
|
|
&& collapseIndex != -2
|
|
)
|
|
{
|
|
meshMod.modifyPoint
|
|
(
|
|
pointI,
|
|
collapsePoint,
|
|
pointZones.whichZone(pointI),
|
|
false
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
const polyBoundaryMesh& boundaryMesh = mesh.boundaryMesh();
|
|
const faceZoneMesh& faceZones = mesh.faceZones();
|
|
|
|
// Renumber faces that use points
|
|
forAll(allPointInfo, pointI)
|
|
{
|
|
if (removedPoints[pointI] == true)
|
|
{
|
|
const labelList& changedFaces = pointFaces[pointI];
|
|
|
|
forAll(changedFaces, changedFaceI)
|
|
{
|
|
label faceI = changedFaces[changedFaceI];
|
|
|
|
if (!doneFace[faceI])
|
|
{
|
|
doneFace[faceI] = true;
|
|
|
|
// Get current zone info
|
|
label zoneID = faceZones.whichZone(faceI);
|
|
|
|
bool zoneFlip = false;
|
|
|
|
if (zoneID >= 0)
|
|
{
|
|
const faceZone& fZone = faceZones[zoneID];
|
|
|
|
zoneFlip = fZone.flipMap()[fZone.whichFace(faceI)];
|
|
}
|
|
|
|
// Get current connectivity
|
|
label own = faceOwner[faceI];
|
|
label nei = -1;
|
|
label patchID = -1;
|
|
|
|
if (mesh.isInternalFace(faceI))
|
|
{
|
|
nei = faceNeighbour[faceI];
|
|
}
|
|
else
|
|
{
|
|
patchID = boundaryMesh.whichPatch(faceI);
|
|
}
|
|
|
|
meshMod.modifyFace
|
|
(
|
|
newFaces[faceI], // face
|
|
faceI, // faceI to change
|
|
own, // owner
|
|
nei, // neighbour
|
|
false, // flipFaceFlux
|
|
patchID, // patch
|
|
zoneID,
|
|
zoneFlip
|
|
);
|
|
meshChanged = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return meshChanged;
|
|
}
|
|
|
|
|
|
// Get faceEdges in order of face points, i.e. faceEdges[0] is between
|
|
// f[0] and f[1]
|
|
labelList getSortedEdges
|
|
(
|
|
const edgeList& edges,
|
|
const labelList& f,
|
|
const labelList& edgeLabels
|
|
)
|
|
{
|
|
labelList faceEdges(edgeLabels.size(), -1);
|
|
|
|
// Find starting pos in f for every edgeLabels
|
|
forAll(edgeLabels, i)
|
|
{
|
|
label edgeI = edgeLabels[i];
|
|
|
|
const edge& e = edges[edgeI];
|
|
|
|
label fp = findIndex(f, e[0]);
|
|
label fp1 = f.fcIndex(fp);
|
|
|
|
if (f[fp1] == e[1])
|
|
{
|
|
// EdgeI between fp -> fp1
|
|
faceEdges[fp] = edgeI;
|
|
}
|
|
else
|
|
{
|
|
// EdgeI between fp-1 -> fp
|
|
faceEdges[f.rcIndex(fp)] = edgeI;
|
|
}
|
|
}
|
|
|
|
return faceEdges;
|
|
}
|
|
|
|
|
|
// Return master point edge needs to be collapsed to (or -1)
|
|
label edgeMaster
|
|
(
|
|
const labelList& boundaryPoint,
|
|
const bool flipEdge,
|
|
const edge& e
|
|
)
|
|
{
|
|
label masterPoint = -1;
|
|
|
|
label e0 = e[0];
|
|
label e1 = e[1];
|
|
|
|
if (flipEdge)
|
|
{
|
|
e0 = e[1];
|
|
e1 = e[0];
|
|
}
|
|
|
|
// Check if one of the points is on a processor
|
|
// if
|
|
// (
|
|
// boundaryPoint[e0] > 0
|
|
// && boundaryPoint[e1] > 0
|
|
// )
|
|
// {
|
|
// if (boundaryPoint[e0] != boundaryPoint[e1])
|
|
// {
|
|
// return -1;
|
|
// }
|
|
// }
|
|
//
|
|
// if (boundaryPoint[e0] > 0)
|
|
// {
|
|
// return e0;
|
|
// }
|
|
// else if (boundaryPoint[e1] > 0)
|
|
// {
|
|
// return e1;
|
|
// }
|
|
|
|
// Collapse edge to boundary point.
|
|
if (boundaryPoint[e0] == 0)
|
|
{
|
|
if (boundaryPoint[e1] == 0)
|
|
{
|
|
// Both points on boundary. Choose one to collapse to.
|
|
// Note: should look at feature edges/points!
|
|
masterPoint = e0;
|
|
}
|
|
else
|
|
{
|
|
masterPoint = e0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (boundaryPoint[e1] == 0)
|
|
{
|
|
masterPoint = e1;
|
|
}
|
|
else
|
|
{
|
|
// None on boundary. Choose arbitrary.
|
|
// Note: should look at geometry?
|
|
masterPoint = e0;
|
|
}
|
|
}
|
|
|
|
return masterPoint;
|
|
}
|
|
|
|
|
|
// Create consistent set of collapses.
|
|
// collapseEdge : per edge:
|
|
// -1 : do not collapse
|
|
// 0 : collapse to start
|
|
// 1 : collapse to end
|
|
// Note: collapseEdge has to be parallel consistent (in orientation)
|
|
label syncCollapse
|
|
(
|
|
const polyMesh& mesh,
|
|
const globalIndex& globalStrings,
|
|
const labelList& collapseEdge,
|
|
List<pointEdgeCollapse>& allPointInfo
|
|
)
|
|
{
|
|
const edgeList& edges = mesh.edges();
|
|
const pointField& points = mesh.points();
|
|
|
|
label nCollapsed = 0;
|
|
|
|
DynamicList<label> initPoints(mesh.nPoints());
|
|
DynamicList<pointEdgeCollapse> initPointInfo(mesh.nPoints());
|
|
allPointInfo.resize(mesh.nPoints());
|
|
|
|
// Initialise edges to no collapse
|
|
List<pointEdgeCollapse> allEdgeInfo
|
|
(
|
|
mesh.nEdges(),
|
|
pointEdgeCollapse(vector::zero, -1)
|
|
);
|
|
|
|
// Mark selected edges for collapse
|
|
forAll(edges, edgeI)
|
|
{
|
|
const edge& e = edges[edgeI];
|
|
|
|
if (collapseEdge[edgeI] != -1)
|
|
{
|
|
label masterPointI = e[collapseEdge[edgeI]];
|
|
|
|
const pointEdgeCollapse pec
|
|
(
|
|
points[masterPointI],
|
|
globalStrings.toGlobal(masterPointI)
|
|
);
|
|
|
|
// Mark as collapsable but with nonsense master so it gets
|
|
// overwritten and starts an update wave
|
|
allEdgeInfo[edgeI] = pointEdgeCollapse
|
|
(
|
|
points[masterPointI],
|
|
labelMax
|
|
);
|
|
|
|
initPointInfo.append(pec);
|
|
initPoints.append(e.start());
|
|
|
|
initPointInfo.append(pec);
|
|
initPoints.append(e.end());
|
|
|
|
nCollapsed++;
|
|
}
|
|
}
|
|
|
|
PointEdgeWave<pointEdgeCollapse> collapsePropagator
|
|
(
|
|
mesh,
|
|
initPoints,
|
|
initPointInfo,
|
|
allPointInfo,
|
|
allEdgeInfo,
|
|
mesh.globalData().nTotalPoints() // Maximum number of iterations
|
|
);
|
|
|
|
return nCollapsed;
|
|
}
|
|
|
|
|
|
void syncCollapseEdge(const polyMesh& mesh, labelList& collapseEdge)
|
|
{
|
|
// Check whether edge point order is reversed from mesh to coupledPatch
|
|
const globalMeshData& globalData = mesh.globalData();
|
|
const mapDistribute& map = globalData.globalEdgeSlavesMap();
|
|
const labelList& coupledMeshEdges = globalData.coupledPatchMeshEdges();
|
|
const indirectPrimitivePatch& coupledPatch = globalData.coupledPatch();
|
|
const PackedBoolList& cppOrientation = globalData.globalEdgeOrientation();
|
|
PackedBoolList meshToPatchSameOrientation(coupledMeshEdges.size(), true);
|
|
|
|
forAll(coupledMeshEdges, eI)
|
|
{
|
|
const label meshEdgeIndex = coupledMeshEdges[eI];
|
|
|
|
if (collapseEdge[meshEdgeIndex] != -1)
|
|
{
|
|
const edge& meshEdge = mesh.edges()[meshEdgeIndex];
|
|
const edge& coupledPatchEdge = coupledPatch.edges()[eI];
|
|
|
|
if
|
|
(
|
|
meshEdge[0] == coupledPatch.meshPoints()[coupledPatchEdge[1]]
|
|
&& meshEdge[1] == coupledPatch.meshPoints()[coupledPatchEdge[0]]
|
|
)
|
|
{
|
|
meshToPatchSameOrientation[eI] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
labelList cppEdgeData(map.constructSize());
|
|
|
|
forAll(coupledMeshEdges, eI)
|
|
{
|
|
const label meshEdgeIndex = coupledMeshEdges[eI];
|
|
|
|
cppEdgeData[eI] = collapseEdge[meshEdgeIndex];
|
|
|
|
if
|
|
(
|
|
(collapseEdge[meshEdgeIndex] != -1)
|
|
&& (meshToPatchSameOrientation[eI] != cppOrientation[eI])
|
|
)
|
|
{
|
|
cppEdgeData[eI] = 1-cppEdgeData[eI];
|
|
}
|
|
}
|
|
|
|
|
|
// Synchronise cppEdgeData
|
|
// Use minEqOp reduction, so that edge will only be collapsed on processor
|
|
// boundary if both processors agree to collapse it
|
|
globalData.syncData
|
|
(
|
|
cppEdgeData,
|
|
globalData.globalEdgeSlaves(),
|
|
globalData.globalEdgeTransformedSlaves(),
|
|
map,
|
|
minEqOp<label>()
|
|
);
|
|
|
|
|
|
forAll(coupledMeshEdges, eI)
|
|
{
|
|
const label meshEdgeIndex = coupledMeshEdges[eI];
|
|
|
|
collapseEdge[meshEdgeIndex] = cppEdgeData[eI];
|
|
|
|
if
|
|
(
|
|
(cppEdgeData[eI] != -1)
|
|
&& (meshToPatchSameOrientation[eI] != cppOrientation[eI])
|
|
)
|
|
{
|
|
collapseEdge[meshEdgeIndex] = 1-collapseEdge[meshEdgeIndex];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Mark (in collapseEdge) any edges to collapse
|
|
label collapseSmallEdges
|
|
(
|
|
const polyMesh& mesh,
|
|
const labelList& boundaryPoint,
|
|
const scalarField& minEdgeLen,
|
|
labelList& collapseEdge
|
|
)
|
|
{
|
|
// Work out which edges to collapse
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
label nCollapsed = 0;
|
|
|
|
forAll(mesh.edges(), edgeI)
|
|
{
|
|
if (collapseEdge[edgeI] == -1)
|
|
{
|
|
const edge& e = mesh.edges()[edgeI];
|
|
|
|
if (e.mag(mesh.points()) < minEdgeLen[edgeI])
|
|
{
|
|
label masterPointI = edgeMaster(boundaryPoint, false, e);
|
|
if (masterPointI == e[0])
|
|
{
|
|
collapseEdge[edgeI] = 0;
|
|
}
|
|
else
|
|
{
|
|
collapseEdge[edgeI] = 1;
|
|
}
|
|
nCollapsed++;
|
|
}
|
|
}
|
|
}
|
|
return nCollapsed;
|
|
}
|
|
|
|
|
|
// Mark (in collapseEdge) any edges to merge
|
|
label mergeEdges
|
|
(
|
|
const polyMesh& mesh,
|
|
const scalar maxCos,
|
|
const labelList& boundaryPoint,
|
|
const scalarField& minEdgeLen,
|
|
labelList& collapseEdge
|
|
)
|
|
{
|
|
const edgeList& edges = mesh.edges();
|
|
const labelListList& pointEdges = mesh.pointEdges();
|
|
|
|
// Point removal engine
|
|
removePoints pointRemover(mesh, false);
|
|
|
|
// Find out points that can be deleted
|
|
boolList pointCanBeDeleted;
|
|
label nTotRemove = pointRemover.countPointUsage(maxCos, pointCanBeDeleted);
|
|
|
|
|
|
// Rework point-to-remove into edge-to-collapse.
|
|
|
|
label nCollapsed = 0;
|
|
|
|
if (nTotRemove > 0)
|
|
{
|
|
forAll(pointEdges, pointI)
|
|
{
|
|
if (pointCanBeDeleted[pointI])
|
|
{
|
|
const labelList& pEdges = pointEdges[pointI];
|
|
|
|
if (pEdges.size() == 2)
|
|
{
|
|
// Always the case?
|
|
|
|
label e0 = pEdges[0];
|
|
label e1 = pEdges[1];
|
|
|
|
if
|
|
(
|
|
collapseEdge[e0] == -1
|
|
&& minEdgeLen[e0] >= 0
|
|
&& collapseEdge[e1] == -1
|
|
&& minEdgeLen[e1] >= 0
|
|
)
|
|
{
|
|
// Get the two vertices on both sides of the point
|
|
label leftV = edges[e0].otherVertex(pointI);
|
|
label rightV = edges[e1].otherVertex(pointI);
|
|
|
|
// Can collapse pointI onto either leftV or rightV.
|
|
// Preferentially choose an internal point to hopefully
|
|
// give less distortion
|
|
|
|
if (boundaryPoint[leftV] == -1)
|
|
{
|
|
collapseEdge[e0] = findIndex(edges[e0], leftV);
|
|
}
|
|
else
|
|
{
|
|
collapseEdge[e1] = findIndex(edges[e1], rightV);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nCollapsed;
|
|
}
|
|
|
|
|
|
// Make consistent set of collapses that does not collapse any cells
|
|
label consistentCollapse
|
|
(
|
|
const bool allowCellCollapse,
|
|
const polyMesh& mesh,
|
|
const globalIndex& globalStrings,
|
|
labelList& collapseEdge,
|
|
List<pointEdgeCollapse>& allPointInfo
|
|
)
|
|
{
|
|
// Make sure we don't collapse cells
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
while (true)
|
|
{
|
|
// Sync collapseEdge
|
|
syncCollapseEdge(mesh, collapseEdge);
|
|
|
|
|
|
// Get collapsed faces
|
|
|
|
label nAdditionalCollapsed = 0;
|
|
|
|
PackedBoolList isCollapsedFace(mesh.nFaces());
|
|
forAll(mesh.faceEdges(), faceI)
|
|
{
|
|
const labelList& fEdges = mesh.faceEdges()[faceI];
|
|
|
|
// Count number of remaining edges
|
|
label nEdges = fEdges.size();
|
|
forAll(fEdges, fEdgeI)
|
|
{
|
|
label edgeI = fEdges[fEdgeI];
|
|
if (collapseEdge[edgeI] != -1)
|
|
{
|
|
nEdges--;
|
|
}
|
|
}
|
|
|
|
if (nEdges < 3)
|
|
{
|
|
// Face is collapsed.
|
|
isCollapsedFace[faceI] = 1;
|
|
|
|
if (nEdges == 1)
|
|
{
|
|
// Cannot collapse face down to single edge.
|
|
|
|
//- Option 1: collapse remaining edge as well. However
|
|
// if this edge is on the coupled processor patch this
|
|
// logic clashes with that of syncCollapseEdge
|
|
// (do not collapse if any not collapse)
|
|
//forAll(fEdges, fEdgeI)
|
|
//{
|
|
// label edgeI = fEdges[fEdgeI];
|
|
// if (collapseEdge[edgeI] == -1)
|
|
// {
|
|
// collapseEdge[edgeI] = 0;
|
|
// nAdditionalCollapsed++;
|
|
// }
|
|
//}
|
|
|
|
//- Option 2: uncollapse this face.
|
|
forAll(fEdges, fEdgeI)
|
|
{
|
|
label edgeI = fEdges[fEdgeI];
|
|
if (collapseEdge[edgeI] != -1)
|
|
{
|
|
collapseEdge[edgeI] = -1;
|
|
nAdditionalCollapsed++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Pout<< "nAdditionalCollapsed : " << nAdditionalCollapsed << endl;
|
|
|
|
|
|
label nUncollapsed = 0;
|
|
|
|
if (!allowCellCollapse)
|
|
{
|
|
// Check collapsed cells
|
|
|
|
forAll(mesh.cells(), cellI)
|
|
{
|
|
const cell& cFaces = mesh.cells()[cellI];
|
|
label nFaces = cFaces.size();
|
|
forAll(cFaces, i)
|
|
{
|
|
label faceI = cFaces[i];
|
|
if (isCollapsedFace[faceI])
|
|
{
|
|
nFaces--;
|
|
if (nFaces < 4)
|
|
{
|
|
// Unmark this face for collapse
|
|
const labelList& fEdges = mesh.faceEdges()[faceI];
|
|
|
|
forAll(fEdges, fEdgeI)
|
|
{
|
|
label edgeI = fEdges[fEdgeI];
|
|
if (collapseEdge[edgeI] != -1)
|
|
{
|
|
collapseEdge[edgeI] = -1;
|
|
nUncollapsed++;
|
|
}
|
|
}
|
|
|
|
// Uncollapsed this face.
|
|
isCollapsedFace[faceI] = 0;
|
|
nFaces++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//Pout<< "** disallowing cells : nUncollapsed : "
|
|
// << nUncollapsed << endl;
|
|
}
|
|
|
|
|
|
if
|
|
(
|
|
returnReduce(nUncollapsed+nAdditionalCollapsed, sumOp<label>())
|
|
== 0
|
|
)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Create consistent set of collapses
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Note: requires collapseEdge to be synchronised. (above loop makes sure
|
|
// of that)
|
|
|
|
return syncCollapse(mesh, globalStrings, collapseEdge, allPointInfo);
|
|
}
|
|
|
|
|
|
// Check mesh and mark points on faces in error
|
|
// Returns boolList with points in error set
|
|
PackedBoolList checkMeshQuality(const polyMesh& mesh)
|
|
{
|
|
//mesh.checkMesh(true);
|
|
|
|
IOdictionary meshQualityDict
|
|
(
|
|
IOobject
|
|
(
|
|
"meshQualityDict",
|
|
mesh.time().system(),
|
|
mesh.time(),
|
|
IOobject::MUST_READ,
|
|
IOobject::NO_WRITE
|
|
)
|
|
);
|
|
|
|
labelHashSet badFaces(mesh.nFaces()/100);
|
|
DynamicList<label> checkFaces(mesh.nFaces());
|
|
|
|
const vectorField& fAreas = mesh.faceAreas();
|
|
|
|
scalar faceAreaLimit = SMALL;
|
|
|
|
forAll(fAreas, fI)
|
|
{
|
|
if (mag(fAreas[fI]) > faceAreaLimit)
|
|
{
|
|
checkFaces.append(fI);
|
|
}
|
|
}
|
|
|
|
motionSmoother::checkMesh
|
|
(
|
|
false,
|
|
mesh,
|
|
meshQualityDict,
|
|
checkFaces,
|
|
badFaces
|
|
);
|
|
|
|
label nBadFaces = badFaces.size();
|
|
reduce(nBadFaces, sumOp<label>());
|
|
|
|
Info<< nl << "Number of bad faces : " << nBadFaces << endl;
|
|
|
|
PackedBoolList isErrorPoint(mesh.nPoints());
|
|
forAllConstIter(labelHashSet, badFaces, iter)
|
|
{
|
|
const face& f = mesh.faces()[iter.key()];
|
|
|
|
forAll(f, pI)
|
|
{
|
|
isErrorPoint[f[pI]] = 1;
|
|
}
|
|
}
|
|
|
|
syncTools::syncPointList
|
|
(
|
|
mesh,
|
|
isErrorPoint,
|
|
orEqOp<unsigned int>(),
|
|
0
|
|
);
|
|
|
|
return isErrorPoint;
|
|
}
|
|
|
|
|
|
// Check mesh with collapses (newMesh), updates minEdgeLen, nFrozenEdges
|
|
void checkMeshAndFreezeEdges
|
|
(
|
|
const polyMesh& newMesh,
|
|
const labelList& oldToNewMesh,
|
|
const polyMesh& oldMesh,
|
|
scalarField& minEdgeLen,
|
|
label& nFrozenEdges
|
|
)
|
|
{
|
|
PackedBoolList isErrorPoint = checkMeshQuality(newMesh);
|
|
|
|
forAll(oldMesh.edges(), edgeI)
|
|
{
|
|
const edge& e = oldMesh.edges()[edgeI];
|
|
label newStart = oldToNewMesh[e[0]];
|
|
label newEnd = oldToNewMesh[e[1]];
|
|
|
|
if
|
|
(
|
|
(newStart >= 0 && isErrorPoint[newStart])
|
|
|| (newEnd >= 0 && isErrorPoint[newEnd])
|
|
)
|
|
{
|
|
// Gradual decrease? For now just hard disable
|
|
if (minEdgeLen[edgeI] > -GREAT/2)
|
|
{
|
|
minEdgeLen[edgeI] = -GREAT;
|
|
nFrozenEdges++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Mark boundary points
|
|
// boundaryPoint:
|
|
// + -1 : point not on boundary
|
|
// + 0 : point on a real boundary
|
|
// + >0 : point on a processor patch with that ID
|
|
labelList findBoundaryPoints(const polyMesh& mesh)
|
|
{
|
|
const faceList& faces = mesh.faces();
|
|
const polyBoundaryMesh& bMesh = mesh.boundaryMesh();
|
|
|
|
|
|
labelList boundaryPoint(mesh.nPoints(), -1);
|
|
|
|
// Get all points on a boundary
|
|
label nIntFaces = mesh.nInternalFaces();
|
|
for (label faceI = nIntFaces; faceI < mesh.nFaces(); faceI++)
|
|
{
|
|
const face& f = faces[faceI];
|
|
|
|
forAll(f, fp)
|
|
{
|
|
boundaryPoint[f[fp]] = 0;
|
|
}
|
|
}
|
|
|
|
// Get all processor boundary points and the processor patch label
|
|
// that they are on.
|
|
forAll(bMesh, patchI)
|
|
{
|
|
const polyPatch& patch = bMesh[patchI];
|
|
|
|
if (isA<coupledPolyPatch>(patch))
|
|
{
|
|
if (patchI == 0)
|
|
{
|
|
// We mark 'normal' boundary points with 0 so make sure this
|
|
// coupled patch is not 0.
|
|
FatalErrorIn("findBoundaryPoints(const polyMesh&)")
|
|
<< "Your patches should have non-coupled ones before any"
|
|
<< " coupled ones. Current patches " << bMesh.names()
|
|
<< exit(FatalError);
|
|
}
|
|
|
|
forAll(patch, fI)
|
|
{
|
|
const face& f = patch[fI];
|
|
|
|
forAll(f, fp)
|
|
{
|
|
boundaryPoint[f[fp]] = patchI;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return boundaryPoint;
|
|
}
|
|
|
|
|
|
// Main program:
|
|
int main(int argc, char *argv[])
|
|
{
|
|
argList::addNote
|
|
(
|
|
"Merges small and in-line edges.\n"
|
|
"Collapses faces and optionally cells to a point."
|
|
);
|
|
|
|
# include "addOverwriteOption.H"
|
|
|
|
argList::validArgs.append("edge length [m]");
|
|
argList::validArgs.append("merge angle (degrees)");
|
|
argList::addBoolOption
|
|
(
|
|
"allowCellCollapse",
|
|
"Allow collapsing of cells to a single point"
|
|
);
|
|
argList::addBoolOption
|
|
(
|
|
"meshQuality",
|
|
"Only collapse if not exceeding given meshQualityDict limits"
|
|
);
|
|
|
|
|
|
|
|
# include "setRootCase.H"
|
|
# include "createTime.H"
|
|
runTime.functionObjects().off();
|
|
# include "createPolyMesh.H"
|
|
const word oldInstance = mesh.pointsInstance();
|
|
|
|
scalar minLen = args.argRead<scalar>(1);
|
|
const scalar angle = args.argRead<scalar>(2);
|
|
|
|
const bool allowCellCollapse = args.optionFound("allowCellCollapse");
|
|
const bool overwrite = args.optionFound("overwrite");
|
|
const bool checkQuality = args.optionFound("meshQuality");
|
|
|
|
scalar maxCos = Foam::cos(degToRad(angle));
|
|
|
|
Info<< "Merging:" << nl
|
|
<< " edges with length less than " << minLen << " meters" << nl
|
|
<< " edges split by a point with edges in line to within " << angle
|
|
<< " degrees" << nl
|
|
<< endl;
|
|
|
|
if (allowCellCollapse)
|
|
{
|
|
Info<< "Allowing collapse of cells down to a point." << nl
|
|
<< endl;
|
|
}
|
|
else
|
|
{
|
|
Info<< "Disallowing collapse of cells down to a point." << nl
|
|
<< endl;
|
|
}
|
|
|
|
|
|
if (checkQuality)
|
|
{
|
|
Info<< "Selectively disabling wanted collapses until resulting quality"
|
|
<< " satisfies constraints in system/meshQualityDict" << nl
|
|
<< endl;
|
|
}
|
|
|
|
|
|
|
|
// To mark master of collapes
|
|
globalIndex globalStrings(mesh.nPoints());
|
|
|
|
|
|
// Local collapse length. Any edge below this length gets (attempted)
|
|
// collapsed. Use either aa gradually decreasing value
|
|
// (so from minLen to e.g. 0.5*minLen) or a hard limit (GREAT)
|
|
scalarField minEdgeLen(mesh.nEdges(), minLen);
|
|
label nFrozenEdges = 0;
|
|
|
|
|
|
|
|
// Initial mesh check
|
|
// ~~~~~~~~~~~~~~~~~~
|
|
// Do not allow collapses in regions of error.
|
|
// Updates minEdgeLen, nFrozenEdges
|
|
if (checkQuality)
|
|
{
|
|
checkMeshAndFreezeEdges
|
|
(
|
|
mesh,
|
|
identity(mesh.nPoints()),
|
|
mesh,
|
|
minEdgeLen,
|
|
nFrozenEdges
|
|
);
|
|
Info<< "Initial frozen edges "
|
|
<< returnReduce(nFrozenEdges, sumOp<label>())
|
|
<< " out of " << returnReduce(mesh.nEdges(), sumOp<label>())
|
|
<< endl;
|
|
}
|
|
|
|
|
|
// Mark points on boundary
|
|
const labelList boundaryPoint = findBoundaryPoints(mesh);
|
|
|
|
// Copy of current set of topology changes. Used to generate final mesh.
|
|
polyTopoChange savedMeshMod(mesh.boundaryMesh().size());
|
|
|
|
// Keep track of whether mesh has changed at all
|
|
bool meshChanged = false;
|
|
|
|
|
|
|
|
// Main loop
|
|
// ~~~~~~~~~
|
|
// It tries and do some collapses, checks the resulting mesh and
|
|
// 'freezes' some edges (by marking in minEdgeLen) and tries again.
|
|
// This will iterate ultimately to the situation where every edge is
|
|
// frozen and nothing gets collapsed.
|
|
|
|
do
|
|
{
|
|
// Per edge collapse status:
|
|
// -1 : not collapsed
|
|
// 0 : collapse to start
|
|
// 1 : collapse to end
|
|
labelList collapseEdge(mesh.nEdges(), -1);
|
|
|
|
|
|
// Work out which edges to collapse
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// This is by looking at minEdgeLen (to avoid frozen edges)
|
|
// and marking in collapseEdge.
|
|
|
|
// Small edges
|
|
label nSmallCollapsed = collapseSmallEdges
|
|
(
|
|
mesh,
|
|
boundaryPoint,
|
|
minEdgeLen,
|
|
collapseEdge
|
|
);
|
|
reduce(nSmallCollapsed, sumOp<label>());
|
|
|
|
Info<< indent << "Collapsing " << nSmallCollapsed
|
|
<< " small edges" << endl;
|
|
|
|
|
|
// Inline edges
|
|
label nMerged = mergeEdges
|
|
(
|
|
mesh,
|
|
maxCos,
|
|
boundaryPoint,
|
|
minEdgeLen,
|
|
collapseEdge
|
|
);
|
|
|
|
reduce(nMerged, sumOp<label>());
|
|
Info<< indent << "Collapsing " << nMerged << " in line edges"
|
|
<< endl;
|
|
|
|
|
|
// Merge edge collapses into consistent collapse-network. Make sure
|
|
// no cells get collapsed.
|
|
List<pointEdgeCollapse> allPointInfo;
|
|
label nLocalCollapse = consistentCollapse
|
|
(
|
|
allowCellCollapse,
|
|
mesh,
|
|
globalStrings,
|
|
collapseEdge,
|
|
allPointInfo
|
|
);
|
|
|
|
reduce(nLocalCollapse, sumOp<label>());
|
|
Info<< "nLocalCollapse = " << nLocalCollapse << endl;
|
|
|
|
if (nLocalCollapse == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
// There are collapses so mesh will get changed
|
|
meshChanged = true;
|
|
|
|
|
|
// Apply collapses to current mesh
|
|
polyTopoChange meshMod(mesh);
|
|
|
|
// Insert mesh refinement into polyTopoChange.
|
|
setRefinement(mesh, meshMod, allPointInfo);
|
|
|
|
// Do all changes
|
|
Info<< indent << "Applying changes to the mesh" << nl
|
|
//<< decrIndent
|
|
<< endl;
|
|
|
|
savedMeshMod = meshMod;
|
|
|
|
autoPtr<fvMesh> newMeshPtr;
|
|
autoPtr<mapPolyMesh> mapPtr = meshMod.makeMesh
|
|
(
|
|
newMeshPtr,
|
|
IOobject
|
|
(
|
|
mesh.name(),
|
|
mesh.instance(),
|
|
mesh.time(), // register with runTime
|
|
IOobject::NO_READ,
|
|
IOobject::NO_WRITE,
|
|
false
|
|
),
|
|
mesh,
|
|
true // parallel sync
|
|
);
|
|
|
|
fvMesh& newMesh = newMeshPtr();
|
|
const mapPolyMesh& map = mapPtr();
|
|
|
|
// Update fields
|
|
newMesh.updateMesh(map);
|
|
if (map.hasMotionPoints())
|
|
{
|
|
newMesh.movePoints(map.preMotionPoints());
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no checks needed exit.
|
|
if (!checkQuality)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Mesh check
|
|
// ~~~~~~~~~~~~~~~~~~
|
|
// Do not allow collapses in regions of error.
|
|
// Updates minEdgeLen, nFrozenEdges
|
|
label nOldFrozenEdges = returnReduce(nFrozenEdges, sumOp<label>());
|
|
checkMeshAndFreezeEdges
|
|
(
|
|
newMesh,
|
|
map.reversePointMap(),
|
|
mesh,
|
|
minEdgeLen,
|
|
nFrozenEdges
|
|
);
|
|
label nNewFrozenEdges = returnReduce(nFrozenEdges, sumOp<label>());
|
|
|
|
Info<< "Frozen edges "
|
|
<< returnReduce(nFrozenEdges, sumOp<label>())
|
|
<< " out of " << returnReduce(mesh.nEdges(), sumOp<label>())
|
|
<< endl;
|
|
|
|
if (nNewFrozenEdges == nOldFrozenEdges)
|
|
{
|
|
break;
|
|
}
|
|
|
|
} while (true);
|
|
|
|
|
|
if (meshChanged)
|
|
{
|
|
// Apply changes to current mesh
|
|
autoPtr<mapPolyMesh> mapPtr = savedMeshMod.changeMesh(mesh, false);
|
|
const mapPolyMesh& map = mapPtr();
|
|
|
|
// Update fields
|
|
mesh.updateMesh(map);
|
|
if (map.hasMotionPoints())
|
|
{
|
|
mesh.movePoints(map.preMotionPoints());
|
|
}
|
|
|
|
|
|
// Write resulting mesh
|
|
if (!overwrite)
|
|
{
|
|
runTime++;
|
|
}
|
|
else
|
|
{
|
|
mesh.setInstance(oldInstance);
|
|
}
|
|
|
|
Info<< nl << "Writing collapsed mesh to time "
|
|
<< runTime.timeName() << nl << endl;
|
|
|
|
mesh.write();
|
|
}
|
|
|
|
|
|
Info<< nl << "ExecutionTime = " << runTime.elapsedCpuTime() << " s"
|
|
<< " ClockTime = " << runTime.elapsedClockTime() << " s"
|
|
<< nl << endl;
|
|
|
|
Info<< "End\n" << endl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|