/*---------------------------------------------------------------------------*\
========= |
\\ / 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 .
\*---------------------------------------------------------------------------*/
#include "triangle.H"
#include "triSurface.H"
#include "triSurfaceTools.H"
#include "triSurfaceSearch.H"
#include "argList.H"
#include "OFstream.H"
#include "surfaceIntersection.H"
#include "SortableList.H"
#include "PatchTools.H"
using namespace Foam;
// Does face use valid vertices?
bool validTri(const bool verbose, const triSurface& surf, const label faceI)
{
// Simple check on indices ok.
const labelledTri& f = surf[faceI];
if
(
(f[0] < 0) || (f[0] >= surf.points().size())
|| (f[1] < 0) || (f[1] >= surf.points().size())
|| (f[2] < 0) || (f[2] >= surf.points().size())
)
{
WarningIn("validTri(const triSurface&, const label)")
<< "triangle " << faceI << " vertices " << f
<< " uses point indices outside point range 0.."
<< surf.points().size()-1 << endl;
return false;
}
if ((f[0] == f[1]) || (f[0] == f[2]) || (f[1] == f[2]))
{
WarningIn("validTri(const triSurface&, const label)")
<< "triangle " << faceI
<< " uses non-unique vertices " << f
<< " coords:" << f.points(surf.points())
<< endl;
return false;
}
// Duplicate triangle check
const labelList& fFaces = surf.faceFaces()[faceI];
// Check if faceNeighbours use same points as this face.
// Note: discards normal information - sides of baffle are merged.
forAll(fFaces, i)
{
label nbrFaceI = fFaces[i];
if (nbrFaceI <= faceI)
{
// lower numbered faces already checked
continue;
}
const labelledTri& nbrF = surf[nbrFaceI];
if
(
((f[0] == nbrF[0]) || (f[0] == nbrF[1]) || (f[0] == nbrF[2]))
&& ((f[1] == nbrF[0]) || (f[1] == nbrF[1]) || (f[1] == nbrF[2]))
&& ((f[2] == nbrF[0]) || (f[2] == nbrF[1]) || (f[2] == nbrF[2]))
)
{
WarningIn("validTri(const triSurface&, const label)")
<< "triangle " << faceI << " vertices " << f
<< " has the same vertices as triangle " << nbrFaceI
<< " vertices " << nbrF
<< " coords:" << f.points(surf.points())
<< endl;
return false;
}
}
return true;
}
labelList countBins
(
const scalar min,
const scalar max,
const label nBins,
const scalarField& vals
)
{
scalar dist = nBins/(max - min);
labelList binCount(nBins, 0);
forAll(vals, i)
{
scalar val = vals[i];
label index = -1;
if (Foam::mag(val - min) < SMALL)
{
index = 0;
}
else if (val >= max - SMALL)
{
index = nBins - 1;
}
else
{
index = label((val - min)*dist);
if ((index < 0) || (index >= nBins))
{
WarningIn
(
"countBins(const scalar, const scalar, const label"
", const scalarField&)"
) << "value " << val << " at index " << i
<< " outside range " << min << " .. " << max << endl;
if (index < 0)
{
index = 0;
}
else
{
index = nBins - 1;
}
}
}
binCount[index]++;
}
return binCount;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Main program:
int main(int argc, char *argv[])
{
argList::noParallel();
argList::validArgs.clear();
argList::validArgs.append("surface file");
argList::validOptions.insert("checkSelfIntersection", "");
argList::validOptions.insert("verbose", "");
argList args(argc, argv);
bool checkSelfIntersection = args.optionFound("checkSelfIntersection");
bool verbose = args.optionFound("verbose");
fileName surfFileName(args.additionalArgs()[0]);
Pout<< "Reading surface from " << surfFileName << " ..." << nl << endl;
// Read
// ~~~~
triSurface surf(surfFileName);
const pointField& surfPoints = surf.points();
Pout<< "Statistics:" << endl;
surf.writeStats(Pout);
Pout<< endl;
// Region sizes
// ~~~~~~~~~~~~
{
labelList regionSize(surf.patches().size(), 0);
scalarField regionSumArea(surf.patches().size(), 0);
forAll(surf, faceI)
{
label region = surf[faceI].region();
if (region < 0 || region >= regionSize.size())
{
WarningIn(args.executable())
<< "Triangle " << faceI << " vertices " << surf[faceI]
<< " has region " << region
<< " which is outside the range"
<< " of regions 0.." << surf.patches().size() - 1
<< endl;
}
else
{
regionSize[region]++;
regionSumArea[region] += surf[faceI].mag(surfPoints);
}
}
Pout<< "Region\tSize\tArea" << nl
<< "------\t----\t----" << nl;
forAll(surf.patches(), patchI)
{
Pout<< surf.patches()[patchI].name() << tab
<< regionSize[patchI] << tab << regionSumArea[patchI] << nl;
}
Pout<< nl << endl;
}
// Check triangles
// ~~~~~~~~~~~~~~~
{
dynamicLabelList illegalFaces(surf.size()/100 + 1);
forAll(surf, faceI)
{
if (!validTri(verbose, surf, faceI))
{
illegalFaces.append(faceI);
}
}
if (illegalFaces.size())
{
Pout<< "Surface has " << illegalFaces.size()
<< " illegal triangles." << endl;
OFstream str("illegalFaces");
Pout<< "Dumping conflicting face labels to " << str.name() << endl
<< "Paste this into the input for surfaceSubset" << endl;
str << illegalFaces;
}
else
{
Pout<< "Surface has no illegal triangles." << endl;
}
Pout<< endl;
}
// Triangle quality
// ~~~~~~~~~~~~~~~~
{
scalarField triQ(surf.size(), 0);
forAll(surf, faceI)
{
const labelledTri& f = surf[faceI];
if (f[0] == f[1] || f[0] == f[2] || f[1] == f[2])
{
//WarningIn(args.executable())
// << "Illegal triangle " << faceI << " vertices " << f
// << " coords " << f.points(surf.points()) << endl;
}
else
{
triPointRef tri
(
surf.points()[f[0]],
surf.points()[f[1]],
surf.points()[f[2]]
);
vector ba(tri.b() - tri.a());
ba /= mag(ba) + VSMALL;
vector ca(tri.c() - tri.a());
ca /= mag(ca) + VSMALL;
if (mag(ba&ca) > 1-1E-3)
{
triQ[faceI] = SMALL;
}
else
{
triQ[faceI] = triPointRef
(
surf.points()[f[0]],
surf.points()[f[1]],
surf.points()[f[2]]
).quality();
}
}
}
labelList binCount = countBins(0, 1, 20, triQ);
Pout<< "Triangle quality (equilateral=1, collapsed=0):"
<< endl;
OSstream& os = Pout;
os.width(4);
scalar dist = (1.0 - 0.0)/20.0;
scalar min = 0;
forAll(binCount, binI)
{
Pout<< " " << min << " .. " << min+dist << " : "
<< 1.0/surf.size() * binCount[binI]
<< endl;
min += dist;
}
Pout<< endl;
label minIndex = findMin(triQ);
label maxIndex = findMax(triQ);
Pout<< " min " << triQ[minIndex] << " for triangle " << minIndex
<< nl
<< " max " << triQ[maxIndex] << " for triangle " << maxIndex
<< nl
<< endl;
if (triQ[minIndex] < SMALL)
{
WarningIn(args.executable()) << "Minimum triangle quality is "
<< triQ[minIndex] << ". This might give problems in"
<< " self-intersection testing later on." << endl;
}
// Dump for subsetting
{
dynamicLabelList problemFaces(surf.size()/100+1);
forAll(triQ, faceI)
{
if (triQ[faceI] < 1E-11)
{
problemFaces.append(faceI);
}
}
OFstream str("badFaces");
Pout<< "Dumping bad quality faces to " << str.name() << endl
<< "Paste this into the input for surfaceSubset" << nl
<< nl << endl;
str << problemFaces;
}
}
// Edges
// ~~~~~
{
const edgeList& edges = surf.edges();
const pointField& localPoints = surf.localPoints();
scalarField edgeMag(edges.size());
forAll(edges, edgeI)
{
edgeMag[edgeI] = edges[edgeI].mag(localPoints);
}
label minEdgeI = findMin(edgeMag);
label maxEdgeI = findMax(edgeMag);
const edge& minE = edges[minEdgeI];
const edge& maxE = edges[maxEdgeI];
Pout<< "Edges:" << nl
<< " min " << edgeMag[minEdgeI] << " for edge " << minEdgeI
<< " points " << localPoints[minE[0]] << localPoints[minE[1]]
<< nl
<< " max " << edgeMag[maxEdgeI] << " for edge " << maxEdgeI
<< " points " << localPoints[maxE[0]] << localPoints[maxE[1]]
<< nl
<< endl;
}
// Close points
// ~~~~~~~~~~~~
{
const edgeList& edges = surf.edges();
const pointField& localPoints = surf.localPoints();
const boundBox bb(localPoints);
scalar smallDim = 1E-6 * bb.mag();
Pout<< "Checking for points less than 1E-6 of bounding box ("
<< bb.span() << " meter) apart."
<< endl;
// Sort points
SortableList sortedMag(mag(localPoints));
label nClose = 0;
for (label i = 1; i < sortedMag.size(); i++)
{
label ptI = sortedMag.indices()[i];
label prevPtI = sortedMag.indices()[i-1];
if (mag(localPoints[ptI] - localPoints[prevPtI]) < smallDim)
{
// Check if neighbours.
const labelList& pEdges = surf.pointEdges()[ptI];
label edgeI = -1;
forAll(pEdges, i)
{
const edge& e = edges[pEdges[i]];
if (e[0] == prevPtI || e[1] == prevPtI)
{
// point1 and point0 are connected through edge.
edgeI = pEdges[i];
break;
}
}
nClose++;
if (edgeI == -1)
{
Pout<< " close unconnected points "
<< ptI << ' ' << localPoints[ptI]
<< " and " << prevPtI << ' '
<< localPoints[prevPtI]
<< " distance:"
<< mag(localPoints[ptI] - localPoints[prevPtI])
<< endl;
}
else
{
Pout<< " small edge between points "
<< ptI << ' ' << localPoints[ptI]
<< " and " << prevPtI << ' '
<< localPoints[prevPtI]
<< " distance:"
<< mag(localPoints[ptI] - localPoints[prevPtI])
<< endl;
}
}
}
Pout<< "Found " << nClose << " nearby points." << nl
<< endl;
}
// Check manifold
// ~~~~~~~~~~~~~~
dynamicLabelList problemFaces(surf.size()/100 + 1);
const labelListList& eFaces = surf.edgeFaces();
label nSingleEdges = 0;
forAll(eFaces, edgeI)
{
const labelList& myFaces = eFaces[edgeI];
if (myFaces.size() == 1)
{
problemFaces.append(myFaces[0]);
nSingleEdges++;
}
}
label nMultEdges = 0;
forAll(eFaces, edgeI)
{
const labelList& myFaces = eFaces[edgeI];
if (myFaces.size() > 2)
{
forAll(myFaces, myFaceI)
{
problemFaces.append(myFaces[myFaceI]);
}
nMultEdges++;
}
}
problemFaces.shrink();
if ((nSingleEdges != 0) || (nMultEdges != 0))
{
Pout<< "Surface is not closed since not all edges connected to "
<< "two faces:" << endl
<< " connected to one face : " << nSingleEdges << endl
<< " connected to >2 faces : " << nMultEdges << endl;
Pout<< "Conflicting face labels:" << problemFaces.size() << endl;
OFstream str("problemFaces");
Pout<< "Dumping conflicting face labels to " << str.name() << endl
<< "Paste this into the input for surfaceSubset" << endl;
str << problemFaces;
}
else
{
Pout<< "Surface is closed. All edges connected to two faces." << endl;
}
Pout<< endl;
// Check singly connected domain
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
labelList faceZone;
label numZones = surf.markZones(boolList(surf.nEdges(), false), faceZone);
Pout<< "Number of unconnected parts : " << numZones << endl;
if (numZones > 1)
{
Pout<< "Splitting surface into parts ..." << endl << endl;
fileName surfFileNameBase(surfFileName.name());
for(label zone = 0; zone < numZones; zone++)
{
boolList includeMap(surf.size(), false);
forAll(faceZone, faceI)
{
if (faceZone[faceI] == zone)
{
includeMap[faceI] = true;
}
}
labelList pointMap;
labelList faceMap;
triSurface subSurf
(
surf.subsetMesh
(
includeMap,
pointMap,
faceMap
)
);
fileName subFileName
(
surfFileNameBase.lessExt()
+ "_"
+ name(zone)
+ ".ftr"
);
Pout<< "writing part " << zone << " size " << subSurf.size()
<< " to " << subFileName << endl;
subSurf.write(subFileName);
}
return 0;
}
// Check orientation
// ~~~~~~~~~~~~~~~~~
labelHashSet borderEdge(surf.size()/1000);
PatchTools::checkOrientation(surf, false, &borderEdge);
//
// Colour all faces into zones using borderEdge
//
labelList normalZone;
label numNormalZones = PatchTools::markZones(surf, borderEdge, normalZone);
Pout<< endl
<< "Number of zones (connected area with consistent normal) : "
<< numNormalZones << endl;
if (numNormalZones > 1)
{
Pout<< "More than one normal orientation." << endl;
}
Pout<< endl;
// Check self-intersection
// ~~~~~~~~~~~~~~~~~~~~~~~
if (checkSelfIntersection)
{
Pout<< "Checking self-intersection." << endl;
triSurfaceSearch querySurf(surf);
surfaceIntersection inter(querySurf);
if (inter.cutEdges().empty() && inter.cutPoints().empty())
{
Pout<< "Surface is not self-intersecting" << endl;
}
else
{
Pout<< "Surface is self-intersecting" << endl;
Pout<< "Writing edges of intersection to selfInter.obj" << endl;
OFstream intStream("selfInter.obj");
forAll(inter.cutPoints(), cutPointI)
{
const point& pt = inter.cutPoints()[cutPointI];
intStream << "v " << pt.x() << ' ' << pt.y() << ' ' << pt.z()
<< endl;
}
forAll(inter.cutEdges(), cutEdgeI)
{
const edge& e = inter.cutEdges()[cutEdgeI];
intStream << "l " << e.start()+1 << ' ' << e.end()+1 << endl;
}
}
Pout<< endl;
}
Pout<< "End\n" << endl;
return 0;
}
// ************************************************************************* //