Files
openfoam/src/sampling/surface/cuttingPlane/cuttingPlane.C
Mark Olesen dd9ecd4988 ENH: add missing Hash function for List/UList (issue #966)
- there were previously no hashing mechanisms for lists so they
  would fall back to the definition for primitives and hash the
  memory location of the allocated List object.

- provide a UList::Hash<> sub-class for inheritance, and also a global
  specialization for UList<T>, List<T> such that the hash value for
  List<List<T>> cascades properly.

- provide similar function in triFace to ensure that it remains
  similar in behaviour to face.

- added SymmHash to Pair, for use when order is unimportant.

STYLE: use string::hash() more consistently

- no particular reason to use Hash<word>() which forwards to
  string::hash() anyhow
2018-08-08 23:54:27 +02:00

720 lines
18 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2018 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 "cuttingPlane.H"
#include "fvMesh.H"
#include "volFields.H"
#include "meshTools.H"
#include "edgeHashes.H"
#include "HashOps.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
int Foam::cuttingPlane::debug(Foam::debug::debugSwitch("cuttingPlane", 0));
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace Foam
{
// Check edge/plane intersection based on crossings ... trivial check.
// Orients the edge (first,last) points in the positive normal direction
inline bool intersectEdgeOrient(const PackedList<2>& sides, edge& e)
{
if (sides[e.first()] == sides[e.last()])
{
return false;
}
else if (sides[e.last()] < sides[e.first()])
{
e.flip();
}
return true;
}
// Check for face/plane intersection based on crossings
// Took (-1,0,+1) from plane::sign and packed as (0,1,2).
// Now use for left shift to obtain (1,2,4).
//
// Test accumulated value for an intersection with the plane.
inline bool intersectsFace
(
const PackedList<2>& sides,
const labelUList& indices
)
{
unsigned accum = 0u;
for (const label pointi : indices)
{
accum |= (1u << sides[pointi]);
}
// Accumulated value 3,5,6,7 indicates an intersection
return (accum == 3 || accum >= 5);
}
//- For hashing face point labels, which are pre-sorted.
typedef HashSet<labelList, labelList::Hash<>> labelListHashSet;
} // End namespace Foam
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
Foam::PackedList<2> Foam::cuttingPlane::classifySides
(
const plane& pln,
const pointField& pts
)
{
const label len = pts.size();
PackedList<2> output(len);
// From signed (-1,0,+1) to (0,1,2) for PackedList
for (label i=0; i < len; ++i)
{
output.set(i, unsigned(1 + pln.sign(pts[i], SMALL)));
}
return output;
}
Foam::label Foam::cuttingPlane::calcCellCuts
(
const primitiveMesh& mesh,
const PackedList<2>& sides,
bitSet& cellCuts
)
{
const faceList& faces = mesh.faces();
const label nCells = mesh.nCells();
const label nFaces = mesh.nFaces();
const label nInternFaces = mesh.nInternalFaces();
// Detect cells cuts from the face cuts
label nFaceCuts = 0;
// 1st face cut of cell
bitSet hasCut1(nCells);
// 2nd face cut of cell
bitSet hasCut2(nCells);
for (label facei = 0; facei < nInternFaces; ++facei)
{
if (intersectsFace(sides, faces[facei]))
{
const label own = mesh.faceOwner()[facei];
const label nei = mesh.faceNeighbour()[facei];
++nFaceCuts;
if (!hasCut1.set(own))
{
hasCut2.set(own);
}
if (!hasCut1.set(nei))
{
hasCut2.set(nei);
}
}
}
for (label facei = nInternFaces; facei < nFaces; ++facei)
{
if (intersectsFace(sides, faces[facei]))
{
const label own = mesh.faceOwner()[facei];
++nFaceCuts;
if (!hasCut1.set(own))
{
hasCut2.set(own);
}
}
}
hasCut1.clearStorage(); // Not needed now
if (cellCuts.size())
{
cellCuts.resize(nCells); // safety
cellCuts &= hasCut2; // restrict to cell subset
if (debug)
{
Pout<<"detected " << cellCuts.count() << "/" << nCells
<< " cells cut, subsetted from "
<< hasCut2.count() << "/" << nCells << " cells." << endl;
}
}
else
{
cellCuts = std::move(hasCut2);
if (debug)
{
Pout<<"detected " << cellCuts.count() << "/" << nCells
<< " cells cut." << endl;
}
}
if (debug && isA<fvMesh>(mesh))
{
const fvMesh& fvm = dynamicCast<const fvMesh&>(mesh);
volScalarField debugField
(
IOobject
(
"cuttingPlane.cellCuts",
fvm.time().timeName(),
fvm.time(),
IOobject::NO_READ,
IOobject::NO_WRITE,
false
),
fvm,
dimensionedScalar(dimless, Zero)
);
auto& debugFld = debugField.primitiveFieldRef();
for (const label celli : cellCuts)
{
debugFld[celli] = 1;
}
Pout<< "Writing cut types:"
<< debugField.objectPath() << endl;
debugField.write();
}
return nFaceCuts;
}
void Foam::cuttingPlane::walkCellCuts
(
const primitiveMesh& mesh,
const PackedList<2>& sides,
const bitSet& cellCuts,
const bool triangulate,
label nFaceCuts
)
{
// Dynamic lists to handle triangulation and/or missed cuts etc
const label nCellCuts = cellCuts.count();
DynamicList<point> dynCutPoints(4*nCellCuts);
DynamicList<face> dynCutFaces(4*nCellCuts);
DynamicList<label> dynCutCells(nCellCuts);
// No nFaceCuts provided? Use a reasonable estimate
if (!nFaceCuts)
{
nFaceCuts = 4*nCellCuts;
}
// Information required from the mesh
const faceList& faces = mesh.faces();
const cellList& cells = mesh.cells();
const pointField& points = mesh.points();
// Edge to pointId mapping
EdgeMap<label> handledEdges(4*nFaceCuts);
// Local scratch space for face vertices
DynamicList<label> localFaceLoop(16);
// Local scratch space for edge to pointId
EdgeMap<label> localEdges(128);
// Local scratch space for edge to faceId
EdgeMap<edge> localFaces(128);
// Avoid duplicates for cuts exactly through a mesh point.
// No other way to distinguish them, since there is no single edge
// that "owns" the point.
Map<label> endPoints;
// Hash of faces (face points) exactly on-plane
labelListHashSet onPlaneFaces;
// Failure handling
// Cells where walking failed (concave, degenerate, ...)
labelHashSet failedCells;
// To unwind insertion of end-point cutting
labelHashSet localEndPoints;
// Our recovery point on failure
label unwindPoint = 0;
// Cleanup routine for failed cell cut:
const auto unwindWalk =
[&](const label failedCellId = -1) -> void
{
// Discard points introduced
dynCutPoints.resize(unwindPoint);
// Discard end-point cuts
endPoints.erase(localEndPoints);
// Optionally record the failedCellId
if (failedCellId != -1)
{
failedCells.insert(failedCellId);
}
};
// Loop over cells that are cut
for (const label celli : cellCuts)
{
const cell& cFace = cells[celli];
// Reset local scratch
localEdges.clear();
localFaces.clear();
localFaceLoop.clear();
localEndPoints.clear();
unwindPoint = dynCutPoints.size();
// Classification for all the points cut - see intersectsFace() above
// for more detail
unsigned pointCutType = 0u;
for (const label facei : cFace)
{
const face& f = faces[facei];
forAll(f, fp)
{
edge e(f.faceEdge(fp));
// Action #1: detect edge intersection and orient edge
if (!intersectEdgeOrient(sides, e))
{
continue;
}
// Record the edge/face combination for the edge cut.
// NB: the second operation is edge::insert() which places
// facei in the unoccupied 'slot'
localFaces(e).insert(facei);
// Already handled cut point in this inner-loop?
if (localEdges.found(e))
{
// No new edge cut required
continue;
}
// Already handled cut point in the outer-loop?
label cutPointId = handledEdges.lookup(e, -1);
if (cutPointId >= 0)
{
// Copy existing edge cut-point index
localEdges.insert(e, cutPointId);
continue;
}
// Expected id for the cut point
cutPointId = dynCutPoints.size();
const point& p0 = points[e[0]];
const point& p1 = points[e[1]];
// Action #2: edge cut alpha
const scalar alpha =
this->lineIntersect(linePointRef(p0, p1));
if (alpha < SMALL)
{
// -> equal(alpha, 0) with more tolerance
if (endPoints.insert(e[0], cutPointId))
{
localEndPoints.insert(e[0]);
dynCutPoints.append(p0);
}
else
{
cutPointId = endPoints[e[0]];
}
pointCutType |= 0x1; // Cut at 0
}
else if (alpha >= (1.0 - SMALL))
{
// -> equal(alpha, 1) with more tolerance
if (endPoints.insert(e[1], cutPointId))
{
localEndPoints.insert(e[1]);
dynCutPoints.append(p1);
}
else
{
cutPointId = endPoints[e[1]];
}
pointCutType |= 0x2; // Cut at 1
}
else
{
dynCutPoints.append((1-alpha)*p0 + alpha*p1);
pointCutType |= 0x4; // Cut between
}
// Introduce new edge cut point
localEdges.insert(e, cutPointId);
}
}
// The keys of localEdges, localFaces are now identical.
// The size of either should represent the number of points for
// the resulting face loop.
const label nTargetLoop = localFaces.size();
if (nTargetLoop < 3)
{
unwindWalk(celli);
continue;
}
// Handling cuts between two cells (on-plane cuts)
// After the previous intersectEdgeOrient call, the edge is oriented
// to have 0-1 in the positive plane normal.
// If we only ever cut at the same edge end we know that we have
// an on-plane cut.
if (pointCutType == 1 || pointCutType == 2)
{
// Hash the face-points to avoid duplicate faces
if (!onPlaneFaces.insert(HashTableOps::values(localEdges, true)))
{
DebugInfo
<<"skip duplicate on-place cut for cell " << celli
<< " type (" << pointCutType << ")" << endl;
// A duplicate is not failure
unwindWalk();
continue;
}
}
// Start somewhere
label nextFace = localFaces.begin().object()[0];
DebugInfo
<< "search face " << nextFace << " IN " << localEdges << endl;
for
(
label loopi = 0;
localFaces.size() && loopi < 2*nTargetLoop;
++loopi
)
{
bool ok = false;
forAllIters(localFaces, iter)
{
DebugInfo
<< "lookup " << nextFace << " in " << iter.object() << nl;
const label got = iter.object().which(nextFace);
if (got != -1)
{
ok = true;
// The other face
nextFace = iter.object()[(got?0:1)];
// The edge -> cut point
localFaceLoop.append(localEdges[iter.key()]);
DebugInfo
<<" faces " << iter.object()
<< " point " << localFaceLoop.last()
<< " edge=" << iter.key() << " nextFace=" << nextFace
<< nl;
// Done this connection
localFaces.erase(iter);
break;
}
}
if (!ok)
{
break;
}
}
// Could also check if localFaces is now empty.
if (nTargetLoop != localFaceLoop.size())
{
DebugInfo
<<"Warn expected " << nTargetLoop << " but got "
<< localFaceLoop.size() << endl;
unwindWalk(celli);
continue;
}
// Success
handledEdges += localEdges;
face f(localFaceLoop);
// Action #3: orient face
// Orient face to point in the same direction as the plane normal
if ((f.areaNormal(dynCutPoints) & this->normal()) < 0)
{
f.flip();
}
// The cut faces can be quite ugly, so optionally triangulate
if (triangulate)
{
label nTri = f.triangles(dynCutPoints, dynCutFaces);
while (nTri--)
{
dynCutCells.append(celli);
}
}
else
{
dynCutFaces.append(f);
dynCutCells.append(celli);
}
}
if (failedCells.size())
{
WarningInFunction
<< "Failed cuts for " << failedCells.size() << " cells:" << nl
<< " " << flatOutput(failedCells.sortedToc()) << nl
<< endl;
}
// No cuts? Then no need for any of this information
if (dynCutCells.empty())
{
this->storedPoints().clear();
this->storedFaces().clear();
meshCells_.clear();
}
else
{
this->storedPoints().transfer(dynCutPoints);
this->storedFaces().transfer(dynCutFaces);
meshCells_.transfer(dynCutCells);
}
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::cuttingPlane::cuttingPlane(const plane& pln)
:
plane(pln)
{}
Foam::cuttingPlane::cuttingPlane
(
const plane& pln,
const primitiveMesh& mesh,
const bool triangulate,
const bitSet& cellIdLabels
)
:
plane(pln)
{
performCut(mesh, triangulate, cellIdLabels);
}
Foam::cuttingPlane::cuttingPlane
(
const plane& pln,
const primitiveMesh& mesh,
const bool triangulate,
bitSet&& cellIdLabels
)
:
plane(pln)
{
performCut(mesh, triangulate, cellIdLabels);
}
Foam::cuttingPlane::cuttingPlane
(
const plane& pln,
const primitiveMesh& mesh,
const bool triangulate,
const labelUList& cellIdLabels
)
:
plane(pln)
{
performCut(mesh, triangulate, cellIdLabels);
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
void Foam::cuttingPlane::performCut
(
const primitiveMesh& mesh,
const bool triangulate,
bitSet&& cellIdLabels
)
{
MeshStorage::clear();
meshCells_.clear();
// Pre-populate with restriction
bitSet cellCuts(std::move(cellIdLabels));
if (cellCuts.size())
{
cellCuts.resize(mesh.nCells());
}
// Classification of points with respect to the plane
PackedList<2> sides = classifySides(*this, mesh.points());
// Determine cells that are (likely) cut
// - some ambiguity when plane is exactly between cells
const label nFaceCuts = calcCellCuts(mesh, sides, cellCuts);
// Find closed loop from cell cuts
walkCellCuts(mesh, sides, cellCuts, triangulate, nFaceCuts);
}
void Foam::cuttingPlane::performCut
(
const primitiveMesh& mesh,
const bool triangulate,
const bitSet& cellIdLabels
)
{
bitSet subsetCells(cellIdLabels);
performCut(mesh, triangulate, std::move(subsetCells));
}
void Foam::cuttingPlane::performCut
(
const primitiveMesh& mesh,
const bool triangulate,
const labelUList& cellIdLabels
)
{
bitSet subsetCells;
if (notNull(cellIdLabels))
{
// Pre-populate with restriction
subsetCells.resize(mesh.nCells());
subsetCells.set(cellIdLabels);
}
performCut(mesh, triangulate, std::move(subsetCells));
}
void Foam::cuttingPlane::remapFaces(const labelUList& faceMap)
{
if (notNull(faceMap) && !faceMap.empty())
{
MeshStorage::remapFaces(faceMap);
List<label> newCutCells(faceMap.size());
forAll(faceMap, facei)
{
newCutCells[facei] = meshCells_[faceMap[facei]];
}
meshCells_.transfer(newCutCells);
}
}
// * * * * * * * * * * * * * * * Member Operators * * * * * * * * * * * * * //
void Foam::cuttingPlane::operator=(const cuttingPlane& rhs)
{
if (this == &rhs)
{
FatalErrorInFunction
<< "Attempted assignment to self"
<< abort(FatalError);
}
static_cast<MeshStorage&>(*this) = rhs;
static_cast<plane&>(*this) = rhs;
meshCells_ = rhs.meshCells();
}
// ************************************************************************* //