Skip to content
Snippets Groups Projects
surfaceCheck.C 18.2 KiB
Newer Older
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
Mark Olesen's avatar
Mark Olesen committed
    \\  /    A nd           | Copyright (C) 1991-2009 OpenCFD Ltd.
     \\/     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 2 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, write to the Free Software Foundation,
    Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

\*---------------------------------------------------------------------------*/

#include "triangle.H"
#include "triSurface.H"
#include "triSurfaceTools.H"
#include "triSurfaceSearch.H"
#include "argList.H"
#include "OFstream.H"
#include "surfaceIntersection.H"
#include "SortableList.H"
mattijs's avatar
mattijs committed
#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())
    )
    {
mattijs's avatar
mattijs committed
        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]))
    {
mattijs's avatar
mattijs committed
        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]))
        )
        {
mattijs's avatar
mattijs committed
            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::addBoolOption("checkSelfIntersection");
    argList::addBoolOption("verbose");
    argList::addBoolOption
    (
        "blockMesh",
        "write vertices/blocks for blockMeshDict"
    );
    argList args(argc, argv);

    bool checkSelfIntersection = args.optionFound("checkSelfIntersection");
    bool verbose = args.optionFound("verbose");

    fileName surfFileName(args.additionalArgs()[0]);
    Info<< "Reading surface from " << surfFileName << " ..." << nl << endl;
    Info<< "Statistics:" << endl;
    surf.writeStats(Info);
    Info<< endl;

    // write bounding box corners
    if (args.optionFound("blockMesh"))
    {
        pointField cornerPts = boundBox(surf.points()).corners();

        Info<<"// blockMeshDict info" << nl;

        Info<<"vertices\n(" << nl;
        forAll(cornerPts, ptI)
        {
            Info << "    " << cornerPts[ptI] << nl;
        }

        // number of divisions needs adjustment later
        Info<<");\n" << nl
            <<"blocks\n"
            <<"(\n"
            <<"    hex (0 1 2 3 4 5 6 7) (10 10 10) simpleGrading (1 1 1)\n"
            <<");\n" << nl;

        Info<<"edges();" << nl
            <<"patches();" << endl;
    }


    // Region sizes
    // ~~~~~~~~~~~~

    {
        labelList regionSize(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]++;
            }
        }

        Info<< "Region\tSize" << nl
            << "------\t----" << nl;
        forAll(surf.patches(), patchI)
        {
            Info<< surf.patches()[patchI].name() << '\t'
                << regionSize[patchI] << nl;
        }
        Info<< nl << endl;
    }


    // Check triangles
    // ~~~~~~~~~~~~~~~

    {
        DynamicList<label> illegalFaces(surf.size()/100 + 1);

        forAll(surf, faceI)
        {
mattijs's avatar
mattijs committed
            if (!validTri(verbose, surf, faceI))
            Info<< "Surface has " << illegalFaces.size()
                << " illegal triangles." << endl;

            OFstream str("illegalFaces");
            Info<< "Dumping conflicting face labels to " << str.name() << endl
                << "Paste this into the input for surfaceSubset" << endl;
            str << illegalFaces;
        }
        else
        {
            Info<< "Surface has no illegal triangles." << endl;
        Info<< 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])
            {
Mattijs Janssens's avatar
Mattijs Janssens committed
                //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);

        Info<< "Triangle quality (equilateral=1, collapsed=0):"
        OSstream& os = Info;
        os.width(4);

        scalar dist = (1.0 - 0.0)/20.0;
        scalar min = 0;
        forAll(binCount, binI)
        {
            Info<< "    " << min << " .. " << min+dist << "  : "
                << 1.0/surf.size() * binCount[binI]
                << endl;
        Info<< endl;

        label minIndex = findMin(triQ);
        label maxIndex = findMax(triQ);

        Info<< "    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
        {
            DynamicList<label> problemFaces(surf.size()/100+1);

            forAll(triQ, faceI)
            {
                if (triQ[faceI] < 1E-11)
                {
                    problemFaces.append(faceI);
                }
            }
            OFstream str("badFaces");

            Info<< "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];


        Info<< "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();
        Info<< "Checking for points less than 1E-6 of bounding box ("
            << bb.span() << " meter) apart."
            << endl;

        // Sort points
        SortableList<scalar> 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)
                {
                    Info<< "    close unconnected points "
                        << ptI << ' ' << localPoints[ptI]
                        << " and " << prevPtI << ' '
                        << localPoints[prevPtI]
                        << " distance:"
                        << mag(localPoints[ptI] - localPoints[prevPtI])
                        << endl;
                }
                else
                {
                    Info<< "    small edge between points "
                        << ptI << ' ' << localPoints[ptI]
                        << " and " << prevPtI << ' '
                        << localPoints[prevPtI]
                        << " distance:"
                        << mag(localPoints[ptI] - localPoints[prevPtI])
                        << endl;
                }
            }
        }

        Info<< "Found " << nClose << " nearby points." << nl
            << endl;
    }



    // Check manifold
    // ~~~~~~~~~~~~~~

    DynamicList<label> 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))
    {
        Info<< "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;

        Info<< "Conflicting face labels:" << problemFaces.size() << endl;

        OFstream str("problemFaces");

        Info<< "Dumping conflicting face labels to " << str.name() << endl
            << "Paste this into the input for surfaceSubset" << endl;

        str << problemFaces;
    }
    else
    {
        Info<< "Surface is closed. All edges connected to two faces." << endl;
    Info<< endl;



    // Check singly connected domain
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    labelList faceZone;
    label numZones = surf.markZones(boolList(surf.nEdges(), false), faceZone);

    Info<< "Number of unconnected parts : " << numZones << endl;
        Info<< "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"
            );

            Info<< "writing part " << zone << " size " << subSurf.size()
                << " to " << subFileName << endl;

            subSurf.write(subFileName);
        }

        return 0;
    }



    // Check orientation
    // ~~~~~~~~~~~~~~~~~

mattijs's avatar
mattijs committed
    labelHashSet borderEdge(surf.size()/1000);
    PatchTools::checkOrientation(surf, false, &borderEdge);

    //
    // Colour all faces into zones using borderEdge
    //
    labelList normalZone;
mattijs's avatar
mattijs committed
    label numNormalZones = PatchTools::markZones(surf, borderEdge, normalZone);
    Info<< endl
        << "Number of zones (connected area with consistent normal) : "
        << numNormalZones << endl;

    if (numNormalZones > 1)
    {
        Info<< "More than one normal orientation." << endl;
    Info<< endl;



    // Check self-intersection
    // ~~~~~~~~~~~~~~~~~~~~~~~

    if (checkSelfIntersection)
    {
        Info<< "Checking self-intersection." << endl;

        triSurfaceSearch querySurf(surf);
        surfaceIntersection inter(querySurf);

        if (inter.cutEdges().empty() && inter.cutPoints().empty())
            Info<< "Surface is not self-intersecting" << endl;
            Info<< "Surface is self-intersecting" << endl;
            Info<< "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;
            }
        }
        Info<< endl;
    Info<< "\nEnd\n" << endl;

    return 0;
}


// ************************************************************************* //