From 84fa233c009abc4d369c9fc6f7a1fe633257f8b1 Mon Sep 17 00:00:00 2001
From: mattijs <mattijs>
Date: Mon, 22 Nov 2010 10:48:07 +0000
Subject: [PATCH] ENH: featureEdgeMesh : merged dev_cvm functionality.

---
 .../surfaceFeatureExtract/Make/options        |   3 +-
 .../surfaceFeatureExtract.C                   |  23 +-
 src/edgeMesh/Make/options                     |   6 +-
 src/edgeMesh/edgeMesh.C                       |  10 -
 src/edgeMesh/edgeMeshI.H                      |  10 +
 .../featureEdgeMesh/featureEdgeMesh.C         | 935 +++++++++++++++++-
 .../featureEdgeMesh/featureEdgeMesh.H         | 292 +++++-
 7 files changed, 1234 insertions(+), 45 deletions(-)

diff --git a/applications/utilities/surface/surfaceFeatureExtract/Make/options b/applications/utilities/surface/surfaceFeatureExtract/Make/options
index 02e49d32c6e..f87dafaa5d1 100644
--- a/applications/utilities/surface/surfaceFeatureExtract/Make/options
+++ b/applications/utilities/surface/surfaceFeatureExtract/Make/options
@@ -1,8 +1,9 @@
 EXE_INC = \
-    -I$(LIB_SRC)/cfdTools/general/lnInclude \
     -I$(LIB_SRC)/meshTools/lnInclude \
+    -I$(LIB_SRC)/edgeMesh/lnInclude \
     -I$(LIB_SRC)/triSurface/lnInclude
 
 EXE_LIBS = \
     -lmeshTools \
+    -ledgeMesh \
     -ltriSurface
diff --git a/applications/utilities/surface/surfaceFeatureExtract/surfaceFeatureExtract.C b/applications/utilities/surface/surfaceFeatureExtract/surfaceFeatureExtract.C
index 83d84b92c82..7ba05f9d0e5 100644
--- a/applications/utilities/surface/surfaceFeatureExtract/surfaceFeatureExtract.C
+++ b/applications/utilities/surface/surfaceFeatureExtract/surfaceFeatureExtract.C
@@ -29,10 +29,13 @@ Description
 
 \*---------------------------------------------------------------------------*/
 
+
 #include "triangle.H"
 #include "triSurface.H"
 #include "argList.H"
+#include "Time.H"
 #include "surfaceFeatures.H"
+#include "featureEdgeMesh.H"
 #include "treeBoundBox.H"
 #include "meshTools.H"
 #include "OFstream.H"
@@ -135,7 +138,8 @@ int main(int argc, char *argv[])
         "remove edges within specified bounding box"
     );
 
-    argList args(argc, argv);
+#   include "setRootCase.H"
+#   include "createTime.H"
 
     Info<< "Feature line extraction is only valid on closed manifold surfaces."
         << endl;
@@ -282,7 +286,6 @@ int main(int argc, char *argv[])
     Info<< endl << "Writing edge objs." << endl;
     newSet.writeObj("final");
 
-
     Info<< nl
         << "Final feature set:" << nl
         << "    feature points : " << newSet.featurePoints().size() << nl
@@ -293,6 +296,22 @@ int main(int argc, char *argv[])
         << "        internal edges : " << newSet.nInternalEdges() << nl
         << endl;
 
+    // Extracting and writing a featureEdgeMesh
+
+    Pout<< nl << "Writing featureEdgeMesh to constant/featureEdgeMesh."
+        << endl;
+
+    featureEdgeMesh feMesh
+    (
+        newSet,
+        runTime,
+        surfFileName.lessExt().name() + ".featureEdgeMesh"
+    );
+
+    feMesh.writeObj(surfFileName.lessExt().name());
+
+    feMesh.write();
+
     Info<< "End\n" << endl;
 
     return 0;
diff --git a/src/edgeMesh/Make/options b/src/edgeMesh/Make/options
index 7e207d0dbea..4796c18e22c 100644
--- a/src/edgeMesh/Make/options
+++ b/src/edgeMesh/Make/options
@@ -1,5 +1,9 @@
 EXE_INC = \
-    -I$(LIB_SRC)/fileFormats/lnInclude
+    -I$(LIB_SRC)/fileFormats/lnInclude \
+    -I$(LIB_SRC)/triSurface/lnInclude \ 
+    -I$(LIB_SRC)/meshTools/lnInclude    
 
 LIB_LIBS = \
+    -ltriSurface \
+    -lmeshTools \ 
     -lfileFormats
diff --git a/src/edgeMesh/edgeMesh.C b/src/edgeMesh/edgeMesh.C
index d5c8f12ef8f..7dd70151445 100644
--- a/src/edgeMesh/edgeMesh.C
+++ b/src/edgeMesh/edgeMesh.C
@@ -387,14 +387,4 @@ void Foam::edgeMesh::mergePoints(const scalar mergeDist)
 }
 
 
-// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
-
-void Foam::edgeMesh::operator=(const edgeMesh& rhs)
-{
-    points_ = rhs.points_;
-    edges_ = rhs.edges_;
-    pointEdgesPtr_.clear();
-}
-
-
 // ************************************************************************* //
diff --git a/src/edgeMesh/edgeMeshI.H b/src/edgeMesh/edgeMeshI.H
index d5e38343ec4..c15fbd14ce9 100644
--- a/src/edgeMesh/edgeMeshI.H
+++ b/src/edgeMesh/edgeMeshI.H
@@ -69,4 +69,14 @@ inline Foam::edgeList& Foam::edgeMesh::storedEdges()
 }
 
 
+// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
+
+void Foam::edgeMesh::operator=(const edgeMesh& rhs)
+{
+    points_ = rhs.points_;
+    edges_ = rhs.edges_;
+    pointEdgesPtr_.clear();
+}
+
+
 // ************************************************************************* //
diff --git a/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.C b/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.C
index 34bcd4388e4..da89dcf2d53 100644
--- a/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.C
+++ b/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.C
@@ -24,18 +24,56 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "featureEdgeMesh.H"
+#include "triSurface.H"
+#include "Random.H"
+#include "Time.H"
+#include "meshTools.H"
+#include "linePointRef.H"
+#include "ListListOps.H"
+#include "OFstream.H"
+#include "IFstream.H"
+#include "unitConversion.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 defineTypeNameAndDebug(Foam::featureEdgeMesh, 0);
 
+Foam::scalar Foam::featureEdgeMesh::cosNormalAngleTol_ =
+    Foam::cos(degToRad(0.1));
+
+
+Foam::label Foam::featureEdgeMesh::convexStart_ = 0;
+
+
+Foam::label Foam::featureEdgeMesh::externalStart_ = 0;
+
+
+Foam::label Foam::featureEdgeMesh::nPointTypes = 4;
+
+
+Foam::label Foam::featureEdgeMesh::nEdgeTypes = 5;
+
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::featureEdgeMesh::featureEdgeMesh(const IOobject& io)
 :
     regIOobject(io),
-    edgeMesh(pointField(0), edgeList(0))
+    edgeMesh(pointField(0), edgeList(0)),
+    concaveStart_(0),
+    mixedStart_(0),
+    nonFeatureStart_(0),
+    internalStart_(0),
+    flatStart_(0),
+    openStart_(0),
+    multipleStart_(0),
+    normals_(0),
+    edgeDirections_(0),
+    edgeNormals_(0),
+    featurePointNormals_(0),
+    regionEdges_(0),
+    edgeTree_(),
+    edgeTreesByType_()
 {
     if
     (
@@ -52,9 +90,39 @@ Foam::featureEdgeMesh::featureEdgeMesh(const IOobject& io)
                 << endl;
         }
 
+        Istream& is = readStream(typeName);
+
+        is  >> *this
+            >> concaveStart_
+            >> mixedStart_
+            >> nonFeatureStart_
+            >> internalStart_
+            >> flatStart_
+            >> openStart_
+            >> multipleStart_
+            >> normals_
+            >> edgeNormals_
+            >> featurePointNormals_
+            >> regionEdges_;
 
-        readStream(typeName) >> *this;
         close();
+
+        {
+            // Calculate edgeDirections
+
+            const edgeList& eds(edges());
+
+            const pointField& pts(points());
+
+            edgeDirections_.setSize(eds.size());
+
+            forAll(eds, eI)
+            {
+                edgeDirections_[eI] = eds[eI].vec(pts);
+            }
+
+            edgeDirections_ /= mag(edgeDirections_);
+        }
     }
 
     if (debug)
@@ -71,11 +139,25 @@ Foam::featureEdgeMesh::featureEdgeMesh(const IOobject& io)
 Foam::featureEdgeMesh::featureEdgeMesh
 (
     const IOobject& io,
-    const featureEdgeMesh& em
+    const featureEdgeMesh& fem
 )
 :
     regIOobject(io),
-    edgeMesh(em)
+    edgeMesh(fem),
+    concaveStart_(fem.concaveStart()),
+    mixedStart_(fem.mixedStart()),
+    nonFeatureStart_(fem.nonFeatureStart()),
+    internalStart_(fem.internalStart()),
+    flatStart_(fem.flatStart()),
+    openStart_(fem.openStart()),
+    multipleStart_(fem.multipleStart()),
+    normals_(fem.normals()),
+    edgeDirections_(fem.edgeDirections()),
+    edgeNormals_(fem.edgeNormals()),
+    featurePointNormals_(fem.featurePointNormals()),
+    regionEdges_(fem.regionEdges()),
+    edgeTree_(),
+    edgeTreesByType_()
 {}
 
 
@@ -87,42 +169,851 @@ Foam::featureEdgeMesh::featureEdgeMesh
 )
 :
     regIOobject(io),
-    edgeMesh(pointLst, edgeLst)
+    edgeMesh(pointLst, edgeLst),
+    concaveStart_(0),
+    mixedStart_(0),
+    nonFeatureStart_(0),
+    internalStart_(0),
+    flatStart_(0),
+    openStart_(0),
+    multipleStart_(0),
+    normals_(0),
+    edgeDirections_(0),
+    edgeNormals_(0),
+    featurePointNormals_(0),
+    regionEdges_(0),
+    edgeTree_(),
+    edgeTreesByType_()
+{}
+
+
+Foam::featureEdgeMesh::featureEdgeMesh
+(
+    const surfaceFeatures& sFeat,
+    const objectRegistry& obr,
+    const fileName& sFeatFileName
+)
+:
+    regIOobject
+    (
+        IOobject
+        (
+            sFeatFileName,
+            obr.time().constant(),
+            "featureEdgeMesh",
+            obr,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        )
+    ),
+    edgeMesh(pointField(0), edgeList(0)),
+    concaveStart_(-1),
+    mixedStart_(-1),
+    nonFeatureStart_(-1),
+    internalStart_(-1),
+    flatStart_(-1),
+    openStart_(-1),
+    multipleStart_(-1),
+    normals_(0),
+    edgeDirections_(0),
+    edgeNormals_(0),
+    featurePointNormals_(0),
+    regionEdges_(0),
+    edgeTree_(),
+    edgeTreesByType_()
 {
-    if
+    // Extract and reorder the data from surfaceFeatures
+
+    // References to the surfaceFeatures data
+    const triSurface& surf(sFeat.surface());
+    const pointField& sFeatLocalPts(surf.localPoints());
+    const edgeList& sFeatEds(surf.edges());
+
+    // Filling the featureEdgeMesh with the raw geometrical data.
+
+    label nFeatEds = sFeat.featureEdges().size();
+
+    DynamicList<point> tmpPts;
+    edgeList eds(nFeatEds);
+    DynamicList<vector> norms;
+    vectorField edgeDirections(nFeatEds);
+    labelListList edgeNormals(nFeatEds);
+    DynamicList<label> regionEdges;
+
+    // Mapping between old and new indices, there is entry in the map for each
+    // of sFeatLocalPts, -1 means that this point hasn't been used (yet), >= 0
+    // corresponds to the index
+    labelList pointMap(sFeatLocalPts.size(), -1);
+
+    // Noting when the normal of a face has been used so not to duplicate
+    labelList faceMap(surf.size(), -1);
+
+    // Collecting the status of edge for subsequent sorting
+    List<edgeStatus> edStatus(nFeatEds, NONE);
+
+    forAll(sFeat.featurePoints(), i)
+    {
+        label sFPI = sFeat.featurePoints()[i];
+
+        tmpPts.append(sFeatLocalPts[sFPI]);
+
+        pointMap[sFPI] = tmpPts.size() - 1;
+    }
+
+    // All feature points have been added
+    nonFeatureStart_ = tmpPts.size();
+
+    forAll(sFeat.featureEdges(), i)
+    {
+        label sFEI = sFeat.featureEdges()[i];
+
+        const edge& fE(sFeatEds[sFEI]);
+
+        // Check to see if the points have been already used
+        if (pointMap[fE.start()] == -1)
+        {
+             tmpPts.append(sFeatLocalPts[fE.start()]);
+
+             pointMap[fE.start()] = tmpPts.size() - 1;
+        }
+
+        eds[i].start() = pointMap[fE.start()];
+
+        if (pointMap[fE.end()] == -1)
+        {
+            tmpPts.append(sFeatLocalPts[fE.end()]);
+
+            pointMap[fE.end()] = tmpPts.size() - 1;
+        }
+
+        eds[i].end() = pointMap[fE.end()];
+
+        // Pick up the faces adjacent to the feature edge
+        const labelList& eFaces = surf.edgeFaces()[sFEI];
+
+        edgeNormals[i].setSize(eFaces.size());
+
+        forAll(eFaces, j)
+        {
+            label eFI = eFaces[j];
+
+            // Check to see if the points have been already used
+            if (faceMap[eFI] == -1)
+            {
+                norms.append(surf.faceNormals()[eFI]);
+
+                faceMap[eFI] = norms.size() - 1;
+            }
+
+            edgeNormals[i][j] = faceMap[eFI];
+        }
+
+        vector fC0tofC1(vector::zero);
+
+        if (eFaces.size() == 2)
+        {
+            fC0tofC1 =
+                surf[eFaces[1]].centre(surf.points())
+              - surf[eFaces[0]].centre(surf.points());
+        }
+
+        edStatus[i] = classifyEdge(norms, edgeNormals[i], fC0tofC1);
+
+        edgeDirections[i] = fE.vec(sFeatLocalPts);
+
+        if (i < sFeat.nRegionEdges())
+        {
+            regionEdges.append(i);
+        }
+    }
+
+    // Reorder the edges by classification
+
+    List<DynamicList<label> > allEds(nEdgeTypes);
+
+    DynamicList<label>& externalEds(allEds[0]);
+    DynamicList<label>& internalEds(allEds[1]);
+    DynamicList<label>& flatEds(allEds[2]);
+    DynamicList<label>& openEds(allEds[3]);
+    DynamicList<label>& multipleEds(allEds[4]);
+
+    forAll(eds, i)
+    {
+        edgeStatus eStat = edStatus[i];
+
+        if (eStat == EXTERNAL)
+        {
+            externalEds.append(i);
+        }
+        else if (eStat == INTERNAL)
+        {
+            internalEds.append(i);
+        }
+        else if (eStat == FLAT)
+        {
+            flatEds.append(i);
+        }
+        else if (eStat == OPEN)
+        {
+            openEds.append(i);
+        }
+        else if (eStat == MULTIPLE)
+        {
+            multipleEds.append(i);
+        }
+        else if (eStat == NONE)
+        {
+            FatalErrorIn
+            (
+                "Foam::featureEdgeMesh::featureEdgeMesh"
+                "("
+                "    const surfaceFeatures& sFeat,"
+                "    const objectRegistry& obr,"
+                "    const fileName& sFeatFileName"
+                ")"
+            )
+                << nl << "classifyEdge returned NONE on edge "
+                << eds[i]
+                << ". There is a problem with definition of this edge."
+                << nl << abort(FatalError);
+        }
+    }
+
+    internalStart_ = externalEds.size();
+    flatStart_ = internalStart_ + internalEds.size();
+    openStart_ = flatStart_ + flatEds.size();
+    multipleStart_ = openStart_ + openEds.size();
+
+    labelList edMap
     (
-        io.readOpt() == IOobject::MUST_READ
-     || io.readOpt() == IOobject::MUST_READ_IF_MODIFIED
-     || (io.readOpt() == IOobject::READ_IF_PRESENT && headerOk())
-    )
+        ListListOps::combine<labelList>
+        (
+            allEds,
+            accessOp<labelList>()
+        )
+    );
+
+    edMap = invert(edMap.size(), edMap);
+
+    inplaceReorder(edMap, eds);
+    inplaceReorder(edMap, edStatus);
+    inplaceReorder(edMap, edgeDirections);
+    inplaceReorder(edMap, edgeNormals);
+    inplaceRenumber(edMap, regionEdges);
+
+    pointField pts(tmpPts);
+
+    // Initialise the edgeMesh
+    edgeMesh::operator=(edgeMesh(pts, eds));
+
+    // Initialise sorted edge related data
+    edgeDirections_ = edgeDirections/mag(edgeDirections);
+    edgeNormals_ = edgeNormals;
+    regionEdges_ = regionEdges;
+
+    // Normals are all now found and indirectly addressed, can also be stored
+    normals_ = vectorField(norms);
+
+    // Reorder the feature points by classification
+
+    List<DynamicList<label> > allPts(3);
+
+    DynamicList<label>& convexPts(allPts[0]);
+    DynamicList<label>& concavePts(allPts[1]);
+    DynamicList<label>& mixedPts(allPts[2]);
+
+    for (label i = 0; i < nonFeatureStart_; i++)
     {
-        if (readOpt() == IOobject::MUST_READ_IF_MODIFIED)
+        pointStatus ptStatus = classifyFeaturePoint(i);
+
+        if (ptStatus == CONVEX)
         {
-            WarningIn("featureEdgeMesh::featureEdgeMesh(const IOobject&)")
-                << "Specified IOobject::MUST_READ_IF_MODIFIED but class"
-                << " does not support automatic rereading."
-                << endl;
+            convexPts.append(i);
+        }
+        else if (ptStatus == CONCAVE)
+        {
+            concavePts.append(i);
         }
+        else if (ptStatus == MIXED)
+        {
+            mixedPts.append(i);
+        }
+        else if (ptStatus == NONFEATURE)
+        {
+            FatalErrorIn
+            (
+                "Foam::featureEdgeMesh::featureEdgeMesh"
+                "("
+                "    const surfaceFeatures& sFeat,"
+                "    const objectRegistry& obr,"
+                "    const fileName& sFeatFileName"
+                ")"
+            )
+                << nl << "classifyFeaturePoint returned NONFEATURE on point at "
+                << points()[i]
+                << ". There is a problem with definition of this feature point."
+                << nl << abort(FatalError);
+        }
+    }
 
+    concaveStart_ = convexPts.size();
+    mixedStart_ = concaveStart_ + concavePts.size();
 
-        readStream(typeName) >> *this;
-        close();
+    labelList ftPtMap
+    (
+        ListListOps::combine<labelList>
+        (
+            allPts,
+            accessOp<labelList>()
+        )
+    );
+
+    ftPtMap = invert(ftPtMap.size(), ftPtMap);
+
+    // Creating the ptMap from the ftPtMap with identity values up to the size
+    // of pts to create an oldToNew map for inplaceReorder
+
+    labelList ptMap(identity(pts.size()));
+
+    forAll(ftPtMap, i)
+    {
+        ptMap[i] = ftPtMap[i];
+    }
+
+    inplaceReorder(ptMap, pts);
+
+    forAll(eds, i)
+    {
+        inplaceRenumber(ptMap, eds[i]);
+    }
+
+    // Reinitialise the edgeMesh with sorted feature points and
+    // renumbered edges
+    edgeMesh::operator=(edgeMesh(pts, eds));
+
+    // Generate the featurePointNormals
+
+    labelListList featurePointNormals(nonFeatureStart_);
+
+    for (label i = 0; i < nonFeatureStart_; i++)
+    {
+        DynamicList<label> tmpFtPtNorms;
+
+        const labelList& ptEds = pointEdges()[i];
+
+        forAll(ptEds, j)
+        {
+            const labelList& ptEdNorms(edgeNormals[ptEds[j]]);
+
+            forAll(ptEdNorms, k)
+            {
+                if (findIndex(tmpFtPtNorms, ptEdNorms[k]) == -1)
+                {
+                    bool addNormal = true;
+
+                    // Check that the normal direction is unique at this feature
+                    forAll(tmpFtPtNorms, q)
+                    {
+                        if
+                        (
+                            (normals_[ptEdNorms[k]] & normals_[tmpFtPtNorms[q]])
+                          > cosNormalAngleTol_
+                        )
+                        {
+                            // Parallel to an existing normal, do not add
+                            addNormal = false;
+
+                            break;
+                        }
+                    }
+
+                    if (addNormal)
+                    {
+                        tmpFtPtNorms.append(ptEdNorms[k]);
+                    }
+                }
+            }
+        }
+
+        featurePointNormals[i] = tmpFtPtNorms;
+    }
+
+    featurePointNormals_ = featurePointNormals;
+}
+
+
+Foam::featureEdgeMesh::featureEdgeMesh
+(
+    const IOobject& io,
+    const pointField& pts,
+    const edgeList& eds,
+    label concaveStart,
+    label mixedStart,
+    label nonFeatureStart,
+    label internalStart,
+    label flatStart,
+    label openStart,
+    label multipleStart,
+    const vectorField& normals,
+    const vectorField& edgeDirections,
+    const labelListList& edgeNormals,
+    const labelListList& featurePointNormals,
+    const labelList& regionEdges
+)
+:
+    regIOobject(io),
+    edgeMesh(pts, eds),
+    concaveStart_(concaveStart),
+    mixedStart_(mixedStart),
+    nonFeatureStart_(nonFeatureStart),
+    internalStart_(internalStart),
+    flatStart_(flatStart),
+    openStart_(openStart),
+    multipleStart_(multipleStart),
+    normals_(normals),
+    edgeDirections_(edgeDirections),
+    edgeNormals_(edgeNormals),
+    featurePointNormals_(featurePointNormals),
+    regionEdges_(regionEdges),
+    edgeTree_(),
+    edgeTreesByType_()
+{}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::featureEdgeMesh::~featureEdgeMesh()
+{}
+
+
+// * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * * //
+
+Foam::featureEdgeMesh::pointStatus Foam::featureEdgeMesh::classifyFeaturePoint
+(
+    label ptI
+) const
+{
+    labelList ptEds(pointEdges()[ptI]);
+
+    label nPtEds = ptEds.size();
+    label nExternal = 0;
+    label nInternal = 0;
+
+    if (nPtEds == 0)
+    {
+        // There are no edges attached to the point, this is a problem
+        return NONFEATURE;
+    }
+
+    forAll(ptEds, i)
+    {
+        edgeStatus edStat = getEdgeStatus(ptEds[i]);
+
+        if (edStat == EXTERNAL)
+        {
+            nExternal++;
+        }
+        else if (edStat == INTERNAL)
+        {
+            nInternal++;
+        }
+    }
+
+    if (nExternal == nPtEds)
+    {
+        return CONVEX;
+    }
+    else if (nInternal == nPtEds)
+    {
+        return CONCAVE;
+    }
+    else
+    {
+        return MIXED;
+    }
+}
+
+
+Foam::featureEdgeMesh::edgeStatus Foam::featureEdgeMesh::classifyEdge
+(
+    const List<vector>& norms,
+    const labelList& edNorms,
+    const vector& fC0tofC1
+) const
+{
+    label nEdNorms = edNorms.size();
+
+    if (nEdNorms == 1)
+    {
+        return OPEN;
+    }
+    else if (nEdNorms == 2)
+    {
+        const vector n0(norms[edNorms[0]]);
+        const vector n1(norms[edNorms[1]]);
+
+        if ((n0 & n1) > cosNormalAngleTol_)
+        {
+            return FLAT;
+        }
+        else if ((fC0tofC1 & n0) > 0.0)
+        {
+            return INTERNAL;
+        }
+        else
+        {
+            return EXTERNAL;
+        }
+    }
+    else if (nEdNorms > 2)
+    {
+        return MULTIPLE;
+    }
+    else
+    {
+        // There is a problem - the edge has no normals
+        return NONE;
+    }
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+void Foam::featureEdgeMesh::nearestFeatureEdge
+(
+    const point& sample,
+    scalar searchDistSqr,
+    pointIndexHit& info
+) const
+{
+    info = edgeTree().findNearest
+    (
+        sample,
+        searchDistSqr
+    );
+}
+
+
+void Foam::featureEdgeMesh::nearestFeatureEdge
+(
+    const pointField& samples,
+    const scalarField& searchDistSqr,
+    List<pointIndexHit>& info
+) const
+{
+    info.setSize(samples.size());
+
+    forAll(samples, i)
+    {
+        nearestFeatureEdge
+        (
+            samples[i],
+            searchDistSqr[i],
+            info[i]
+        );
+    }
+}
+
+
+void Foam::featureEdgeMesh::nearestFeatureEdgeByType
+(
+    const point& sample,
+    const scalarField& searchDistSqr,
+    List<pointIndexHit>& info
+) const
+{
+    const PtrList<indexedOctree<treeDataEdge> >& edgeTrees = edgeTreesByType();
+
+    info.setSize(edgeTrees.size());
+
+    labelList sliceStarts(edgeTrees.size());
+
+    sliceStarts[0] = externalStart_;
+    sliceStarts[1] = internalStart_;
+    sliceStarts[2] = flatStart_;
+    sliceStarts[3] = openStart_;
+    sliceStarts[4] = multipleStart_;
+
+    forAll(edgeTrees, i)
+    {
+        info[i] = edgeTrees[i].findNearest
+        (
+            sample,
+            searchDistSqr[i]
+        );
+
+        // The index returned by the indexedOctree is local to the slice of
+        // edges it was supplied with, return the index to the value in the
+        // complete edge list
+
+        info[i].setIndex(info[i].index() + sliceStarts[i]);
+    }
+}
+
+
+const Foam::indexedOctree<Foam::treeDataEdge>&
+Foam::featureEdgeMesh::edgeTree() const
+{
+    if (edgeTree_.empty())
+    {
+        Random rndGen(17301893);
+
+        // Slightly extended bb. Slightly off-centred just so on symmetric
+        // geometry there are less face/edge aligned items.
+        treeBoundBox bb
+        (
+            treeBoundBox(points()).extend(rndGen, 1E-4)
+        );
+
+        bb.min() -= point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
+        bb.max() += point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
+
+        labelList allEdges(identity(edges().size()));
+
+        edgeTree_.reset
+        (
+            new indexedOctree<treeDataEdge>
+            (
+                treeDataEdge
+                (
+                    false,          // cachebb
+                    edges(),        // edges
+                    points(),       // points
+                    allEdges        // selected edges
+                ),
+                bb,     // bb
+                8,      // maxLevel
+                10,     // leafsize
+                3.0     // duplicity
+            )
+        );
     }
+
+    return edgeTree_();
 }
 
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+const Foam::PtrList<Foam::indexedOctree<Foam::treeDataEdge> >&
+Foam::featureEdgeMesh::edgeTreesByType() const
+{
+    if (edgeTreesByType_.size() == 0)
+    {
+        edgeTreesByType_.setSize(nEdgeTypes);
+
+        Random rndGen(872141);
+
+        // Slightly extended bb. Slightly off-centred just so on symmetric
+        // geometry there are less face/edge aligned items.
+        treeBoundBox bb
+        (
+            treeBoundBox(points()).extend(rndGen, 1E-4)
+        );
+
+        bb.min() -= point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
+        bb.max() += point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
+
+        labelListList sliceEdges(nEdgeTypes);
+
+        // External edges
+        sliceEdges[0] =
+            identity(internalStart_ - externalStart_) + externalStart_;
+
+        // Internal edges
+        sliceEdges[1] = identity(flatStart_ - internalStart_) + internalStart_;
+
+        // Flat edges
+        sliceEdges[2] = identity(openStart_ - flatStart_) + flatStart_;
+
+        // Open edges
+        sliceEdges[3] = identity(multipleStart_ - openStart_) + openStart_;
+
+        // Multiple edges
+        sliceEdges[4] =
+            identity(edges().size() - multipleStart_) + multipleStart_;
+
+        forAll(edgeTreesByType_, i)
+        {
+            edgeTreesByType_.set
+            (
+                i,
+                new indexedOctree<treeDataEdge>
+                (
+                    treeDataEdge
+                    (
+                        false,          // cachebb
+                        edges(),        // edges
+                        points(),       // points
+                        sliceEdges[i]   // selected edges
+                    ),
+                    bb,     // bb
+                    8,      // maxLevel
+                    10,     // leafsize
+                    3.0     // duplicity
+                )
+            );
+        }
+    }
+
+    return edgeTreesByType_;
+}
+
 
-bool Foam::featureEdgeMesh::readData(Istream& is)
+void Foam::featureEdgeMesh::writeObj
+(
+    const fileName& prefix
+) const
 {
-    is  >> *this;
-    return !is.bad();
+    Pout<< nl << "Writing featureEdgeMesh components to " << prefix << endl;
+
+    label verti = 0;
+
+    edgeMesh::write(prefix + "_edgeMesh.obj");
+
+    OFstream convexFtPtStr(prefix + "_convexFeaturePts.obj");
+    Pout<< "Writing convex feature points to " << convexFtPtStr.name() << endl;
+
+    for(label i = 0; i < concaveStart_; i++)
+    {
+        meshTools::writeOBJ(convexFtPtStr, points()[i]);
+    }
+
+    OFstream concaveFtPtStr(prefix + "_concaveFeaturePts.obj");
+    Pout<< "Writing concave feature points to "
+        << concaveFtPtStr.name() << endl;
+
+    for(label i = concaveStart_; i < mixedStart_; i++)
+    {
+        meshTools::writeOBJ(concaveFtPtStr, points()[i]);
+    }
+
+    OFstream mixedFtPtStr(prefix + "_mixedFeaturePts.obj");
+    Pout<< "Writing mixed feature points to " << mixedFtPtStr.name() << endl;
+
+    for(label i = mixedStart_; i < nonFeatureStart_; i++)
+    {
+        meshTools::writeOBJ(mixedFtPtStr, points()[i]);
+    }
+
+    OFstream mixedFtPtStructureStr(prefix + "_mixedFeaturePtsStructure.obj");
+    Pout<< "Writing mixed feature point structure to "
+        << mixedFtPtStructureStr.name() << endl;
+
+    verti = 0;
+    for(label i = mixedStart_; i < nonFeatureStart_; i++)
+    {
+        const labelList& ptEds = pointEdges()[i];
+
+        forAll(ptEds, j)
+        {
+            const edge& e = edges()[ptEds[j]];
+
+            meshTools::writeOBJ(mixedFtPtStructureStr, points()[e[0]]); verti++;
+            meshTools::writeOBJ(mixedFtPtStructureStr, points()[e[1]]); verti++;
+            mixedFtPtStructureStr << "l " << verti-1 << ' ' << verti << endl;
+        }
+    }
+
+    OFstream externalStr(prefix + "_externalEdges.obj");
+    Pout<< "Writing external edges to " << externalStr.name() << endl;
+
+    verti = 0;
+    for (label i = externalStart_; i < internalStart_; i++)
+    {
+        const edge& e = edges()[i];
+
+        meshTools::writeOBJ(externalStr, points()[e[0]]); verti++;
+        meshTools::writeOBJ(externalStr, points()[e[1]]); verti++;
+        externalStr << "l " << verti-1 << ' ' << verti << endl;
+    }
+
+    OFstream internalStr(prefix + "_internalEdges.obj");
+    Pout<< "Writing internal edges to " << internalStr.name() << endl;
+
+    verti = 0;
+    for (label i = internalStart_; i < flatStart_; i++)
+    {
+        const edge& e = edges()[i];
+
+        meshTools::writeOBJ(internalStr, points()[e[0]]); verti++;
+        meshTools::writeOBJ(internalStr, points()[e[1]]); verti++;
+        internalStr << "l " << verti-1 << ' ' << verti << endl;
+    }
+
+    OFstream flatStr(prefix + "_flatEdges.obj");
+    Pout<< "Writing flat edges to " << flatStr.name() << endl;
+
+    verti = 0;
+    for (label i = flatStart_; i < openStart_; i++)
+    {
+        const edge& e = edges()[i];
+
+        meshTools::writeOBJ(flatStr, points()[e[0]]); verti++;
+        meshTools::writeOBJ(flatStr, points()[e[1]]); verti++;
+        flatStr << "l " << verti-1 << ' ' << verti << endl;
+    }
+
+    OFstream openStr(prefix + "_openEdges.obj");
+    Pout<< "Writing open edges to " << openStr.name() << endl;
+
+    verti = 0;
+    for (label i = openStart_; i < multipleStart_; i++)
+    {
+        const edge& e = edges()[i];
+
+        meshTools::writeOBJ(openStr, points()[e[0]]); verti++;
+        meshTools::writeOBJ(openStr, points()[e[1]]); verti++;
+        openStr << "l " << verti-1 << ' ' << verti << endl;
+    }
+
+    OFstream multipleStr(prefix + "_multipleEdges.obj");
+    Pout<< "Writing multiple edges to " << multipleStr.name() << endl;
+
+    verti = 0;
+    for (label i = multipleStart_; i < edges().size(); i++)
+    {
+        const edge& e = edges()[i];
+
+        meshTools::writeOBJ(multipleStr, points()[e[0]]); verti++;
+        meshTools::writeOBJ(multipleStr, points()[e[1]]); verti++;
+        multipleStr << "l " << verti-1 << ' ' << verti << endl;
+    }
+
+    OFstream regionStr(prefix + "_regionEdges.obj");
+    Pout<< "Writing region edges to " << regionStr.name() << endl;
+
+    verti = 0;
+    forAll(regionEdges_, i)
+    {
+        const edge& e = edges()[regionEdges_[i]];
+
+        meshTools::writeOBJ(regionStr, points()[e[0]]); verti++;
+        meshTools::writeOBJ(regionStr, points()[e[1]]); verti++;
+        regionStr << "l " << verti-1 << ' ' << verti << endl;
+    }
 }
 
 
 bool Foam::featureEdgeMesh::writeData(Ostream& os) const
 {
-    os  << *this;
+    os  << "// points, edges, concaveStart, mixedStart, nonFeatureStart, " << nl
+        << "// internalStart, flatStart, openStart, multipleStart, " << nl
+        << "// normals, edgeNormals, featurePointNormals, regionEdges" << nl
+        << endl;
+
+    os  << points() << nl
+        << edges() << nl
+        << concaveStart_ << token::SPACE
+        << mixedStart_ << token::SPACE
+        << nonFeatureStart_ << token::SPACE
+        << internalStart_ << token::SPACE
+        << flatStart_ << token::SPACE
+        << openStart_ << token::SPACE
+        << multipleStart_ << nl
+        << normals_ << nl
+        << edgeNormals_ << nl
+        << featurePointNormals_ << nl
+        << regionEdges_
+        << endl;
 
     return os.good();
 }
diff --git a/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.H b/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.H
index 613c9bdd94b..93f034d9aaf 100644
--- a/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.H
+++ b/src/edgeMesh/featureEdgeMesh/featureEdgeMesh.H
@@ -25,9 +25,27 @@ Class
     Foam::featureEdgeMesh
 
 Description
-    features (lines), readable from file
+
+    Feature points are a sorted subset at the start of the overall points list:
+        0 .. concaveStart_-1                : convex points (w.r.t normals)
+        concaveStart_-1 .. mixedStart_-1    : concave points
+        mixedStart_ .. nonFeatureStart_     : mixed internal/external points
+        nonFeatureStart_ .. size-1          : non-feature points
+
+    Feature edges are the edgeList of the edgeMesh and are sorted:
+        0 .. internalStart_-1           : external edges (convex w.r.t normals)
+        internalStart_ .. flatStart_-1  : internal edges (concave)
+        flatStart_ .. openStart_-1      : flat edges (neither concave or convex)
+                                          can arise from region interfaces on
+                                          flat surfaces
+        openStart_ .. multipleStart_-1  : open edges (e.g. from baffle surfaces)
+        multipleStart_ .. size-1        : multiply connected edges
+
+    The edge direction and feature edge and feature point adjacent normals
+    are stored.
 
 SourceFiles
+    featureEdgeMeshI.H
     featureEdgeMesh.C
 
 \*---------------------------------------------------------------------------*/
@@ -36,7 +54,12 @@ SourceFiles
 #define featureEdgeMesh_H
 
 #include "edgeMesh.H"
-#include "regIOobject.H"
+#include "surfaceFeatures.H"
+#include "objectRegistry.H"
+#include "IOdictionary.H"
+#include "indexedOctree.H"
+#include "treeDataEdge.H"
+#include "pointIndexHit.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -58,11 +81,115 @@ public:
     //- Runtime type information
     TypeName("featureEdgeMesh");
 
+    enum pointStatus
+    {
+        CONVEX,     // Fully convex point (w.r.t normals)
+        CONCAVE,    // Fully concave point
+        MIXED,      // A point surrounded by both convex and concave edges
+        NONFEATURE  // Not a feature point
+    };
+
+    enum edgeStatus
+    {
+        EXTERNAL,   // "Convex" edge
+        INTERNAL,   // "Concave" edge
+        FLAT,       // Neither concave or convex, on a flat surface
+        OPEN,       // i.e. only connected to one face
+        MULTIPLE,   // Multiply connected (connected to more than two faces)
+        NONE        // Not a classified feature edge (consistency with
+                    // surfaceFeatures)
+    };
+
+private:
+
+    // Static data
+
+        //- Angular closeness tolerance for treating normals as the same
+        static scalar cosNormalAngleTol_;
+
+        //- Index of the start of the convex feature points - static as 0
+        static label convexStart_;
+
+        //- Index of the start of the external feature edges - static as 0
+        static label externalStart_;
+
+
+    // Private data
+
+        //- Index of the start of the concave feature points
+        label concaveStart_;
+
+        //- Index of the start of the mixed type feature points
+        label mixedStart_;
+
+        //- Index of the start of the non-feature points
+        label nonFeatureStart_;
+
+        //- Index of the start of the internal feature edges
+        label internalStart_;
+
+        //- Index of the start of the flat feature edges
+        label flatStart_;
+
+        //- Index of the start of the open feature edges
+        label openStart_;
+
+        //- Index of the start of the multiply-connected feature edges
+        label multipleStart_;
+
+        //- Normals of the features, to be referred to by index by both feature
+        //  points and edges, unsorted
+        vectorField normals_;
+
+        //- Flat and open edges require the direction of the edge
+        vectorField edgeDirections_;
+
+        //- Indices of the normals that are adjacent to the feature edges
+        labelListList edgeNormals_;
+
+        //- Indices of the normals that are adjacent to the feature points
+        labelListList featurePointNormals_;
+
+        //- Feature edges which are on the boundary between regions
+        labelList regionEdges_;
+
+        //- Search tree for all edges
+        mutable autoPtr<indexedOctree<treeDataEdge> > edgeTree_;
+
+        //- Individual search trees for each type of edge
+        mutable PtrList<indexedOctree<treeDataEdge> > edgeTreesByType_;
+
+
+    // Private Member Functions
+
+        //- Classify the type of feature point.  Requires valid stored member
+        //  data for edges and normals.
+        pointStatus classifyFeaturePoint(label ptI) const;
+
+        //- Classify the type of feature edge.  Requires face centre 0 to face
+        //  centre 1 vector to distinguish internal from external
+        edgeStatus classifyEdge
+        (
+            const List<vector>& norms,
+            const labelList& edNorms,
+            const vector& fC0tofC1
+        ) const;
+
+
+public:
+
+    // Static data
+
+        //- Number of possible point types (i.e. number of slices)
+        static label nPointTypes;
+
+        //- Number of possible feature edge types (i.e. number of slices)
+        static label nEdgeTypes;
 
     // Constructors
 
         //- Construct (read) given an IOobject
-        explicit featureEdgeMesh(const IOobject&);
+        featureEdgeMesh(const IOobject&);
 
         //- Construct as copy
         explicit featureEdgeMesh(const IOobject&, const featureEdgeMesh&);
@@ -75,14 +202,157 @@ public:
             const Xfer<edgeList>&
         );
 
-        //- Give precedence to the regIOobject write
-        using regIOobject::write;
+        //- Construct (read) given surfaceFeatures, an objectRegistry and a
+        //  fileName to write to.  Extracts, classifies and reorders the data
+        //  from surfaceFeatures.
+        featureEdgeMesh
+        (
+            const surfaceFeatures& sFeat,
+            const objectRegistry& obr,
+            const fileName& sFeatFileName
+        );
 
-        //- ReadData function required for regIOobject read operation
-        virtual bool readData(Istream&);
+        //- Construct from all components
+        featureEdgeMesh
+        (
+            const IOobject& io,
+            const pointField& pts,
+            const edgeList& eds,
+            label concaveStart,
+            label mixedStart,
+            label nonFeatureStart,
+            label internalStart,
+            label flatStart,
+            label openStart,
+            label multipleStart,
+            const vectorField& normals,
+            const vectorField& edgeDirections,
+            const labelListList& edgeNormals,
+            const labelListList& featurePointNormals,
+            const labelList& regionEdges
+        );
+
+
+    //- Destructor
+    ~featureEdgeMesh();
+
+
+    // Member Functions
+
+        // Find
+
+            //- Find nearest surface edge for the sample point.
+            void nearestFeatureEdge
+            (
+                const point& sample,
+                scalar searchDistSqr,
+                pointIndexHit& info
+            ) const;
+
+            //- Find nearest surface edge for each sample point.
+            void nearestFeatureEdge
+            (
+                const pointField& samples,
+                const scalarField& searchDistSqr,
+                List<pointIndexHit>& info
+            ) const;
+
+            //- Find the nearest point on each type of feature edge
+            void nearestFeatureEdgeByType
+            (
+                const point& sample,
+                const scalarField& searchDistSqr,
+                List<pointIndexHit>& info
+            ) const;
+
+        // Access
+
+            //- Return the index of the start of the convex feature points
+            inline label convexStart() const;
+
+            //- Return the index of the start of the concave feature points
+            inline label concaveStart() const;
+
+            //- Return the index of the start of the mixed type feature points
+            inline label mixedStart() const;
+
+            //- Return the index of the start of the non-feature points
+            inline label nonFeatureStart() const;
+
+            //- Return the index of the start of the external feature edges
+            inline label externalStart() const;
+
+            //- Return the index of the start of the internal feature edges
+            inline label internalStart() const;
+
+            //- Return the index of the start of the flat feature edges
+            inline label flatStart() const;
+
+            //- Return the index of the start of the open feature edges
+            inline label openStart() const;
+
+            //- Return the index of the start of the multiply-connected feature
+            //  edges
+            inline label multipleStart() const;
+
+            //- Return whether or not the point index is a feature point
+            inline bool featurePoint(label ptI) const;
 
-        //- WriteData function required for regIOobject write operation
-        virtual bool writeData(Ostream&) const;
+            //- Return the normals of the surfaces adjacent to the feature edges
+            //  and points
+            inline const vectorField& normals() const;
+
+            //- Return the edgeDirection vectors
+            inline const vectorField& edgeDirections() const;
+
+            //- Return the direction of edgeI, pointing away from ptI
+            inline vector edgeDirection(label edgeI, label ptI) const;
+
+            //- Return the indices of the normals that are adjacent to the
+            //  feature edges
+            inline const labelListList& edgeNormals() const;
+
+            //- Return the normal vectors for a given set of normal indices
+            inline vectorField edgeNormals(const labelList& edgeNormIs) const;
+
+            //- Return the normal vectors for a given edge
+            inline vectorField edgeNormals(label edgeI) const;
+
+            //- Return the indices of the normals that are adjacent to the
+            //  feature points
+            inline const labelListList& featurePointNormals() const;
+
+            //- Return the normal vectors for a given feature point
+            inline vectorField featurePointNormals(label ptI) const;
+
+            //- Return the feature edges which are on the boundary between
+            //  regions
+            inline const labelList& regionEdges() const;
+
+            //- Return the pointStatus of a specified point
+            inline pointStatus getPointStatus(label ptI) const;
+
+            //- Return the edgeStatus of a specified edge
+            inline edgeStatus getEdgeStatus(label edgeI) const;
+
+            //- Demand driven construction of octree for boundary edges
+            const indexedOctree<treeDataEdge>& edgeTree() const;
+
+            //- Demand driven construction of octree for boundary edges by type
+            const PtrList<indexedOctree<treeDataEdge> >&
+            edgeTreesByType() const;
+
+
+        // Write
+
+            //- Write all components of the featureEdgeMesh as obj files
+            void writeObj(const fileName& prefix) const;
+
+            //- Give precedence to the regIOobject write
+            using regIOobject::write;
+
+            //- WriteData function required for regIOobject write operation
+            virtual bool writeData(Ostream&) const;
 };
 
 
@@ -92,6 +362,10 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+#include "featureEdgeMeshI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 #endif
 
 // ************************************************************************* //
-- 
GitLab