/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | foam-extend: Open Source CFD
\\ / O peration | Version: 4.1
\\ / A nd | Web: http://www.foam-extend.org
\\/ M anipulation | For copyright notice see file Copyright
-------------------------------------------------------------------------------
License
This file is part of foam-extend.
foam-extend 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.
foam-extend 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 foam-extend. If not, see .
Description
Utility to split cells with flat faces.
Uses a geometric cut with a plane dividing the edge angle into two so
might produce funny cells. For hexes it will use by default a cut from
edge onto opposite edge (i.e. purely topological).
Options:
- split cells from cellSet only
- use geometric cut for hexes as well
The angle is the angle between two faces sharing an edge as seen from
inside each cell. So a cube will have all angles 90. If you want
to split cells with cell centre outside use e.g. angle 200
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "objectRegistry.H"
#include "foamTime.H"
#include "directTopoChange.H"
#include "mapPolyMesh.H"
#include "polyMesh.H"
#include "cellCuts.H"
#include "cellSet.H"
#include "cellModeller.H"
#include "meshCutter.H"
#include "mathematicalConstants.H"
#include "geomCellLooper.H"
#include "plane.H"
#include "edgeVertex.H"
#include "meshTools.H"
#include "ListOps.H"
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
labelList pack(const boolList& lst)
{
labelList packedLst(lst.size());
label packedI = 0;
forAll(lst, i)
{
if (lst[i])
{
packedLst[packedI++] = i;
}
}
packedLst.setSize(packedI);
return packedLst;
}
scalarField pack(const boolList& lst, const scalarField& elems)
{
scalarField packedElems(lst.size());
label packedI = 0;
forAll(lst, i)
{
if (lst[i])
{
packedElems[packedI++] = elems[i];
}
}
packedElems.setSize(packedI);
return packedElems;
}
// Given sin and cos of max angle between normals calculate whether f0 and f1
// on cellI make larger angle. Uses sinAngle only for quadrant detection.
bool largerAngle
(
const primitiveMesh& mesh,
const scalar cosAngle,
const scalar sinAngle,
const label cellI,
const label f0, // face label
const label f1,
const vector& n0, // normal at f0
const vector& n1
)
{
const labelList& own = mesh.faceOwner();
bool sameFaceOrder = !((own[f0] == cellI) ^ (own[f1] == cellI));
// Get cos between faceArea vectors. Correct so flat angle (180 degrees)
// gives -1.
scalar normalCosAngle = n0 & n1;
if (sameFaceOrder)
{
normalCosAngle = -normalCosAngle;
}
// Get cos between faceCentre and normal vector to determine in
// which quadrant angle is. (Is correct for unwarped faces only!)
// Correct for non-outwards pointing normal.
vector c1c0(mesh.faceCentres()[f1] - mesh.faceCentres()[f0]);
c1c0 /= mag(c1c0) + VSMALL;
scalar fcCosAngle = n0 & c1c0;
if (own[f0] != cellI)
{
fcCosAngle = -fcCosAngle;
}
if (sinAngle < 0.0)
{
// Looking for concave angles (quadrant 3 or 4)
if (fcCosAngle <= 0)
{
// Angle is convex so smaller.
return false;
}
else
{
if (normalCosAngle < cosAngle)
{
return false;
}
else
{
return true;
}
}
}
else
{
// Looking for convex angles (quadrant 1 or 2)
if (fcCosAngle > 0)
{
// Concave angle
return true;
}
else
{
// Convex. Check cos of normal vectors.
if (normalCosAngle > cosAngle)
{
return false;
}
else
{
return true;
}
}
}
}
// Split hex (and hex only) along edgeI creating two prisms
bool splitHex
(
const polyMesh& mesh,
const label cellI,
const label edgeI,
dynamicLabelList& cutCells,
DynamicList& cellLoops,
DynamicList& cellEdgeWeights
)
{
// cut handling functions
edgeVertex ev(mesh);
const edgeList& edges = mesh.edges();
const faceList& faces = mesh.faces();
const edge& e = edges[edgeI];
// Get faces on the side, i.e. faces not using edge but still using one of
// the edge endpoints.
label leftI = -1;
label rightI = -1;
label leftFp = -1;
label rightFp = -1;
const cell& cFaces = mesh.cells()[cellI];
forAll(cFaces, i)
{
label faceI = cFaces[i];
const face& f = faces[faceI];
label fp0 = findIndex(f, e[0]);
label fp1 = findIndex(f, e[1]);
if (fp0 == -1)
{
if (fp1 != -1)
{
// Face uses e[1] but not e[0]
rightI = faceI;
rightFp = fp1;
if (leftI != -1)
{
// Have both faces so exit
break;
}
}
}
else
{
if (fp1 != -1)
{
// Face uses both e[1] and e[0]
}
else
{
leftI = faceI;
leftFp = fp0;
if (rightI != -1)
{
break;
}
}
}
}
if (leftI == -1 || rightI == -1)
{
FatalErrorIn("splitHex") << "Problem : leftI:" << leftI
<< " rightI:" << rightI << abort(FatalError);
}
// Walk two vertices further on faces.
const face& leftF = faces[leftI];
label leftV = leftF[(leftFp + 2) % leftF.size()];
const face& rightF = faces[rightI];
label rightV = rightF[(rightFp + 2) % rightF.size()];
labelList loop(4);
loop[0] = ev.vertToEVert(e[0]);
loop[1] = ev.vertToEVert(leftV);
loop[2] = ev.vertToEVert(rightV);
loop[3] = ev.vertToEVert(e[1]);
scalarField loopWeights(4);
loopWeights[0] = -GREAT;
loopWeights[1] = -GREAT;
loopWeights[2] = -GREAT;
loopWeights[3] = -GREAT;
cutCells.append(cellI);
cellLoops.append(loop);
cellEdgeWeights.append(loopWeights);
return true;
}
// Split cellI along edgeI with a plane along halfNorm direction.
bool splitCell
(
const polyMesh& mesh,
const geomCellLooper& cellCutter,
const label cellI,
const label edgeI,
const vector& halfNorm,
const boolList& vertIsCut,
const boolList& edgeIsCut,
const scalarField& edgeWeight,
dynamicLabelList& cutCells,
DynamicList& cellLoops,
DynamicList& cellEdgeWeights
)
{
const edge& e = mesh.edges()[edgeI];
vector eVec = e.vec(mesh.points());
eVec /= mag(eVec);
vector planeN = eVec ^ halfNorm;
// Slightly tilt plane to make it not cut edges exactly
// halfway on fully regular meshes (since we want cuts
// to be snapped to vertices)
planeN += 0.01*halfNorm;
planeN /= mag(planeN);
// Define plane through edge
plane cutPlane(mesh.points()[e.start()], planeN);
labelList loop;
scalarField loopWeights;
if
(
cellCutter.cut
(
cutPlane,
cellI,
vertIsCut,
edgeIsCut,
edgeWeight,
loop,
loopWeights
)
)
{
// Did manage to cut cell. Copy into overall list.
cutCells.append(cellI);
cellLoops.append(loop);
cellEdgeWeights.append(loopWeights);
return true;
}
else
{
return false;
}
}
// Collects cuts for all cells in cellSet
void collectCuts
(
const polyMesh& mesh,
const geomCellLooper& cellCutter,
const bool geometry,
const scalar minCos,
const scalar minSin,
const cellSet& cellsToCut,
dynamicLabelList& cutCells,
DynamicList& cellLoops,
DynamicList& cellEdgeWeights
)
{
// Get data from mesh
const cellShapeList& cellShapes = mesh.cellShapes();
const labelList& own = mesh.faceOwner();
const labelListList& cellEdges = mesh.cellEdges();
const vectorField& faceAreas = mesh.faceAreas();
// Hex shape
const cellModel& hex = *(cellModeller::lookup("hex"));
// cut handling functions
edgeVertex ev(mesh);
// Cut information per mesh entity
boolList vertIsCut(mesh.nPoints(), false);
boolList edgeIsCut(mesh.nEdges(), false);
scalarField edgeWeight(mesh.nEdges(), -GREAT);
for
(
cellSet::const_iterator iter = cellsToCut.begin();
iter != cellsToCut.end();
++iter
)
{
label cellI = iter.key();
const labelList& cEdges = cellEdges[cellI];
forAll(cEdges, i)
{
label edgeI = cEdges[i];
label f0, f1;
meshTools::getEdgeFaces(mesh, cellI, edgeI, f0, f1);
vector n0 = faceAreas[f0];
n0 /= mag(n0);
vector n1 = faceAreas[f1];
n1 /= mag(n1);
if
(
largerAngle
(
mesh,
minCos,
minSin,
cellI,
f0,
f1,
n0,
n1
)
)
{
bool splitOk = false;
if (!geometry && cellShapes[cellI].model() == hex)
{
splitOk =
splitHex
(
mesh,
cellI,
edgeI,
cutCells,
cellLoops,
cellEdgeWeights
);
}
else
{
vector halfNorm;
if ((own[f0] == cellI) ^ (own[f1] == cellI))
{
// Opposite owner orientation
halfNorm = 0.5*(n0 - n1);
}
else
{
// Faces have same owner or same neighbour so
// normals point in same direction
halfNorm = 0.5*(n0 + n1);
}
splitOk =
splitCell
(
mesh,
cellCutter,
cellI,
edgeI,
halfNorm,
vertIsCut,
edgeIsCut,
edgeWeight,
cutCells,
cellLoops,
cellEdgeWeights
);
}
if (splitOk)
{
// Update cell/edge/vertex wise info.
label index = cellLoops.size() - 1;
const labelList& loop = cellLoops[index];
const scalarField& loopWeights = cellEdgeWeights[index];
forAll(loop, i)
{
label cut = loop[i];
if (ev.isEdge(cut))
{
edgeIsCut[ev.getEdge(cut)] = true;
edgeWeight[ev.getEdge(cut)] = loopWeights[i];
}
else
{
vertIsCut[ev.getVertex(cut)] = true;
}
}
// Stop checking edges for this cell.
break;
}
}
}
}
cutCells.shrink();
cellLoops.shrink();
cellEdgeWeights.shrink();
}
// Main program:
int main(int argc, char *argv[])
{
argList::noParallel();
argList::validOptions.insert("set", "cellSet name");
argList::validOptions.insert("geometry", "");
argList::validOptions.insert("tol", "edge snap tolerance");
argList::validOptions.insert("overwrite", "");
argList::validArgs.append("edge angle [0..360]");
# include "setRootCase.H"
# include "createTime.H"
runTime.functionObjects().off();
# include "createPolyMesh.H"
const word oldInstance = mesh.pointsInstance();
scalar featureAngle(readScalar(IStringStream(args.additionalArgs()[0])()));
scalar radAngle = featureAngle * mathematicalConstant::pi/180.0;
scalar minCos = Foam::cos(radAngle);
scalar minSin = Foam::sin(radAngle);
bool readSet = args.optionFound("set");
bool geometry = args.optionFound("geometry");
bool overwrite = args.optionFound("overwrite");
scalar edgeTol = 0.2;
args.optionReadIfPresent("tol", edgeTol);
Info<< "Trying to split cells with internal angles > feature angle\n" << nl
<< "featureAngle : " << featureAngle << nl
<< "edge snapping tol : " << edgeTol << nl;
if (readSet)
{
Info<< "candidate cells : cellSet " << args.option("set") << nl;
}
else
{
Info<< "candidate cells : all cells" << nl;
}
if (geometry)
{
Info<< "hex cuts : geometric; using edge tolerance" << nl;
}
else
{
Info<< "hex cuts : topological; cut to opposite edge" << nl;
}
Info<< endl;
// Cell circumference cutter
geomCellLooper cellCutter(mesh);
// Snap all edge cuts close to endpoints to vertices.
geomCellLooper::setSnapTol(edgeTol);
// Candidate cells to cut
cellSet cellsToCut(mesh, "cellsToCut", mesh.nCells()/100);
if (readSet)
{
// Read cells to cut from cellSet
cellSet cells(mesh, args.option("set"));
cellsToCut = cells;
}
while (true)
{
if (!readSet)
{
// Try all cells for cutting
for (label cellI = 0; cellI < mesh.nCells(); cellI++)
{
cellsToCut.insert(cellI);
}
}
// Cut information per cut cell
dynamicLabelList cutCells(mesh.nCells()/10 + 10);
DynamicList cellLoops(mesh.nCells()/10 + 10);
DynamicList cellEdgeWeights(mesh.nCells()/10 + 10);
collectCuts
(
mesh,
cellCutter,
geometry,
minCos,
minSin,
cellsToCut,
cutCells,
cellLoops,
cellEdgeWeights
);
cellSet cutSet(mesh, "cutSet", cutCells.size());
forAll(cutCells, i)
{
cutSet.insert(cutCells[i]);
}
// Gets cuts across cells from cuts through edges.
Info<< "Writing " << cutSet.size() << " cells to cut to cellSet "
<< cutSet.instance()/cutSet.local()/cutSet.name()
<< endl << endl;
cutSet.write();
// Analyze cuts for clashes.
cellCuts cuts
(
mesh,
cutCells, // cells candidate for cutting
cellLoops,
cellEdgeWeights
);
Info<< "Actually cut cells:" << cuts.nLoops() << nl << endl;
if (cuts.nLoops() == 0)
{
break;
}
// Remove cut cells from cellsToCut (Note:only relevant if -readSet)
forAll(cuts.cellLoops(), cellI)
{
if (cuts.cellLoops()[cellI].size())
{
//Info<< "Removing cut cell " << cellI << " from wishlist"
// << endl;
cellsToCut.erase(cellI);
}
}
// At least some cells are cut.
directTopoChange meshMod(mesh);
// Cutting engine
meshCutter cutter(mesh);
// Insert mesh refinement into directTopoChange.
cutter.setRefinement(cuts, meshMod);
// Do all changes
Info<< "Morphing ..." << endl;
if (!overwrite)
{
runTime++;
}
autoPtr morphMap = meshMod.changeMesh(mesh, false);
if (morphMap().hasMotionPoints())
{
mesh.movePoints(morphMap().preMotionPoints());
}
// Update stored labels on meshCutter
cutter.updateMesh(morphMap());
// Update cellSet
cellsToCut.updateMesh(morphMap());
Info<< "Remaining:" << cellsToCut.size() << endl;
// Write resulting mesh
if (overwrite)
{
mesh.setInstance(oldInstance);
}
Info<< "Writing refined morphMesh to time " << runTime.timeName()
<< endl;
mesh.write();
}
Info << "End\n" << endl;
return 0;
}
// ************************************************************************* //