mirror of
https://develop.openfoam.com/Development/openfoam.git
synced 2025-11-28 03:28:01 +00:00
- 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
720 lines
18 KiB
C
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();
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|