From 48055b2deafd5628e500eb2a9245865bd8121081 Mon Sep 17 00:00:00 2001
From: mattijs <mattijs>
Date: Mon, 18 Feb 2019 13:29:01 +0000
Subject: [PATCH] ENH: distributedTriSurfaceMesh: auto-decomposition;
 inside/outside support

---
 .../algorithms/indexedOctree/indexedOctree.H  |    6 +
 .../mapPolyMesh/mapDistribute/mapDistribute.H |    4 +-
 .../searchableBox/searchableBox.H             |    5 +
 .../searchableCone/searchableCone.H           |    6 +
 .../searchableCylinder/searchableCylinder.H   |    6 +
 .../searchableDisk/searchableDisk.H           |    6 +
 .../searchableExtrudedCircle.H                |    6 +
 .../searchablePlane/searchablePlane.H         |    6 +
 .../searchablePlate/searchablePlate.H         |    6 +
 .../searchableRotatedBox.H                    |    6 +
 .../searchableSphere/searchableSphere.H       |    6 +
 .../searchableSurface/searchableSurface.H     |    8 +-
 .../searchableSurfaceCollection.H             |    6 +
 .../searchableSurfaceWithGaps.H               |    6 +
 .../triSurfaceMesh/triSurfaceMesh.C           |  381 +-
 .../triSurfaceMesh/triSurfaceMesh.H           |   24 +-
 .../distributedTriSurfaceMesh.C               | 3529 ++++++++++++++---
 .../distributedTriSurfaceMesh.H               |  136 +-
 .../simpleFoam/motorBike/Allrun               |    3 +-
 .../distributedTriSurfaceMesh/Allclean        |    9 +
 .../distributedTriSurfaceMesh/Allrun          |   53 +
 .../constant/triSurface/box_12.obj            |   34 +
 .../system/blockMeshDict                      |   98 +
 .../system/controlDict                        |   57 +
 .../system/decomposeParDict                   |   27 +
 .../system/fvSchemes                          |   57 +
 .../system/fvSolution                         |   69 +
 .../system/meshQualityDict                    |   21 +
 .../system/snappyHexMeshDict                  |  287 ++
 29 files changed, 4245 insertions(+), 623 deletions(-)
 create mode 100755 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allclean
 create mode 100755 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allrun
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/constant/triSurface/box_12.obj
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/blockMeshDict
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/controlDict
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/decomposeParDict
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSchemes
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSolution
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/meshQualityDict
 create mode 100644 tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/snappyHexMeshDict

diff --git a/src/OpenFOAM/algorithms/indexedOctree/indexedOctree.H b/src/OpenFOAM/algorithms/indexedOctree/indexedOctree.H
index 5aa2a3d5673..8b0e74a14b1 100644
--- a/src/OpenFOAM/algorithms/indexedOctree/indexedOctree.H
+++ b/src/OpenFOAM/algorithms/indexedOctree/indexedOctree.H
@@ -471,6 +471,12 @@ public:
                 return nodes_[0].bb_;
             }
 
+            //- Per node, per octant whether is fully inside/outside/mixed.
+            PackedList<2>& nodeTypes() const
+            {
+                return nodeTypes_;
+            }
+
 
         // Conversions for entries in subNodes_.
 
diff --git a/src/OpenFOAM/meshes/polyMesh/mapPolyMesh/mapDistribute/mapDistribute.H b/src/OpenFOAM/meshes/polyMesh/mapPolyMesh/mapDistribute/mapDistribute.H
index 209acdec40f..111a0633351 100644
--- a/src/OpenFOAM/meshes/polyMesh/mapPolyMesh/mapDistribute/mapDistribute.H
+++ b/src/OpenFOAM/meshes/polyMesh/mapPolyMesh/mapDistribute/mapDistribute.H
@@ -330,10 +330,10 @@ public:
         mapDistribute();
 
         //- Copy construct
-        mapDistribute(const mapDistribute& map);
+        explicit mapDistribute(const mapDistribute& map);
 
         //- Move construct
-        mapDistribute(mapDistribute&& map);
+        explicit mapDistribute(mapDistribute&& map);
 
         //- Move construct from components
         mapDistribute
diff --git a/src/meshTools/searchableSurfaces/searchableBox/searchableBox.H b/src/meshTools/searchableSurfaces/searchableBox/searchableBox.H
index 25c8b19b625..40bd7ac32f7 100644
--- a/src/meshTools/searchableSurfaces/searchableBox/searchableBox.H
+++ b/src/meshTools/searchableSurfaces/searchableBox/searchableBox.H
@@ -128,6 +128,11 @@ public:
         {
             return true;
         }
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::OUTSIDE;
+        }
 
         //- Range of local indices that can be returned.
         virtual label size() const
diff --git a/src/meshTools/searchableSurfaces/searchableCone/searchableCone.H b/src/meshTools/searchableSurfaces/searchableCone/searchableCone.H
index 9681ea3b383..d4151f806a9 100644
--- a/src/meshTools/searchableSurfaces/searchableCone/searchableCone.H
+++ b/src/meshTools/searchableSurfaces/searchableCone/searchableCone.H
@@ -186,6 +186,12 @@ public:
             return true;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::OUTSIDE;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchableCylinder/searchableCylinder.H b/src/meshTools/searchableSurfaces/searchableCylinder/searchableCylinder.H
index 5cae9b1b091..ada4f48f11d 100644
--- a/src/meshTools/searchableSurfaces/searchableCylinder/searchableCylinder.H
+++ b/src/meshTools/searchableSurfaces/searchableCylinder/searchableCylinder.H
@@ -158,6 +158,12 @@ public:
             return true;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::OUTSIDE;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchableDisk/searchableDisk.H b/src/meshTools/searchableSurfaces/searchableDisk/searchableDisk.H
index 8db94718b66..c5f815be4d5 100644
--- a/src/meshTools/searchableSurfaces/searchableDisk/searchableDisk.H
+++ b/src/meshTools/searchableSurfaces/searchableDisk/searchableDisk.H
@@ -141,6 +141,12 @@ public:
             return false;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::UNKNOWN;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchableExtrudedCircle/searchableExtrudedCircle.H b/src/meshTools/searchableSurfaces/searchableExtrudedCircle/searchableExtrudedCircle.H
index 30b354727c8..90f3a7af414 100644
--- a/src/meshTools/searchableSurfaces/searchableExtrudedCircle/searchableExtrudedCircle.H
+++ b/src/meshTools/searchableSurfaces/searchableExtrudedCircle/searchableExtrudedCircle.H
@@ -124,6 +124,12 @@ public:
             return true;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::OUTSIDE;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const;
 
diff --git a/src/meshTools/searchableSurfaces/searchablePlane/searchablePlane.H b/src/meshTools/searchableSurfaces/searchablePlane/searchablePlane.H
index 18733059fad..d0b5d3f444e 100644
--- a/src/meshTools/searchableSurfaces/searchablePlane/searchablePlane.H
+++ b/src/meshTools/searchableSurfaces/searchablePlane/searchablePlane.H
@@ -126,6 +126,12 @@ public:
             return false;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::UNKNOWN;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchablePlate/searchablePlate.H b/src/meshTools/searchableSurfaces/searchablePlate/searchablePlate.H
index ea64d05e2f3..1deb01edbc8 100644
--- a/src/meshTools/searchableSurfaces/searchablePlate/searchablePlate.H
+++ b/src/meshTools/searchableSurfaces/searchablePlate/searchablePlate.H
@@ -152,6 +152,12 @@ public:
             return false;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::UNKNOWN;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchableRotatedBox/searchableRotatedBox.H b/src/meshTools/searchableSurfaces/searchableRotatedBox/searchableRotatedBox.H
index 46b940b89aa..145df14f47e 100644
--- a/src/meshTools/searchableSurfaces/searchableRotatedBox/searchableRotatedBox.H
+++ b/src/meshTools/searchableSurfaces/searchableRotatedBox/searchableRotatedBox.H
@@ -131,6 +131,12 @@ public:
             return true;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::OUTSIDE;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchableSphere/searchableSphere.H b/src/meshTools/searchableSurfaces/searchableSphere/searchableSphere.H
index 096ea09d6ef..bac39bf0867 100644
--- a/src/meshTools/searchableSurfaces/searchableSphere/searchableSphere.H
+++ b/src/meshTools/searchableSurfaces/searchableSphere/searchableSphere.H
@@ -144,6 +144,12 @@ public:
             return true;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::OUTSIDE;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/searchableSurface/searchableSurface.H b/src/meshTools/searchableSurfaces/searchableSurface/searchableSurface.H
index 0e995c62b59..7ce9b4df3c3 100644
--- a/src/meshTools/searchableSurfaces/searchableSurface/searchableSurface.H
+++ b/src/meshTools/searchableSurfaces/searchableSurface/searchableSurface.H
@@ -176,13 +176,13 @@ public:
         }
 
         //- Return const reference to boundBox
-        const boundBox& bounds() const
+        virtual const boundBox& bounds() const
         {
             return bounds_;
         }
 
         //- Return non-const access to the boundBox to allow it to be set.
-        boundBox& bounds()
+        virtual boundBox& bounds()
         {
             return bounds_;
         }
@@ -194,6 +194,10 @@ public:
         //  This is false for the base class.
         virtual bool hasVolumeType() const;
 
+        //- If surface supports volume queries, what is type of points outside
+        //  bounds
+        virtual volumeType outsideVolumeType() const = 0;
+
         //- Range of local indices that can be returned
         virtual label size() const = 0;
 
diff --git a/src/meshTools/searchableSurfaces/searchableSurfaceCollection/searchableSurfaceCollection.H b/src/meshTools/searchableSurfaces/searchableSurfaceCollection/searchableSurfaceCollection.H
index e728e71d655..97b72ec2d7e 100644
--- a/src/meshTools/searchableSurfaces/searchableSurfaceCollection/searchableSurfaceCollection.H
+++ b/src/meshTools/searchableSurfaces/searchableSurfaceCollection/searchableSurfaceCollection.H
@@ -181,6 +181,12 @@ public:
             return false;
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return volumeType::UNKNOWN;
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const;
 
diff --git a/src/meshTools/searchableSurfaces/searchableSurfaceWithGaps/searchableSurfaceWithGaps.H b/src/meshTools/searchableSurfaces/searchableSurfaceWithGaps/searchableSurfaceWithGaps.H
index b93f3882674..7177a1c2a94 100644
--- a/src/meshTools/searchableSurfaces/searchableSurfaceWithGaps/searchableSurfaceWithGaps.H
+++ b/src/meshTools/searchableSurfaces/searchableSurfaceWithGaps/searchableSurfaceWithGaps.H
@@ -166,6 +166,12 @@ public:
             return surface().hasVolumeType();
         }
 
+        //- What is type of points outside bounds
+        virtual volumeType outsideVolumeType() const
+        {
+            return surface().outsideVolumeType();
+        }
+
         //- Range of local indices that can be returned.
         virtual label size() const
         {
diff --git a/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.C b/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.C
index 56e3520564c..e0677dd0e0a 100644
--- a/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.C
+++ b/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.C
@@ -71,7 +71,7 @@ Foam::fileName Foam::triSurfaceMesh::checkFile
 
 Foam::fileName Foam::triSurfaceMesh::relativeFilePath
 (
-    const regIOobject& io,
+    const IOobject& io,
     const fileName& f,
     const bool isGlobal
 )
@@ -96,7 +96,7 @@ Foam::fileName Foam::triSurfaceMesh::relativeFilePath
 
 Foam::fileName Foam::triSurfaceMesh::checkFile
 (
-    const regIOobject& io,
+    const IOobject& io,
     const dictionary& dict,
     const bool isGlobal
 )
@@ -156,6 +156,13 @@ bool Foam::triSurfaceMesh::addFaceToEdge
 
 bool Foam::triSurfaceMesh::isSurfaceClosed() const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::isSurfaceClosed:"
+            << " determining closedness for surface with "
+            << triSurface::size() << " triangles" << endl;
+    }
+
     const pointField& pts = triSurface::points();
 
     // Construct pointFaces. Let's hope surface has compact point
@@ -196,6 +203,11 @@ bool Foam::triSurfaceMesh::isSurfaceClosed() const
 
                 if (!okFace)
                 {
+                    if (debug)
+                    {
+                        Pout<< "triSurfaceMesh::isSurfaceClosed :"
+                            << " surface is open" << endl;
+                    }
                     return false;
                 }
             }
@@ -213,6 +225,11 @@ bool Foam::triSurfaceMesh::isSurfaceClosed() const
 
                 if (!okFace)
                 {
+                    if (debug)
+                    {
+                        Pout<< "triSurfaceMesh::isSurfaceClosed :"
+                            << " surface is open" << endl;
+                    }
                     return false;
                 }
             }
@@ -223,11 +240,21 @@ bool Foam::triSurfaceMesh::isSurfaceClosed() const
         {
             if (iter.val() != 2)
             {
+                if (debug)
+                {
+                    Pout<< "triSurfaceMesh::isSurfaceClosed :"
+                        << " surface is open" << endl;
+                }
                 return false;
             }
         }
     }
 
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::isSurfaceClosed :"
+            << " surface is closed" << endl;
+    }
     return true;
 }
 
@@ -358,7 +385,7 @@ Foam::triSurfaceMesh::triSurfaceMesh
 }
 
 
-Foam::triSurfaceMesh::triSurfaceMesh(const IOobject& io, const bool isGlobal)
+Foam::triSurfaceMesh::triSurfaceMesh(const IOobject& io, const readAction r)
 :
     // Find instance for triSurfaceMesh
     searchableSurface(io),
@@ -376,18 +403,79 @@ Foam::triSurfaceMesh::triSurfaceMesh(const IOobject& io, const bool isGlobal)
             false       // searchableSurface already registered under name
         )
     ),
-    triSurface
-    (
-        checkFile(static_cast<const searchableSurface&>(*this), isGlobal)
-    ),
+    triSurface(),       // construct null
     triSurfaceRegionSearch(static_cast<const triSurface&>(*this)),
     minQuality_(-1),
     surfaceClosed_(-1),
     outsideVolType_(volumeType::UNKNOWN)
 {
-    const pointField& pts = triSurface::points();
+    // Check IO flags
+    if (io.readOpt() != IOobject::NO_READ)
+    {
+        const bool searchGlobal(r == localOrGlobal || r == masterOnly);
+
+        const fileName actualFile
+        (
+            searchGlobal
+          ? io.globalFilePath(typeName)
+          : io.localFilePath(typeName)
+        );
+
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh(const IOobject& io) :"
+                << " loading surface " << io.objectPath()
+                << " local filePath:" << io.localFilePath(typeName)
+                << " from:" << actualFile << endl;
+        }
+
+        if (searchGlobal && Pstream::parRun())
+        {
+            // Check where surface was found
+            const fileName localFile(io.localFilePath(typeName));
+
+            if (r == masterOnly && (actualFile != localFile))
+            {
+                // Found undecomposed surface. Load on master only
+                if (Pstream::master())
+                {
+                    triSurface s2(actualFile);
+                    triSurface::transfer(s2);
+                }
+                Pstream::scatter(triSurface::patches());
+                if (debug)
+                {
+                    Pout<< "triSurfaceMesh(const IOobject& io) :"
+                        << " loaded triangles:" << triSurface::size() << endl;
+                }
+            }
+            else
+            {
+                // Read on all processors
+                triSurface s2(actualFile);
+                triSurface::transfer(s2);
+                if (debug)
+                {
+                    Pout<< "triSurfaceMesh(const IOobject& io) :"
+                        << " loaded triangles:" << triSurface::size() << endl;
+                }
+            }
+        }
+        else
+        {
+            // Read on all processors
+            triSurface s2(actualFile);
+            triSurface::transfer(s2);
+            if (debug)
+            {
+                Pout<< "triSurfaceMesh(const IOobject& io) :"
+                    << " loaded triangles:" << triSurface::size() << endl;
+            }
+        }
+    }
 
-    bounds() = boundBox(pts, isGlobal);
+    const pointField& pts = triSurface::points();
+    bounds() = boundBox(pts, false);
 }
 
 
@@ -395,7 +483,7 @@ Foam::triSurfaceMesh::triSurfaceMesh
 (
     const IOobject& io,
     const dictionary& dict,
-    const bool isGlobal
+    const readAction r
 )
 :
     searchableSurface(io),
@@ -413,26 +501,91 @@ Foam::triSurfaceMesh::triSurfaceMesh
             false       // searchableSurface already registered under name
         )
     ),
-    triSurface
-    (
-        checkFile(static_cast<const searchableSurface&>(*this), dict, isGlobal)
-    ),
+    triSurface(),       // construct null
     triSurfaceRegionSearch(static_cast<const triSurface&>(*this), dict),
     minQuality_(-1),
     surfaceClosed_(-1),
     outsideVolType_(volumeType::UNKNOWN)
 {
-    // Reading from supplied file name instead of objectPath/filePath
-    if (dict.readIfPresent("file", fName_, keyType::LITERAL))
+    // Check IO flags
+    if (io.readOpt() != IOobject::NO_READ)
     {
-        fName_ = relativeFilePath
+        const bool searchGlobal(r == localOrGlobal || r == masterOnly);
+
+        fileName actualFile
         (
-            static_cast<const searchableSurface&>(*this),
-            fName_,
-            isGlobal
+            searchGlobal
+          ? io.globalFilePath(typeName)
+          : io.localFilePath(typeName)
         );
+
+        // Reading from supplied file name instead of objectPath/filePath
+        if (dict.readIfPresent("file", fName_, keyType::LITERAL))
+        {
+            fName_ = relativeFilePath
+            (
+                static_cast<const searchableSurface&>(*this),
+                fName_,
+                searchGlobal
+            );
+            actualFile = fName_;
+        }
+
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh(const IOobject& io, const dictionary&) :"
+                << " loading surface " << io.objectPath()
+                << " local filePath:" << io.localFilePath(typeName)
+                << " from:" << actualFile << endl;
+        }
+
+        if (searchGlobal && Pstream::parRun())
+        {
+            // Check where surface was found
+            const fileName localFile(io.localFilePath(typeName));
+
+            if (r == masterOnly && (actualFile != localFile))
+            {
+                // Surface not loaded from processor directories -> undecomposed
+                // surface. Load on master only
+                if (Pstream::master())
+                {
+                    triSurface s2(actualFile);
+                    triSurface::transfer(s2);
+                }
+                Pstream::scatter(triSurface::patches());
+                if (debug)
+                {
+                    Pout<< "triSurfaceMesh(const IOobject& io) :"
+                        << " loaded triangles:" << triSurface::size() << endl;
+                }
+            }
+            else
+            {
+                // Read on all processors
+                triSurface s2(actualFile);
+                triSurface::transfer(s2);
+                if (debug)
+                {
+                    Pout<< "triSurfaceMesh(const IOobject& io) :"
+                        << " loaded triangles:" << triSurface::size() << endl;
+                }
+            }
+        }
+        else
+        {
+            // Read on all processors
+            triSurface s2(actualFile);
+            triSurface::transfer(s2);
+            if (debug)
+            {
+                Pout<< "triSurfaceMesh(const IOobject& io) :"
+                    << " loaded triangles:" << triSurface::size() << endl;
+            }
+        }
     }
 
+
     scalar scaleFactor = 0;
 
     // Allow rescaling of the surface points
@@ -445,8 +598,7 @@ Foam::triSurfaceMesh::triSurfaceMesh
     }
 
     const pointField& pts = triSurface::points();
-
-    bounds() = boundBox(pts, isGlobal);
+    bounds() = boundBox(pts, false);
 
     // Have optional minimum quality for normal calculation
     if (dict.readIfPresent("minQuality", minQuality_) && minQuality_ > 0)
@@ -468,7 +620,7 @@ Foam::triSurfaceMesh::~triSurfaceMesh()
 
 void Foam::triSurfaceMesh::clearOut()
 {
-    outsideVolType_ = volumeType::UNKNOWN;
+    // Do not clear closedness status
     triSurfaceRegionSearch::clearOut();
     edgeTree_.clear();
     triSurface::clearOut();
@@ -539,7 +691,14 @@ bool Foam::triSurfaceMesh::overlaps(const boundBox& bb) const
 
 void Foam::triSurfaceMesh::movePoints(const pointField& newPoints)
 {
-    outsideVolType_ = volumeType::UNKNOWN;
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::movePoints :"
+            << " moving at time " << objectRegistry::time().timeName()
+            << endl;
+    }
+
+    // Preserve topological point status (surfaceClosed_, outsideVolType_)
 
     // Update local information (instance, event number)
     searchableSurface::instance() = objectRegistry::time().timeName();
@@ -555,6 +714,10 @@ void Foam::triSurfaceMesh::movePoints(const pointField& newPoints)
     triSurface::movePoints(newPoints);
 
     bounds() = boundBox(triSurface::points(), false);
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::movePoints: finished moving points" << endl;
+    }
 }
 
 
@@ -563,6 +726,13 @@ Foam::triSurfaceMesh::edgeTree() const
 {
     if (edgeTree_.empty())
     {
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh::edgeTree :"
+                << " constructing tree for " << nEdges() - nInternalEdges()
+                << " boundary edges" << endl;
+        }
+
         // Boundary edges
         labelList bEdges
         (
@@ -592,6 +762,13 @@ Foam::triSurfaceMesh::edgeTree() const
             bb.max() += point(ROOTVSMALL, ROOTVSMALL, ROOTVSMALL);
         }
 
+
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh::edgeTree : "
+                << "calculating edge tree for bb:" << bb << endl;
+        }
+
         scalar oldTol = indexedOctree<treeDataEdge>::perturbTol();
         indexedOctree<treeDataEdge>::perturbTol() = tolerance();
 
@@ -614,6 +791,14 @@ Foam::triSurfaceMesh::edgeTree() const
         );
 
         indexedOctree<treeDataEdge>::perturbTol() = oldTol;
+
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh::edgeTree :"
+                << " finished constructing tree for "
+                << nEdges() - nInternalEdges()
+                << " boundary edges" << endl;
+        }
     }
 
     return *edgeTree_;
@@ -652,6 +837,40 @@ bool Foam::triSurfaceMesh::hasVolumeType() const
 }
 
 
+Foam::volumeType Foam::triSurfaceMesh::outsideVolumeType() const
+{
+    if (outsideVolType_ == volumeType::UNKNOWN)
+    {
+        // Get point outside bounds()
+        const point outsidePt(bounds().max() + 0.5*bounds().span());
+
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh::outsideVolumeType :"
+                << " triggering outsidePoint:" << outsidePt
+                << " orientation" << endl;
+        }
+
+        //outsideVolType_ = tree().shapes().getVolumeType(tree(), outsidePt);
+        // Note: do not use tree directly so e.g. distributedTriSurfaceMesh
+        //       has opportunity to intercept
+        List<volumeType> outsideVolTypes;
+        getVolumeType(pointField(1, outsidePt), outsideVolTypes);
+        outsideVolType_ = outsideVolTypes[0];
+
+        if (debug)
+        {
+            Pout<< "triSurfaceMesh::outsideVolumeType :"
+                << " finished outsidePoint:" << outsidePt
+                << " orientation:" << volumeType::names[outsideVolType_]
+                << endl;
+        }
+    }
+
+    return outsideVolType_;
+}
+
+
 void Foam::triSurfaceMesh::findNearest
 (
     const pointField& samples,
@@ -659,7 +878,21 @@ void Foam::triSurfaceMesh::findNearest
     List<pointIndexHit>& info
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findNearest :"
+            << " trying to find nearest for " << samples.size()
+            << " samples with max sphere "
+            << (samples.size() ? Foam::sqrt(max(nearestDistSqr)) : Zero)
+            << endl;
+    }
     triSurfaceSearch::findNearest(samples, nearestDistSqr, info);
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findNearest :"
+            << " finished trying to find nearest for " << samples.size()
+            << " samples" << endl;
+    }
 }
 
 
@@ -671,6 +904,14 @@ void Foam::triSurfaceMesh::findNearest
     List<pointIndexHit>& info
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findNearest :"
+            << " trying to find nearest and region for " << samples.size()
+            << " samples with max sphere "
+            << (samples.size() ? Foam::sqrt(max(nearestDistSqr)) : Zero)
+            << endl;
+    }
     triSurfaceRegionSearch::findNearest
     (
         samples,
@@ -678,6 +919,12 @@ void Foam::triSurfaceMesh::findNearest
         regionIndices,
         info
     );
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findNearest :"
+            << " finished trying to find nearest and region for "
+            << samples.size() << " samples" << endl;
+    }
 }
 
 
@@ -688,7 +935,19 @@ void Foam::triSurfaceMesh::findLine
     List<pointIndexHit>& info
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findLine :"
+            << " intersecting with "
+            << start.size() << " rays" << endl;
+    }
     triSurfaceSearch::findLine(start, end, info);
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findLine :"
+            << " finished intersecting with "
+            << start.size() << " rays" << endl;
+    }
 }
 
 
@@ -699,7 +958,19 @@ void Foam::triSurfaceMesh::findLineAny
     List<pointIndexHit>& info
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findLineAny :"
+            << " intersecting with "
+            << start.size() << " rays" << endl;
+    }
     triSurfaceSearch::findLineAny(start, end, info);
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findLineAny :"
+            << " finished intersecting with "
+            << start.size() << " rays" << endl;
+    }
 }
 
 
@@ -710,7 +981,19 @@ void Foam::triSurfaceMesh::findLineAll
     List<List<pointIndexHit>>& info
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findLineAll :"
+            << " intersecting with "
+            << start.size() << " rays" << endl;
+    }
     triSurfaceSearch::findLineAll(start, end, info);
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::findLineAll :"
+            << " finished intersecting with "
+            << start.size() << " rays" << endl;
+    }
 }
 
 
@@ -720,6 +1003,12 @@ void Foam::triSurfaceMesh::getRegion
     labelList& region
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::getRegion :"
+            << " getting region for "
+            << info.size() << " triangles" << endl;
+    }
     region.setSize(info.size());
     forAll(info, i)
     {
@@ -732,6 +1021,12 @@ void Foam::triSurfaceMesh::getRegion
             region[i] = -1;
         }
     }
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::getRegion :"
+            << " finished getting region for "
+            << info.size() << " triangles" << endl;
+    }
 }
 
 
@@ -741,6 +1036,13 @@ void Foam::triSurfaceMesh::getNormal
     vectorField& normal
 ) const
 {
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::getNormal :"
+            << " getting normal for "
+            << info.size() << " triangles" << endl;
+    }
+
     const triSurface& s = *this;
     const pointField& pts = s.points();
 
@@ -803,6 +1105,12 @@ void Foam::triSurfaceMesh::getNormal
             }
         }
     }
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::getNormal :"
+            << " finished getting normal for "
+            << info.size() << " triangles" << endl;
+    }
 }
 
 
@@ -835,6 +1143,12 @@ void Foam::triSurfaceMesh::setField(const labelList& values)
         // Store field on triMesh
         fldPtr->store();
     }
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::setField :"
+            << " finished setting field for "
+            << values.size() << " triangles" << endl;
+    }
 }
 
 
@@ -860,6 +1174,12 @@ void Foam::triSurfaceMesh::getField
             }
         }
     }
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::setField :"
+            << " finished getting field for "
+            << info.size() << " triangles" << endl;
+    }
 }
 
 
@@ -872,6 +1192,13 @@ void Foam::triSurfaceMesh::getVolumeType
     const scalar oldTol = indexedOctree<treeDataTriSurface>::perturbTol();
     indexedOctree<treeDataTriSurface>::perturbTol() = tolerance();
 
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::getVolumeType :"
+            << " finding orientation for " << points.size()
+            << " samples" << endl;
+    }
+
     volType.setSize(points.size());
 
     forAll(points, pointi)
@@ -900,6 +1227,12 @@ void Foam::triSurfaceMesh::getVolumeType
     }
 
     indexedOctree<treeDataTriSurface>::perturbTol() = oldTol;
+    if (debug)
+    {
+        Pout<< "triSurfaceMesh::getVolumeType :"
+            << " finished finding orientation for " << points.size()
+            << " samples" << endl;
+    }
 }
 
 
diff --git a/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.H b/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.H
index abce60f6383..b07da194aef 100644
--- a/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.H
+++ b/src/meshTools/searchableSurfaces/triSurfaceMesh/triSurfaceMesh.H
@@ -52,8 +52,8 @@ SourceFiles
 #ifndef triSurfaceMesh_H
 #define triSurfaceMesh_H
 
-#include "treeBoundBox.H"
 #include "searchableSurface.H"
+#include "treeBoundBox.H"
 #include "objectRegistry.H"
 #include "indexedOctree.H"
 #include "treeDataTriSurface.H"
@@ -79,6 +79,8 @@ class triSurfaceMesh
     public triSurface,
     public triSurfaceRegionSearch
 {
+protected:
+
     // Private member data
 
         //- Supplied fileName override
@@ -110,7 +112,7 @@ class triSurfaceMesh
         //  IOobject
         static fileName relativeFilePath
         (
-            const regIOobject&,
+            const IOobject&,
             const fileName&,
             const bool isGlobal
         );
@@ -118,7 +120,7 @@ class triSurfaceMesh
         //- Return fileName to load IOobject from. Optional override of fileName
         static fileName checkFile
         (
-            const regIOobject&,
+            const IOobject&,
             const dictionary&,
             const bool isGlobal
         );
@@ -179,15 +181,22 @@ public:
 
 
         // Special constructors for use by distributedTriSurface. File search
-        // status (local/global) supplied.
+        // method supplied:
 
-            triSurfaceMesh(const IOobject& io, const bool isGlobal);
+            enum readAction
+            {
+                localOnly,      // load from (processor-)local file only
+                localOrGlobal,  // load from (processor-)local or global file
+                masterOnly      // as localOrGlobal but only load on master
+            };
+
+            triSurfaceMesh(const IOobject& io, const readAction r);
 
             triSurfaceMesh
             (
                 const IOobject& io,
                 const dictionary& dict,
-                const bool isGlobal
+                const readAction r
             );
 
 
@@ -216,6 +225,9 @@ public:
             //- Whether supports volume type (below) - i.e. whether is closed.
             virtual bool hasVolumeType() const;
 
+            //- If surface is closed, what is type of points outside bounds
+            virtual volumeType outsideVolumeType() const;
+
             //- Range of local indices that can be returned.
             virtual label size() const
             {
diff --git a/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.C b/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.C
index 655c934f7ad..3613206a84b 100644
--- a/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.C
+++ b/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.C
@@ -41,6 +41,8 @@ License
 #include "bitSet.H"
 #include "PatchTools.H"
 #include "OBJstream.H"
+#include "labelBits.H"
+#include "profiling.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -53,6 +55,89 @@ namespace Foam
         distributedTriSurfaceMesh,
         dict
     );
+
+
+    //- Combine operator for volume types
+    class volumeCombineOp
+    {
+        public:
+        void operator()(volumeType& x, const volumeType& y) const
+        {
+            if (x == volumeType::MIXED || y == volumeType::MIXED)
+            {
+                FatalErrorInFunction << "Illegal volume type "
+                    << volumeType::names[x]
+                    << "," << volumeType::names[y] << exit(FatalError);
+            }
+            else
+            {
+                switch (x)
+                {
+                    case volumeType::UNKNOWN:
+                    {
+                        if (y == volumeType::INSIDE || y == volumeType::OUTSIDE)
+                        {
+                            x = y;
+                        }
+                    }
+                    break;
+                    case volumeType::INSIDE:
+                    {
+                        if (y == volumeType::OUTSIDE)
+                        {
+                            FatalErrorInFunction << "Conflicting volume types "
+                                << volumeType::names[x] << ","
+                                << volumeType::names[y] << exit(FatalError);
+                        }
+                    }
+                    break;
+                    case volumeType::OUTSIDE:
+                    {
+                        if (y == volumeType::INSIDE)
+                        {
+                            FatalErrorInFunction << "Conflicting volume types "
+                                << volumeType::names[x] << ","
+                                << volumeType::names[y] << exit(FatalError);
+                        }
+                    }
+                    break;
+                    case volumeType::MIXED:
+                    break;
+                }
+            }
+        }
+    };
+
+
+    //- Combine operator for nearest
+    typedef Tuple2<pointIndexHit, scalar> nearestAndDist;
+
+    const nearestAndDist nearestZero
+    (
+        nearestAndDist
+        (
+            pointIndexHit(),
+            -GREAT
+        )
+    );
+    class nearestEqOp
+    {
+        public:
+        void operator()(nearestAndDist& x, const nearestAndDist& y) const
+        {
+            if (x.first().hit())
+            {
+                if (y.first().hit() && y.second() < x.second())
+                {
+                    x = y;
+                }
+            }
+            else if (y.first().hit())
+            {
+                x = y;
+            }
+        }
+    };
 }
 
 
@@ -64,27 +149,181 @@ Foam::distributedTriSurfaceMesh::distributionTypeNames_
 ({
     { distributionType::FOLLOW, "follow" },
     { distributionType::INDEPENDENT, "independent" },
-    { distributionType::FROZEN, "frozen" },
+    { distributionType::DISTRIBUTED, "distributed" },
+    { distributionType::FROZEN, "frozen" }
 });
 
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
+Foam::word Foam::distributedTriSurfaceMesh::findLocalInstance
+(
+    const IOobject& io
+)
+{
+    // Modified findInstance which also looks in parent directory
+    word instance
+    (
+        io.time().findInstance
+        (
+            io.local(),
+            word::null,
+            IOobject::READ_IF_PRESENT
+        )
+    );
+
+    if (instance.size())
+    {
+        return instance;
+    }
+
+
+    // Replicate findInstance operation but now on parent directory
+
+    // Search in parent directory
+    fileName parentDir =
+        io.rootPath()/io.time().globalCaseName()
+       /io.instance()/io.db().dbDir()/io.local()/io.name();
+
+    if (fileHandler().isDir(parentDir))
+    {
+        return io.instance();
+    }
+
+    instantList ts = io.time().times();
+    label instanceI;
+
+    const scalar startValue = io.time().timeOutputValue();
+
+    for (instanceI = ts.size()-1; instanceI >= 0; --instanceI)
+    {
+        if (ts[instanceI].value() <= startValue)
+        {
+            break;
+        }
+    }
+
+    // continue searching from here
+    for (; instanceI >= 0; --instanceI)
+    {
+        // Shortcut: if actual directory is the timeName we've already tested it
+        if (ts[instanceI].name() == io.instance())
+        {
+            continue;
+        }
+
+        fileName parentDir =
+            io.rootPath()/io.time().globalCaseName()
+           /ts[instanceI].name()/io.db().dbDir()/io.local()/io.name();
+
+        if (fileHandler().isDir(parentDir))
+        {
+            return ts[instanceI].name();
+        }
+    }
+
+    // times() usually already includes the constant() so would have been
+    // checked above. Re-test if
+    // - times() is empty. Sometimes this can happen (e.g. decomposePar with
+    //   collated)
+    // - times()[0] is not constant
+    if (!ts.size() || ts[0].name() != io.time().constant())
+    {
+        // Note. This needs to be a hard-coded constant, rather than the
+        // constant function of the time, because the latter points to
+        // the case constant directory in parallel cases
+
+        fileName parentDir =
+            io.rootPath()/io.time().globalCaseName()
+           /io.time().constant()/io.db().dbDir()/io.local()/io.name();
+
+        if (fileHandler().isDir(parentDir))
+        {
+            return io.time().constant();
+        }
+    }
+
+    FatalErrorInFunction
+        << "Cannot find directory " << io.local() << " in times " << ts
+        << exit(FatalError);
+
+    return word::null;
+}
+
+
 // Read my additional data from the dictionary
 bool Foam::distributedTriSurfaceMesh::read()
 {
     // Get bb of all domains.
     procBb_.setSize(Pstream::nProcs());
 
-    procBb_[Pstream::myProcNo()] = List<treeBoundBox>(dict_.lookup("bounds"));
-    Pstream::gatherList(procBb_);
-    Pstream::scatterList(procBb_);
+    if (dict_.empty())
+    {
+        // Did not find the distributed version; assume master has loaded the
+        // triSurfaceMesh version. Make up some settings.
+
+        const boundBox& localBb = triSurfaceMesh::bounds();
+
+        procBb_[Pstream::myProcNo()] =
+            treeBoundBoxList(1, treeBoundBox(localBb));
+        Pstream::gatherList(procBb_);
+        Pstream::scatterList(procBb_);
+
+        dict_.add("bounds", procBb_[Pstream::myProcNo()]);
+
+        // Wanted distribution type
+        distType_ = DISTRIBUTED;    //INDEPENDENT;
+        dict_.add("distributionType", distributionTypeNames_[distType_]);
+
+        // Merge distance
+        mergeDist_ = SMALL;
+        dict_.add("mergeDistance", mergeDist_);
+
+        // Force underlying triSurfaceMesh to calculate volume type
+        // (is topological walk; does not construct tree)
+        surfaceClosed_ = triSurfaceMesh::hasVolumeType();
+        Pstream::scatter(surfaceClosed_);
+        dict_.add("closed", surfaceClosed_);
+
+        // Delay calculating outside vol type since constructs tree. Is ok
+        // after distributing since then local surfaces much smaller
+        //outsideVolType_ = volumeType::UNKNOWN;
+        //if (surfaceClosed_)
+        //{
+        //    point outsidePt(localBb.max()+localBb.midpoint());
+        //    List<volumeType> outsideVolTypes;
+        //    triSurfaceMesh::getVolumeType
+        //    (
+        //        pointField(1, outsidePt),
+        //        outsideVolTypes
+        //    );
+        //    outsideVolType_ = outsideVolTypes[0];
+        //}
+        //dict_.add("outsideVolumeType", volumeType::names[outsideVolType_]);
+    }
+    else
+    {
+        procBb_[Pstream::myProcNo()] =
+            List<treeBoundBox>(dict_.lookup("bounds"));
+        Pstream::gatherList(procBb_);
+        Pstream::scatterList(procBb_);
 
-    // Distribution type
-    distType_ = distributionTypeNames_.get("distributionType", dict_);
+        // Wanted distribution type
+        distType_ = distributionTypeNames_.lookup("distributionType", dict_);
 
-    // Merge distance
-    dict_.readEntry("mergeDistance", mergeDist_);
+        // Merge distance
+        mergeDist_ = readScalar(dict_.lookup("mergeDistance"));
+
+        // Distribution type
+        surfaceClosed_ = dict_.lookupOrDefault<bool>("closed", false);
+
+        outsideVolType_ = volumeType::UNKNOWN;
+        word volType;
+        if (dict_.readIfPresent<word>("outsideVolumeType", volType))
+        {
+            outsideVolType_ = volumeType::names[volType];
+        }
+    }
 
     return true;
 }
@@ -220,7 +459,7 @@ void Foam::distributedTriSurfaceMesh::distributeSegment
             const treeBoundBox& bb = bbs[bbi];
 
             // Scheme a: any processor that intersects the segment gets
-            // the segment.
+            // the whole segment.
 
             // Intersection point
             point clipPt;
@@ -278,6 +517,17 @@ Foam::distributedTriSurfaceMesh::distributeSegments
         // Per processor indices into allSegments to send
         List<DynamicList<label>> dynSendMap(Pstream::nProcs());
 
+        // Pre-size
+        forAll(dynSendMap, proci)
+        {
+            dynSendMap[proci].reserve
+            (
+                (proci == Pstream::myProcNo())
+              ? start.size()
+              : start.size()/Pstream::nProcs()
+            );
+        }
+
         forAll(start, segmenti)
         {
             distributeSegment
@@ -304,49 +554,7 @@ Foam::distributedTriSurfaceMesh::distributeSegments
         allSegmentMap.transfer(dynAllSegmentMap);
     }
 
-
-    // Send over how many i need to receive.
-    labelListList sendSizes(Pstream::nProcs());
-    sendSizes[Pstream::myProcNo()].setSize(Pstream::nProcs());
-    forAll(sendMap, proci)
-    {
-        sendSizes[Pstream::myProcNo()][proci] = sendMap[proci].size();
-    }
-    Pstream::gatherList(sendSizes);
-    Pstream::scatterList(sendSizes);
-
-
-    // Determine order of receiving
-    labelListList constructMap(Pstream::nProcs());
-
-    // My local segments first
-    constructMap[Pstream::myProcNo()] = identity
-    (
-        sendMap[Pstream::myProcNo()].size()
-    );
-
-    label segmenti = constructMap[Pstream::myProcNo()].size();
-    forAll(constructMap, proci)
-    {
-        if (proci != Pstream::myProcNo())
-        {
-            // What i need to receive is what other processor is sending to me.
-            label nRecv = sendSizes[proci][Pstream::myProcNo()];
-            constructMap[proci].setSize(nRecv);
-
-            for (label i = 0; i < nRecv; i++)
-            {
-                constructMap[proci][i] = segmenti++;
-            }
-        }
-    }
-
-    return autoPtr<mapDistribute>::New
-    (
-        segmenti, // size after construction
-        std::move(sendMap),
-        std::move(constructMap)
-    );
+    return autoPtr<mapDistribute>::New(std::move(sendMap));
 }
 
 
@@ -358,6 +566,14 @@ void Foam::distributedTriSurfaceMesh::findLine
     List<pointIndexHit>& info
 ) const
 {
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::findLine :"
+            << " intersecting with "
+            << start.size() << " rays" << endl;
+    }
+    addProfiling(findLine, "distributedTriSurfaceMesh::findLine");
+
     const indexedOctree<treeDataTriSurface>& octree = tree();
 
     // Initialise
@@ -367,9 +583,18 @@ void Foam::distributedTriSurfaceMesh::findLine
         info[i].setMiss();
     }
 
-    if (!Pstream::parRun())
+    // Important:force synchronised construction of indexing
+    const globalIndex& triIndexer = globalTris();
+
+
+    // Do any local queries
+    // ~~~~~~~~~~~~~~~~~~~~
+
+    label nLocal = 0;
+
+    forAll(start, i)
     {
-        forAll(start, i)
+        if (isLocal(procBb_[Pstream::myProcNo()], start[i], end[i]))
         {
             if (nearestIntersection)
             {
@@ -379,150 +604,124 @@ void Foam::distributedTriSurfaceMesh::findLine
             {
                 info[i] = octree.findLineAny(start[i], end[i]);
             }
-        }
-    }
-    else
-    {
-        // Important:force synchronised construction of indexing
-        const globalIndex& triIndexer = globalTris();
 
-
-        // Do any local queries
-        // ~~~~~~~~~~~~~~~~~~~~
-
-        label nLocal = 0;
-
-        forAll(start, i)
-        {
-            if (isLocal(procBb_[Pstream::myProcNo()], start[i], end[i]))
+            if (info[i].hit())
             {
-                if (nearestIntersection)
-                {
-                    info[i] = octree.findLine(start[i], end[i]);
-                }
-                else
-                {
-                    info[i] = octree.findLineAny(start[i], end[i]);
-                }
-
-                if (info[i].hit())
-                {
-                    info[i].setIndex(triIndexer.toGlobal(info[i].index()));
-                }
-                nLocal++;
+                info[i].setIndex(triIndexer.toGlobal(info[i].index()));
             }
+            nLocal++;
         }
+    }
 
 
-        if
-        (
-            returnReduce(nLocal, sumOp<label>())
-          < returnReduce(start.size(), sumOp<label>())
-        )
-        {
-            // Not all can be resolved locally. Build segments and map,
-            // send over segments, do intersections, send back and merge.
+    if
+    (
+        returnReduce(nLocal, sumOp<label>())
+      < returnReduce(start.size(), sumOp<label>())
+    )
+    {
+        // Not all can be resolved locally. Build segments and map,
+        // send over segments, do intersections, send back and merge.
 
 
-            // Construct queries (segments)
-            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        // Construct queries (segments)
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-            // Segments to test
-            List<segment> allSegments(start.size());
-            // Original index of segment
-            labelList allSegmentMap(start.size());
+        // Segments to test
+        List<segment> allSegments(start.size());
+        // Original index of segment
+        labelList allSegmentMap(start.size());
 
-            const autoPtr<mapDistribute> mapPtr
+        const autoPtr<mapDistribute> mapPtr
+        (
+            distributeSegments
             (
-                distributeSegments
-                (
-                    start,
-                    end,
-                    allSegments,
-                    allSegmentMap
-                )
-            );
-            const mapDistribute& map = mapPtr();
+                start,
+                end,
+                allSegments,
+                allSegmentMap
+            )
+        );
+        const mapDistribute& map = mapPtr();
 
-            label nOldAllSegments = allSegments.size();
+        label nOldAllSegments = allSegments.size();
 
 
-            // Exchange the segments
-            // ~~~~~~~~~~~~~~~~~~~~~
+        // Exchange the segments
+        // ~~~~~~~~~~~~~~~~~~~~~
 
-            map.distribute(allSegments);
+        map.distribute(allSegments);
 
 
-            // Do tests i need to do
-            // ~~~~~~~~~~~~~~~~~~~~~
+        // Do tests i need to do
+        // ~~~~~~~~~~~~~~~~~~~~~
 
-            // Intersections
-            List<pointIndexHit> intersections(allSegments.size());
+        // Intersections
+        List<pointIndexHit> intersections(allSegments.size());
 
-            forAll(allSegments, i)
+        forAll(allSegments, i)
+        {
+            if (nearestIntersection)
             {
-                if (nearestIntersection)
-                {
-                    intersections[i] = octree.findLine
-                    (
-                        allSegments[i].first(),
-                        allSegments[i].second()
-                    );
-                }
-                else
-                {
-                    intersections[i] = octree.findLineAny
-                    (
-                        allSegments[i].first(),
-                        allSegments[i].second()
-                    );
-                }
+                intersections[i] = octree.findLine
+                (
+                    allSegments[i].first(),
+                    allSegments[i].second()
+                );
+            }
+            else
+            {
+                intersections[i] = octree.findLineAny
+                (
+                    allSegments[i].first(),
+                    allSegments[i].second()
+                );
+            }
 
-                // Convert triangle index to global numbering
-                if (intersections[i].hit())
-                {
-                    intersections[i].setIndex
-                    (
-                        triIndexer.toGlobal(intersections[i].index())
-                    );
-                }
+            // Convert triangle index to global numbering
+            if (intersections[i].hit())
+            {
+                intersections[i].setIndex
+                (
+                    triIndexer.toGlobal(intersections[i].index())
+                );
             }
+        }
 
 
-            // Exchange the intersections (opposite to segments)
-            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        // Exchange the intersections (opposite to segments)
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-            map.reverseDistribute(nOldAllSegments, intersections);
+        map.reverseDistribute(nOldAllSegments, intersections);
 
 
-            // Extract the hits
-            // ~~~~~~~~~~~~~~~~
+        // Extract the hits
+        // ~~~~~~~~~~~~~~~~
 
-            forAll(intersections, i)
-            {
-                const pointIndexHit& allInfo = intersections[i];
-                label segmenti = allSegmentMap[i];
-                pointIndexHit& hitInfo = info[segmenti];
+        forAll(intersections, i)
+        {
+            const pointIndexHit& allInfo = intersections[i];
+            label segmenti = allSegmentMap[i];
+            pointIndexHit& hitInfo = info[segmenti];
 
-                if (allInfo.hit())
+            if (allInfo.hit())
+            {
+                if (!hitInfo.hit())
+                {
+                    // No intersection yet so take this one
+                    hitInfo = allInfo;
+                }
+                else if (nearestIntersection)
                 {
-                    if (!hitInfo.hit())
+                    // Nearest intersection
+                    if
+                    (
+                        magSqr(allInfo.hitPoint()-start[segmenti])
+                      < magSqr(hitInfo.hitPoint()-start[segmenti])
+                    )
                     {
-                        // No intersection yet so take this one
                         hitInfo = allInfo;
                     }
-                    else if (nearestIntersection)
-                    {
-                        // Nearest intersection
-                        if
-                        (
-                            magSqr(allInfo.hitPoint()-start[segmenti])
-                          < magSqr(hitInfo.hitPoint()-start[segmenti])
-                        )
-                        {
-                            hitInfo = allInfo;
-                        }
-                    }
                 }
             }
         }
@@ -530,6 +729,24 @@ void Foam::distributedTriSurfaceMesh::findLine
 }
 
 
+void Foam::distributedTriSurfaceMesh::convertTriIndices
+(
+    List<pointIndexHit>& info
+) const
+{
+    // Important:force synchronised construction of indexing
+    const globalIndex& triIndexer = globalTris();
+
+    for (pointIndexHit& pi : info)
+    {
+        if (pi.hit())
+        {
+            pi.setIndex(triIndexer.toGlobal(pi.index()));
+        }
+    }
+}
+
+
 // Exchanges indices to the processor they come from.
 // - calculates exchange map
 // - uses map to calculate local triangle index
@@ -540,6 +757,10 @@ Foam::distributedTriSurfaceMesh::calcLocalQueries
     labelList& triangleIndex
 ) const
 {
+    // Note: does not filter duplicate queries so a triangle that gets requested
+    //       from more than one processor also get local queried more than
+    //       once.
+
     triangleIndex.setSize(info.size());
 
     const globalIndex& triIndexer = globalTris();
@@ -587,68 +808,147 @@ Foam::distributedTriSurfaceMesh::calcLocalQueries
     }
 
 
-    // Send over how many i need to receive
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    // Pack into distribution map
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    labelListList sendSizes(Pstream::nProcs());
-    sendSizes[Pstream::myProcNo()].setSize(Pstream::nProcs());
-    forAll(sendMap, proci)
-    {
-        sendSizes[Pstream::myProcNo()][proci] = sendMap[proci].size();
-    }
-    Pstream::gatherList(sendSizes);
-    Pstream::scatterList(sendSizes);
+    autoPtr<mapDistribute> mapPtr(new mapDistribute(std::move(sendMap)));
 
 
-    // Determine receive map
-    // ~~~~~~~~~~~~~~~~~~~~~
+    // Send over queries
+    // ~~~~~~~~~~~~~~~~~
 
-    labelListList constructMap(Pstream::nProcs());
+    mapPtr().distribute(triangleIndex);
+
+    return mapPtr;
+}
 
-    // My local segments first
-    constructMap[Pstream::myProcNo()] = identity
-    (
-        sendMap[Pstream::myProcNo()].size()
-    );
 
-    label segmenti = constructMap[Pstream::myProcNo()].size();
-    forAll(constructMap, proci)
+bool Foam::distributedTriSurfaceMesh::contains
+(
+    const List<treeBoundBox>& bbs,
+    const point& sample
+) const
+{
+    forAll(bbs, bbi)
     {
-        if (proci != Pstream::myProcNo())
+        if (bbs[bbi].contains(sample))
         {
-            // What i need to receive is what other processor is sending to me.
-            label nRecv = sendSizes[proci][Pstream::myProcNo()];
-            constructMap[proci].setSize(nRecv);
-
-            for (label i = 0; i < nRecv; i++)
-            {
-                constructMap[proci][i] = segmenti++;
-            }
+            return true;
         }
     }
+    return false;
+}
 
 
-    // Pack into distribution map
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~
+Foam::Tuple2<Foam::label, Foam::scalar>
+Foam::distributedTriSurfaceMesh::findBestProcs
+(
+    const point& centre,
+    const scalar radiusSqr,
+    boolList& procContains,
+    boolList& procOverlaps,
+    label& minProci
+) const
+{
+    // Find processors:
+    // - that contain the centre
+    // - or overlap the search sphere
 
-    autoPtr<mapDistribute> mapPtr
-    (
-        new mapDistribute
-        (
-            segmenti, // size after construction
-            std::move(sendMap),
-            std::move(constructMap)
-        )
-    );
-    const mapDistribute& map = mapPtr();
+    procContains.setSize(Pstream::nProcs());
+    procContains = false;
 
+    procOverlaps.setSize(Pstream::nProcs());
+    procOverlaps = false;
 
-    // Send over queries
-    // ~~~~~~~~~~~~~~~~~
+    minProci = -1;
 
-    map.distribute(triangleIndex);
+    scalar minDistSqr = radiusSqr;
 
-    return mapPtr;
+    label nContain = 0;
+    forAll(procBb_, proci)
+    {
+        const List<treeBoundBox>& bbs = procBb_[proci];
+
+        forAll(bbs, bbi)
+        {
+            if (bbs[bbi].contains(centre))
+            {
+                // We found a bb that contains the centre. There must be
+                // a triangle inside (since otherwise the bb would never
+                // have been created).
+                if (!procContains[proci])
+                {
+                    procContains[proci] = true;
+                    nContain++;
+                }
+                // Minimum search distance to find the triangle
+                point near, far;
+                bbs[bbi].calcExtremities(centre, near, far);
+                minDistSqr = min(minDistSqr, magSqr(centre-far));
+            }
+        }
+    }
+
+    if (nContain > 0)
+    {
+        return Tuple2<label, scalar>(nContain, minDistSqr);
+    }
+    else
+    {
+        scalar maxDistSqr = radiusSqr;
+
+        // Pass 1: find box with nearest minimum distance. Store its maximum
+        //         extent as well. Since box will always contain a triangle
+        //         this guarantees at least one hit.
+        forAll(procBb_, proci)
+        {
+            const List<treeBoundBox>& bbs = procBb_[proci];
+
+            forAll(bbs, bbi)
+            {
+                if (bbs[bbi].overlaps(centre, radiusSqr))
+                {
+                    point near, far;
+                    bbs[bbi].calcExtremities(centre, near, far);
+
+                    scalar d2 = magSqr(centre-near);
+                    if (d2 < minDistSqr)
+                    {
+                        minDistSqr = d2;
+                        maxDistSqr = min(radiusSqr, magSqr(centre-far));
+                        minProci = proci;
+                    }
+                }
+            }
+        }
+
+        label nOverlap = 0;
+        if (minProci >= 0)
+        {
+            // Pass 2. Find all bb in range minDistSqr..maxDistSqr
+
+            procOverlaps[minProci] = true;
+            nOverlap++;
+
+            forAll(procBb_, proci)
+            {
+                if (proci != minProci)
+                {
+                    const List<treeBoundBox>& bbs = procBb_[proci];
+                    forAll(bbs, bbi)
+                    {
+                        if (bbs[bbi].overlaps(centre, maxDistSqr))
+                        {
+                            procOverlaps[proci] = true;
+                            nOverlap++;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return Tuple2<label, scalar>(nOverlap, maxDistSqr);
+    }
 }
 
 
@@ -686,6 +986,7 @@ Foam::label Foam::distributedTriSurfaceMesh::calcOverlappingProcs
 Foam::autoPtr<Foam::mapDistribute>
 Foam::distributedTriSurfaceMesh::calcLocalQueries
 (
+    const bool includeLocalProcessor,
     const pointField& centres,
     const scalarField& radiusSqr,
 
@@ -708,6 +1009,17 @@ Foam::distributedTriSurfaceMesh::calcLocalQueries
         // Per processor indices into allSegments to send
         List<DynamicList<label>> dynSendMap(Pstream::nProcs());
 
+        // Pre-size
+        forAll(dynSendMap, proci)
+        {
+            dynSendMap[proci].reserve
+            (
+                (proci == Pstream::myProcNo())
+              ? centres.size()
+              : centres.size()/Pstream::nProcs()
+            );
+        }
+
         // Work array - whether processor bb overlaps the bounding sphere.
         boolList procBbOverlaps(Pstream::nProcs());
 
@@ -723,7 +1035,14 @@ Foam::distributedTriSurfaceMesh::calcLocalQueries
 
             forAll(procBbOverlaps, proci)
             {
-                if (proci != Pstream::myProcNo() && procBbOverlaps[proci])
+                if
+                (
+                    procBbOverlaps[proci]
+                 && (
+                        includeLocalProcessor
+                     || proci != Pstream::myProcNo()
+                    )
+                )
                 {
                     dynSendMap[proci].append(dynAllCentres.size());
                     dynAllSegmentMap.append(centrei);
@@ -746,229 +1065,1095 @@ Foam::distributedTriSurfaceMesh::calcLocalQueries
         allSegmentMap.transfer(dynAllSegmentMap);
     }
 
+    return autoPtr<mapDistribute>::New(std::move(sendMap));
+}
+
+
+Foam::volumeType Foam::distributedTriSurfaceMesh::edgeSide
+(
+    const point& sample,
+    const point& nearestPoint,
+    const label face0,
+    const label face1
+) const
+{
+    const triSurface& surf = static_cast<const triSurface&>(*this);
+    const pointField& points = surf.points();
+
+    // Compare to bisector. This is actually correct since edge is
+    // nearest so there is a knife-edge.
+
+    //const vectorField& faceNormals = surf.faceNormals();
+    //vector n = faceNormals[face0] + faceNormals[face1];
+    vector n = surf[face0].unitNormal(points)+surf[face1].unitNormal(points);
 
-    // Send over how many i need to receive.
-    labelListList sendSizes(Pstream::nProcs());
-    sendSizes[Pstream::myProcNo()].setSize(Pstream::nProcs());
-    forAll(sendMap, proci)
+    if (((sample - nearestPoint) & n) > 0)
+    {
+        return volumeType::OUTSIDE;
+    }
+    else
     {
-        sendSizes[Pstream::myProcNo()][proci] = sendMap[proci].size();
+        return volumeType::INSIDE;
     }
-    Pstream::gatherList(sendSizes);
-    Pstream::scatterList(sendSizes);
+}
 
 
-    // Determine order of receiving
-    labelListList constructMap(Pstream::nProcs());
+Foam::label Foam::distributedTriSurfaceMesh::findOtherFace
+(
+    const labelListList& pointFaces,
+    const label nearFacei,
+    const label nearLabel
+) const
+{
+    const triSurface& surf = static_cast<const triSurface&>(*this);
+    const triSurface::FaceType& nearF = surf[nearFacei];
 
-    // My local segments first
-    constructMap[Pstream::myProcNo()] = identity
-    (
-        sendMap[Pstream::myProcNo()].size()
-    );
+    const edge e(nearF[nearLabel], nearF[nearF.fcIndex(nearLabel)]);
 
-    label segmenti = constructMap[Pstream::myProcNo()].size();
-    forAll(constructMap, proci)
+    const labelList& pFaces = pointFaces[e[0]];
+    forAll(pFaces, pFacei)
     {
-        if (proci != Pstream::myProcNo())
+        const label facei = pFaces[pFacei];
+        if (facei != nearFacei)
         {
-            // What i need to receive is what other processor is sending to me.
-            label nRecv = sendSizes[proci][Pstream::myProcNo()];
-            constructMap[proci].setSize(nRecv);
+            const triSurface::FaceType& f = surf[facei];
 
-            for (label i = 0; i < nRecv; i++)
+            int dir = f.edgeDirection(e);
+            if (dir != 0)
             {
-                constructMap[proci][i] = segmenti++;
+                return facei;
             }
         }
     }
-
-    return autoPtr<mapDistribute>::New
-    (
-        segmenti, // size after construction
-        std::move(sendMap),
-        std::move(constructMap)
-    );
+    return -1;
 }
 
 
-// Find bounding boxes that guarantee a more or less uniform distribution
-// of triangles. Decomposition in here is only used to get the bounding
-// boxes, actual decomposition is done later on.
-// Returns a per processor a list of bounding boxes that most accurately
-// describe the shape. For now just a single bounding box per processor but
-// optimisation might be to determine a better fitting shape.
-Foam::List<Foam::List<Foam::treeBoundBox>>
-Foam::distributedTriSurfaceMesh::independentlyDistributedBbs
+void Foam::distributedTriSurfaceMesh::calcFaceFaces
 (
-    const triSurface& s
+    const triSurface& s,
+    const labelListList& pointFaces,
+    labelListList& faceFaces
 )
 {
-    if (!decomposer_.valid())
+    faceFaces.setSize(s.size());
+
+    DynamicList<label> nbrs;
+
+    forAll(faceFaces, facei)
     {
-        // Use singleton decomposeParDict. Cannot use decompositionModel
-        // here since we've only got Time and not a mesh.
+        const labelledTri& f = s[facei];
 
-        const auto* dictPtr =
-            searchableSurface::time().findObject<IOdictionary>
-            (
-                // == decompositionModel::canonicalName
-                "decomposeParDict"
-            );
+        nbrs.reserve(f.size());
+        nbrs.clear();
 
-        if (dictPtr)
-        {
-            decomposer_ = decompositionMethod::New(*dictPtr);
-        }
-        else
+        forAll(f, fp)
         {
-            if (!decomposeParDict_.valid())
+            const edge e(f[fp], f[f.fcIndex(fp)]);
+            const labelList& pFaces = pointFaces[f[fp]];
+            forAll(pFaces, pFacei)
             {
-                decomposeParDict_.reset
-                (
-                    new IOdictionary
-                    (
-                        IOobject
-                        (
-                            // == decompositionModel::canonicalName
-                            "decomposeParDict",
-                            searchableSurface::time().system(),
-                            searchableSurface::time(),
-                            IOobject::MUST_READ,
-                            IOobject::NO_WRITE
-                        )
-                    )
-                );
+                const label otherFacei = pFaces[pFacei];
+                if (otherFacei != facei)
+                {
+                    if (s[otherFacei].edgeDirection(e) != 0)
+                    {
+                        if (!nbrs.find(otherFacei))
+                        {
+                            nbrs.append(otherFacei);
+                        }
+                    }
+                }
             }
-            decomposer_ = decompositionMethod::New(decomposeParDict_());
-        }
-
-
-        if (!decomposer_().parallelAware())
-        {
-            FatalErrorInFunction
-                << "The decomposition method " << decomposer_().typeName
-                << " does not decompose in parallel."
-                << " Please choose one that does." << exit(FatalError);
-        }
-
-        if (!isA<geomDecomp>(decomposer_()))
-        {
-            FatalErrorInFunction
-                << "The decomposition method " << decomposer_().typeName
-                << " is not a geometric decomposition method." << endl
-                << "Only geometric decomposition methods are currently"
-                << " supported."
-                << exit(FatalError);
         }
+        faceFaces[facei] = std::move(nbrs);
     }
+}
 
-    // Do decomposition according to triangle centre
-    pointField triCentres(s.size());
-    forAll(s, trii)
+
+void Foam::distributedTriSurfaceMesh::surfaceSide
+(
+    const pointField& samples,
+    const List<pointIndexHit>& nearestInfo,
+    List<volumeType>& volType
+) const
+{
+    if (debug)
     {
-        triCentres[trii] = s[trii].centre(s.points());
+        Pout<< "distributedTriSurfaceMesh::surfaceSide :"
+            << " finding surface side given points on surface for "
+            << samples.size() << " samples" << endl;
     }
 
+    // Use global index to send local tri and nearest back to originating
+    // processor
 
-    geomDecomp& decomposer = refCast<geomDecomp>(decomposer_());
-
-    // Do the actual decomposition
-    labelList distribution(decomposer.decompose(triCentres));
+    labelList triangleIndex(nearestInfo.size());
+    autoPtr<mapDistribute> mapPtr
+    (
+        calcLocalQueries
+        (
+            nearestInfo,
+            triangleIndex
+        )
+    );
+    const mapDistribute& map = mapPtr();
 
-    // Find bounding box for all triangles on new distribution.
+    // Send over samples
+    pointField localSamples(samples);
+    map.distribute(localSamples);
 
-    // Initialise to inverted box
-    List<List<treeBoundBox>> bbs(Pstream::nProcs());
-    forAll(bbs, proci)
-    {
-        bbs[proci].setSize(1, treeBoundBox(boundBox::invertedBox));
-    }
 
-    forAll(s, trii)
-    {
-        const triSurface::FaceType& f = s[trii];
+    // Do my tests
+    // ~~~~~~~~~~~
 
-        treeBoundBox& bb = bbs[distribution[trii]][0];
-        bb.add(s.points(), f);
-    }
+    volType.setSize(triangleIndex.size());
+    volType = volumeType::UNKNOWN;
 
-    // Now combine for all processors and convert to correct format.
-    forAll(bbs, proci)
+    const triSurface& surf = static_cast<const triSurface&>(*this);
+    const pointField& points = surf.points();
     {
-        Pstream::listCombineGather(bbs[proci], plusEqOp<boundBox>());
-        Pstream::listCombineScatter(bbs[proci]);
-    }
-
-    return bbs;
-}
+        //const labelListList& pointFaces = surf.pointFaces();
+        // Construct pointFaces. Let's hope surface has compact point
+        // numbering ...
+        labelListList pointFaces;
+        invertManyToMany(points.size(), surf, pointFaces);
 
+        EdgeMap<labelPair> edgeToFaces;
 
-// Does any part of triangle overlap bb.
-bool Foam::distributedTriSurfaceMesh::overlaps
-(
-    const List<treeBoundBox>& bbs,
-    const point& p0,
-    const point& p1,
-    const point& p2
-)
-{
-    treeBoundBox triBb(p0);
-    triBb.add(p1);
-    triBb.add(p2);
+        forAll(triangleIndex, i)
+        {
+            label facei = triangleIndex[i];
+            const triSurface::FaceType& f = surf[facei];
+            const point& sample = localSamples[i];
 
-    forAll(bbs, bbi)
-    {
-        const treeBoundBox& bb = bbs[bbi];
+            // Find where point is on face
+            label nearType, nearLabel;
+            pointHit pHit =
+                f.nearestPointClassify(sample, points, nearType, nearLabel);
 
-        // Exact test of triangle intersecting bb
+            const point& nearestPoint(pHit.rawPoint());
 
-        // Quick rejection. If whole bounding box of tri is outside cubeBb then
-        // there will be no intersection.
-        if (bb.overlaps(triBb))
-        {
-            // Check if one or more triangle point inside
-            if (bb.contains(p0) || bb.contains(p1) || bb.contains(p2))
+            if (nearType == triPointRef::NONE)
             {
-                // One or more points inside
-                return true;
-            }
+                const vector sampleNearestVec = (sample - nearestPoint);
 
-            // Now we have the difficult case: all points are outside but
-            // connecting edges might go through cube. Use fast intersection
-            // of bounding box.
-            bool intersect = triangleFuncs::intersectBb(p0, p1, p2, bb);
+                // Nearest to face interior. Use faceNormal to determine side
+                //scalar c = sampleNearestVec & surf.faceNormals()[facei];
+                scalar c = sampleNearestVec & surf[facei].normal(points);
 
-            if (intersect)
+                if (c > 0)
+                {
+                    volType[i] = volumeType::OUTSIDE;
+                }
+                else
+                {
+                    volType[i] = volumeType::INSIDE;
+                }
+            }
+            else if (nearType == triPointRef::EDGE)
             {
-                return true;
+                // Nearest to edge nearLabel. Note that this can only be a
+                // knife-edge
+                // situation since otherwise the nearest point could never be
+                // the edge.
+
+                label otherFacei = findOtherFace(pointFaces, facei, nearLabel);
+                if (otherFacei != -1)
+                {
+                    volType[i] =
+                        edgeSide(sample, nearestPoint, facei, otherFacei);
+                }
+                else
+                {
+                    // Open edge. Leave volume type unknown
+                }
             }
-        }
-    }
-    return false;
-}
+            else
+            {
+                // Nearest to point. Could use pointNormal here but is not
+                // correct.
+                // Instead determine which edge using point is nearest and
+                // use test above (nearType == triPointRef::EDGE).
 
+                const label pointi = f[nearLabel];
+                const labelList& pFaces = pointFaces[pointi];
+                const vector sampleNearestVec = (sample - nearestPoint);
 
-void Foam::distributedTriSurfaceMesh::subsetMeshMap
-(
-    const triSurface& s,
-    const boolList& include,
-    const label nIncluded,
-    labelList& newToOldPoints,
-    labelList& oldToNewPoints,
-    labelList& newToOldFaces
-)
-{
-    newToOldFaces.setSize(nIncluded);
-    newToOldPoints.setSize(s.points().size());
-    oldToNewPoints.setSize(s.points().size());
-    oldToNewPoints = -1;
-    {
-        label facei = 0;
-        label pointi = 0;
+                // Loop over all faces. Check if have both edge faces. Do
+                // test.
+                edgeToFaces.clear();
 
-        forAll(include, oldFacei)
-        {
+                scalar maxCosAngle = -GREAT;
+                labelPair maxEdgeFaces(-1, -1);
+
+                forAll(pFaces, pFacei)
+                {
+                    label facei = pFaces[pFacei];
+                    const triSurface::FaceType& f = surf[facei];
+
+                    label fp = findIndex(f, pointi);
+                    label p1 = f[f.fcIndex(fp)];
+                    label pMin1 = f[f.rcIndex(fp)];
+
+                    Pair<edge> edges
+                    (
+                        edge(pointi, p1),
+                        edge(pointi, pMin1)
+                    );
+
+                    // Check edge fp-to-fp+1  and fp-1
+                    // determine distance/angle to nearPoint
+                    for (const edge& e : edges)
+                    {
+                        auto iter = edgeToFaces.find(e);
+                        if (iter.found())
+                        {
+                            if (iter().second() == -1)
+                            {
+                                // Found second face. Now we have edge+faces
+                                iter().second() = facei;
+
+                                vector eVec(e.vec(points));
+                                scalar magEVec = mag(eVec);
+
+                                if (magEVec > VSMALL)
+                                {
+                                    eVec /= magEVec;
+
+                                    // Determine edge most in direction of
+                                    // sample
+                                    scalar cosAngle(sampleNearestVec&eVec);
+                                    if (cosAngle > maxCosAngle)
+                                    {
+                                        maxCosAngle = cosAngle;
+                                        maxEdgeFaces = iter();
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                FatalErrorInFunction << "Not closed"
+                                    << exit(FatalError);
+                            }
+                        }
+                        else
+                        {
+                            edgeToFaces.insert(e, labelPair(facei, -1));
+                        }
+                    }
+                }
+
+
+                // Check that surface is closed
+                bool closed = true;
+                for (auto iter : edgeToFaces)
+                {
+                    if (iter[0] == -1 || iter[1] == -1)
+                    {
+                        closed = false;
+                        break;
+                    }
+                }
+                if (closed)
+                {
+                    volType[i] = edgeSide
+                    (
+                        sample,
+                        nearestPoint,
+                        maxEdgeFaces[0],
+                        maxEdgeFaces[1]
+                    );
+                }
+            }
+        }
+    }
+
+
+    // Send back results
+    // ~~~~~~~~~~~~~~~~~
+
+    // Note that we make sure that if multiple processors hold data only
+    // the one with inside/outside wins. (though this should already be
+    // handled by the fact we have a unique nearest triangle so we only
+    // send the volume-query to a single processor)
+
+
+    //forAll(localSamples, i)
+    //{
+    //    Pout<< "surfaceSide : for localSample:" << localSamples[i]
+    //        << " found volType:" << volumeType::names[volType[i]]
+    //        << endl;
+    //}
+
+    const volumeType zero(volumeType::UNKNOWN);
+    mapDistributeBase::distribute
+    (
+        Pstream::commsTypes::nonBlocking,
+        List<labelPair>(0),
+        nearestInfo.size(),
+        map.constructMap(),
+        map.constructHasFlip(),
+        map.subMap(),
+        map.subHasFlip(),
+        volType,
+        volumeCombineOp(),
+        noOp(),           // no flipping
+        zero
+    );
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::surfaceSide :"
+            << " finished finding surface side given points on surface for "
+            << samples.size() << " samples" << endl;
+    }
+}
+
+
+void Foam::distributedTriSurfaceMesh::collectLeafMids
+(
+    const label nodeI,
+    DynamicField<point>& midPoints
+) const
+{
+    const indexedOctree<treeDataTriSurface>::node& nod = tree().nodes()[nodeI];
+
+    for (direction octant = 0; octant < nod.subNodes_.size(); octant++)
+    {
+        const labelBits index = nod.subNodes_[octant];
+
+        if (indexedOctree<treeDataTriSurface>::isNode(index))
+        {
+            // tree node. Recurse.
+            collectLeafMids
+            (
+                indexedOctree<treeDataTriSurface>::getNode(index),
+                midPoints
+            );
+        }
+        else if (indexedOctree<treeDataTriSurface>::isContent(index))
+        {}
+        else
+        {
+            // No data in this octant. Set type for octant acc. to the mid
+            // of its bounding box.
+            const treeBoundBox subBb = nod.bb_.subBbox(octant);
+            midPoints.append(subBb.midpoint());
+        }
+    }
+}
+
+
+Foam::volumeType Foam::distributedTriSurfaceMesh::calcVolumeType
+(
+    const List<volumeType>& midPointTypes,
+    label& midPointi,
+    PackedList<2>& nodeTypes,
+    const label nodeI
+) const
+{
+    // Pre-calculates wherever possible the volume status per node/subnode.
+    // Recurses to determine status of lowest level boxes. Level above is
+    // combination of octants below.
+
+    const indexedOctree<treeDataTriSurface>::node& nod = tree().nodes()[nodeI];
+
+    volumeType myType = volumeType::UNKNOWN;
+
+    for (direction octant = 0; octant < nod.subNodes_.size(); octant++)
+    {
+        volumeType subType;
+
+        const labelBits index = nod.subNodes_[octant];
+
+        if (indexedOctree<treeDataTriSurface>::isNode(index))
+        {
+            // tree node. Recurse.
+            subType = calcVolumeType
+            (
+                midPointTypes,
+                midPointi,
+                nodeTypes,
+                indexedOctree<treeDataTriSurface>::getNode(index)
+            );
+        }
+        else if (indexedOctree<treeDataTriSurface>::isContent(index))
+        {
+            // Contents. Depending on position in box might be on either
+            // side.
+            subType = volumeType::MIXED;
+        }
+        else
+        {
+            // No data in this octant. Set type for octant acc. to the mid
+            // of its bounding box.
+            //Pout<< "    for leaf at bb:" << nod.bb_.subBbox(octant)
+            //    << " node:" << nodeI
+            //    << " octant:" << octant
+            //    << " caching volType to:" << midPointTypes[midPointi] << endl;
+            subType = midPointTypes[midPointi++];
+        }
+
+        // Store octant type
+        nodeTypes.set((nodeI<<3)+octant, subType);
+
+        // Combine sub node types into type for treeNode. Result is 'mixed' if
+        // types differ among subnodes.
+        if (myType == volumeType::UNKNOWN)
+        {
+            myType = subType;
+        }
+        else if (subType != myType)
+        {
+            myType = volumeType::MIXED;
+        }
+    }
+    return myType;
+}
+
+
+Foam::volumeType Foam::distributedTriSurfaceMesh::cachedVolumeType
+(
+    const label nodeI,
+    const point& sample
+) const
+{
+    const indexedOctree<treeDataTriSurface>::node& nod = tree().nodes()[nodeI];
+
+    direction octant = nod.bb_.subOctant(sample);
+
+    volumeType octantType = volumeType::type
+    (
+        tree().nodeTypes().get((nodeI<<3)+octant)
+    );
+
+    if (octantType == volumeType::INSIDE)
+    {
+        return octantType;
+    }
+    else if (octantType == volumeType::OUTSIDE)
+    {
+        return octantType;
+    }
+    else if (octantType == volumeType::UNKNOWN)
+    {
+        // Can happen for e.g. non-manifold surfaces.
+        return octantType;
+    }
+    else if (octantType == volumeType::MIXED)
+    {
+        labelBits index = nod.subNodes_[octant];
+
+        if (indexedOctree<treeDataTriSurface>::isNode(index))
+        {
+            // Recurse
+            volumeType subType = cachedVolumeType
+            (
+                    indexedOctree<treeDataTriSurface>::getNode(index),
+                    sample
+            );
+
+            return subType;
+        }
+        else if (indexedOctree<treeDataTriSurface>::isContent(index))
+        {
+            // Content.
+            return volumeType::UNKNOWN;
+        }
+        else
+        {
+            // Empty node. Cannot have 'mixed' as its type since not divided
+            // up and has no items inside it.
+            FatalErrorInFunction
+                << "Sample:" << sample << " node:" << nodeI
+                << " with bb:" << nod.bb_ << nl
+                << "Empty subnode has invalid volume type MIXED."
+                << abort(FatalError);
+
+            return volumeType::UNKNOWN;
+        }
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Sample:" << sample << " at node:" << nodeI
+            << " octant:" << octant
+            << " with bb:" << nod.bb_.subBbox(octant) << nl
+            << "Node has invalid volume type " << octantType
+            << abort(FatalError);
+
+        return volumeType::UNKNOWN;
+    }
+}
+
+
+// Find bounding boxes that guarantee a more or less uniform distribution
+// of triangles. Decomposition in here is only used to get the bounding
+// boxes, actual decomposition is done later on.
+// Returns a per processor a list of bounding boxes that most accurately
+// describe the shape. For now just a single bounding box per processor but
+// optimisation might be to determine a better fitting shape.
+Foam::List<Foam::List<Foam::treeBoundBox>>
+Foam::distributedTriSurfaceMesh::independentlyDistributedBbs
+(
+    const triSurface& s
+)
+{
+    addProfiling
+    (
+        distribute,
+        "distributedTriSurfaceMesh::independentlyDistributedBbs"
+    );
+
+    if (!decomposer_.valid())
+    {
+        // Use singleton decomposeParDict. Cannot use decompositionModel
+        // here since we've only got Time and not a mesh.
+
+        const auto* dictPtr =
+            searchableSurface::time().findObject<IOdictionary>
+            (
+                // == decompositionModel::canonicalName
+                "decomposeParDict"
+            );
+
+        if (dictPtr)
+        {
+            decomposer_ = decompositionMethod::New(*dictPtr);
+        }
+        else
+        {
+            if (!decomposeParDict_.valid())
+            {
+                decomposeParDict_.reset
+                (
+                    new IOdictionary
+                    (
+                        IOobject
+                        (
+                            // == decompositionModel::canonicalName
+                            "decomposeParDict",
+                            searchableSurface::time().system(),
+                            searchableSurface::time(),
+                            IOobject::MUST_READ,
+                            IOobject::NO_WRITE
+                        )
+                    )
+                );
+            }
+            decomposer_ = decompositionMethod::New(decomposeParDict_());
+        }
+    }
+
+
+    // Initialise to inverted box
+    List<List<treeBoundBox>> bbs(Pstream::nProcs());
+    forAll(bbs, proci)
+    {
+        bbs[proci].setSize(1, treeBoundBox(boundBox::invertedBox));
+    }
+
+
+    const globalIndex& triIndexer = globalTris();
+
+    bool masterOnly;
+    {
+        masterOnly = true;
+        for (label proci = 1; proci < Pstream::nProcs(); proci++)
+        {
+            if (triIndexer.localSize(proci) != 0)
+            {
+                masterOnly = false;
+                break;
+            }
+        }
+    }
+
+    if (masterOnly)
+    {
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+                << " determining master-only decomposition for " << s.size()
+                << " centroids for " << searchableSurface::name() << endl;
+        }
+
+        // Triangle centres
+        pointField triCentres(s.size());
+        forAll(s, trii)
+        {
+            triCentres[trii] = s[trii].centre(s.points());
+        }
+
+        labelList distribution;
+        if (!isA<geomDecomp>(decomposer_()))
+        {
+            // Use connectivity
+            labelListList pointFaces;
+            invertManyToMany(s.points().size(), s, pointFaces);
+            labelListList faceFaces(s.size());
+            calcFaceFaces(s, pointFaces, faceFaces);
+
+            // Do the actual decomposition
+            const bool oldParRun = UPstream::parRun();
+            UPstream::parRun() = false;
+            distribution = decomposer_().decompose(faceFaces, triCentres);
+            UPstream::parRun() = oldParRun;
+        }
+        else
+        {
+            // Do the actual decomposition
+            distribution = decomposer_().decompose(triCentres);
+        }
+
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+                << " determining processor bounding boxes" << endl;
+        }
+
+        // Find bounding box for all triangles on new distribution.
+        forAll(s, trii)
+        {
+            const triSurface::FaceType& f = s[trii];
+
+            treeBoundBox& bb = bbs[distribution[trii]][0];
+            bb.add(s.points(), f);
+        }
+
+        // Now combine for all processors and convert to correct format.
+        forAll(bbs, proci)
+        {
+            Pstream::listCombineGather(bbs[proci], plusEqOp<boundBox>());
+            Pstream::listCombineScatter(bbs[proci]);
+        }
+    }
+    else if (distType_ == DISTRIBUTED)
+    {
+        // Fully distributed decomposition. Does not filter duplicate
+        // triangles.
+        if (!decomposer_().parallelAware())
+        {
+            FatalErrorInFunction
+                << "The decomposition method " << decomposer_().typeName
+                << " does not decompose in parallel."
+                << " Please choose one that does." << exit(FatalError);
+        }
+
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+                << " determining decomposition for " << s.size()
+                << " centroids" << endl;
+        }
+
+        // Triangle centres
+        pointField triCentres(s.size());
+        forAll(s, trii)
+        {
+            triCentres[trii] = s[trii].centre(s.points());
+        }
+
+        labelList distribution = decomposer_().decompose(triCentres);
+
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+                << " determining processor bounding boxes for "
+                << searchableSurface::name() << endl;
+        }
+
+        // Find bounding box for all triangles on new distribution.
+        forAll(s, trii)
+        {
+            const triSurface::FaceType& f = s[trii];
+
+            treeBoundBox& bb = bbs[distribution[trii]][0];
+            bb.add(s.points(), f);
+        }
+
+        // Now combine for all processors and convert to correct format.
+        forAll(bbs, proci)
+        {
+            Pstream::listCombineGather(bbs[proci], plusEqOp<boundBox>());
+            Pstream::listCombineScatter(bbs[proci]);
+        }
+    }
+//    //// Tbd. initial duplicate filtering of border points only
+//    if (distType_ == DISTRIBUTED)
+//    {
+//        // Fully distributed decomposition. Does not filter duplicate
+//        // triangles.
+//        if (!decomposer_().parallelAware())
+//        {
+//            FatalErrorInFunction
+//                << "The decomposition method " << decomposer_().typeName
+//                << " does not decompose in parallel."
+//                << " Please choose one that does." << exit(FatalError);
+//        }
+//
+//        if (debug)
+//        {
+//            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+//                << " determining decomposition for " << s.size()
+//                << " centroids" << endl;
+//        }
+//        const pointField& points = s.points();
+//
+//        pointField triCentres(s.size());
+//        forAll(s, trii)
+//        {
+//            triCentres[trii] = s[trii].centre(points);
+//        }
+//
+//        // Collect all triangles not fully inside the current bb
+//        DynamicList<label> borderTris(s.size()/Pstream::nProcs());
+//
+//        const List<treeBoundBox>& myBbs = procBb_[Pstream::myProcNo];
+//
+//        boolList includedFace;
+//        overlappingSurface(s, myBbs, includedFace);
+//        boolList internalOrBorderFace(includedFace);
+//        forAll(s, trii)
+//        {
+//            if (includedFace[trii])
+//            {
+//                // Triangle is either inside or part-inside. Exclude fully
+//                // inside triangles.
+//                const labelledTri& f = s[trii];
+//                const point& p0 = points[f[0]];
+//                const point& p1 = points[f[1]];
+//                const point& p2 = points[f[2]];
+//                if
+//                (
+//                   !contains(myBbs, p0)
+//                || !contains(myBbs, p1)
+//                || !contains(myBbs, p2)
+//                )
+//                {
+//                    borderTris.append(trii);
+//                }
+//            }
+//        }
+//
+//        const label nBorderTris = borderTris.size();
+//
+//        Pout<< "Have " << borderTris.size() << " border triangles out of "
+//            << s.size() << endl;
+//
+//        labelListList sendMap(Pstream::nProcs());
+//        sendMap[0] = std::move(borderTris);
+//
+//        const mapDistribute map(std::move(sendMap));
+//
+//        // Gather all borderTris
+//        //globalIndex globalBorderTris(borderTris.size());
+//        //pointField globalBorderCentres(allCentres, borderTris);
+//        //globalBorderTris.gather
+//        //(
+//        //    UPstream::worldComm,
+//        //    UPstream::procID(Pstream::worldComm),
+//        //    globalBorderCentres
+//        //);
+//        pointField globalBorderCentres(allCentres);
+//        map.distribute(globalBorderCentres);
+//
+//        // Merge on master
+//        labelList masterBorder(borderTris.size(), -1);
+//        if (Pstream::master())
+//        {
+//            labelList allToMerged;
+//            label nMerged = mergePoints
+//            (
+//                globalBorderCentres,
+//                mergeDist_,
+//                false,          //const bool verbose,
+//                allToMerged
+//                // maybe bounds().mid() ?
+//            );
+//
+//            if (debug)
+//            {
+//                Pout<< "distributedTriSurfaceMesh::"
+//                    << "independentlyDistributedBbs :"
+//                    << " merged " << globalBorderCentres.size()
+//                    << " border centroids down to " << nMerged << endl;
+//            }
+//
+//            labelList mergedMaster(nMerged, -1);
+//            isMaster.setSize(globalBorderCentres.size(), false);
+//            forAll(allToMerged, i)
+//            {
+//                label mergedi = allToMerged[i];
+//                if (mergedMaster[mergedi] == -1)
+//                {
+//                    mergedMaster[mergedi] = i;
+//                    isMaster[i] = true;
+//                }
+//            }
+//            forAll(allToMerged, i)
+//            {
+//                label mergedi = allToMerged[i];
+//                masterBorder[i] = mergedMaster[mergedi];
+//            }
+//        }
+//        //globalBorderTris.scatter
+//        //(
+//        //    UPstream::worldComm,
+//        //    UPstream::procID(Pstream::worldComm),
+//        //    isMasterPoint
+//        //);
+//        //boolList isMasterBorder(s.size(), false);
+//        //forAll(borderTris, i)
+//        //{
+//        //    isMasterBorder[borderTris[i]] = isMasterPoint[i];
+//        //}
+//        map.reverseDistribute(s.size(), isMaster);
+//
+//        // Collect all non-border or master-border points
+//        DynamicList<label> triMap(s.size());
+//        forAll(s, trii)
+//        {
+//            if (includedFace[trii])
+//            {
+//                // Triangle is either inside or part-inside. Exclude fully
+//                // inside triangles.
+//                const labelledTri& f = s[trii];
+//                const point& p0 = points[f[0]];
+//                const point& p1 = points[f[1]];
+//                const point& p2 = points[f[2]];
+//
+//                if
+//                (
+//                   contains(myBbs, p0)
+//                && contains(myBbs, p1)
+//                && contains(myBbs, p2)
+//                )
+//                {
+//                    // Internal
+//                    triMap.append(trii);
+//                }
+//                else if (isMasterBorder[trii])
+//                {
+//                    // Part overlapping and master of overlap
+//                    triMap.append(trii);
+//                }
+//            }
+//        }
+//
+//        pointField masterCentres(allCentres, triMap);
+//
+//        // Do the actual decomposition
+//        labelList masterDistribution(decomposer_().decompose(masterCentres));
+//
+//        // Make map to get the decomposition from the master of each border
+//        labelList borderGlobalMaster(borderTris.size());
+//        forAll(borderTris, borderi)
+//        {
+//            borderGlobalMaster[borderi] = ..masterTri
+//        }
+//        mapDistribute map(globalBorderTris, borderGlobalMaster
+//
+//        // Send decomposition
+//
+//
+//        if (debug)
+//        {
+//            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+//                << " determining processor bounding boxes" << endl;
+//        }
+//
+//        // Find bounding box for all triangles on new distribution.
+//        forAll(s, trii)
+//        {
+//            const triSurface::FaceType& f = s[trii];
+//
+//            treeBoundBox& bb = bbs[distribution[trii]][0];
+//            bb.add(s.points(), f);
+//        }
+//
+//        // Now combine for all processors and convert to correct format.
+//        forAll(bbs, proci)
+//        {
+//            Pstream::listCombineGather(bbs[proci], plusEqOp<boundBox>());
+//            Pstream::listCombineScatter(bbs[proci]);
+//        }
+//    }
+    else
+    {
+        // Master-only decomposition. Filters duplicate triangles so repeatable.
+
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+                << " collecting all centroids" << endl;
+        }
+
+        // Collect all triangle centres
+        pointField allCentres(s.size());
+        {
+            forAll(s, trii)
+            {
+                allCentres[trii] = s[trii].centre(s.points());
+            }
+            globalTris().gather
+            (
+                UPstream::worldComm,
+                UPstream::procID(Pstream::worldComm),
+                allCentres
+            );
+        }
+
+        // Determine local decomposition
+        labelList distribution(s.size());
+        {
+            labelList allDistribution;
+            if (Pstream::master())
+            {
+                labelList allToMerged;
+                label nMerged = mergePoints
+                (
+                    allCentres,
+                    mergeDist_,
+                    false,          //const bool verbose,
+                    allToMerged
+                    // maybe bounds().mid() ?
+                );
+
+                if (debug)
+                {
+                    Pout<< "distributedTriSurfaceMesh::"
+                        << "independentlyDistributedBbs :"
+                        << " merged " << allCentres.size()
+                        << " centroids down to " << nMerged << endl;
+                }
+
+                // Collect merged points
+                pointField mergedPoints(nMerged);
+                UIndirectList<point>(mergedPoints, allToMerged) = allCentres;
+
+                // Decompose merged centres
+                const bool oldParRun = UPstream::parRun();
+                UPstream::parRun() = false;
+                labelList mergedDist(decomposer_().decompose(mergedPoints));
+                UPstream::parRun() = oldParRun;
+
+                // Convert to all
+                allDistribution = UIndirectList<label>
+                (
+                    mergedDist,
+                    allToMerged
+                );
+            }
+
+            // Scatter back to processors
+            globalTris().scatter
+            (
+                UPstream::worldComm,
+                UPstream::procID(Pstream::worldComm),
+                allDistribution,
+                distribution
+            );
+            if (debug)
+            {
+                Pout<< "distributedTriSurfaceMesh::"
+                    << "independentlyDistributedBbs :"
+                    << " determined decomposition" << endl;
+            }
+        }
+
+        // Find bounding box for all triangles on new distribution.
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::independentlyDistributedBbs :"
+                << " determining processor bounding boxes for "
+                << searchableSurface::name() << endl;
+        }
+
+        forAll(s, trii)
+        {
+            const triSurface::FaceType& f = s[trii];
+            treeBoundBox& bb = bbs[distribution[trii]][0];
+            bb.add(s.points(), f);
+        }
+
+        // Now combine for all processors and convert to correct format.
+        forAll(bbs, proci)
+        {
+            Pstream::listCombineGather(bbs[proci], plusEqOp<boundBox>());
+            Pstream::listCombineScatter(bbs[proci]);
+        }
+    }
+    return bbs;
+}
+
+
+// Does any part of triangle overlap bb.
+bool Foam::distributedTriSurfaceMesh::overlaps
+(
+    const List<treeBoundBox>& bbs,
+    const point& p0,
+    const point& p1,
+    const point& p2
+)
+{
+    treeBoundBox triBb(p0);
+    triBb.add(p1);
+    triBb.add(p2);
+
+    forAll(bbs, bbi)
+    {
+        const treeBoundBox& bb = bbs[bbi];
+
+        // Exact test of triangle intersecting bb
+
+        // Quick rejection. If whole bounding box of tri is outside cubeBb then
+        // there will be no intersection.
+        if (bb.overlaps(triBb))
+        {
+            // Check if one or more triangle point inside
+            if (bb.contains(p0) || bb.contains(p1) || bb.contains(p2))
+            {
+                // One or more points inside
+                return true;
+            }
+
+            // Now we have the difficult case: all points are outside but
+            // connecting edges might go through cube. Use fast intersection
+            // of bounding box.
+            bool intersect = triangleFuncs::intersectBb(p0, p1, p2, bb);
+
+            if (intersect)
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+
+void Foam::distributedTriSurfaceMesh::subsetMeshMap
+(
+    const triSurface& s,
+    const boolList& include,
+    const label nIncluded,
+    labelList& newToOldPoints,
+    labelList& oldToNewPoints,
+    labelList& newToOldFaces
+)
+{
+    newToOldFaces.setSize(nIncluded);
+    newToOldPoints.setSize(s.points().size());
+    oldToNewPoints.setSize(s.points().size());
+    oldToNewPoints = -1;
+    {
+        label facei = 0;
+        label pointi = 0;
+
+        forAll(include, oldFacei)
+        {
             if (include[oldFacei])
             {
                 // Store new faces compact
@@ -1297,8 +2482,10 @@ Foam::distributedTriSurfaceMesh::distributedTriSurfaceMesh
             searchableSurface::registerObject()
         ),
         dict
-    )
+    ),
+    currentDistType_(FROZEN)    // only used to trigger re-distribution
 {
+    // Read from the provided dictionary
     read();
 
     bounds().reduce();
@@ -1331,14 +2518,14 @@ Foam::distributedTriSurfaceMesh::distributedTriSurfaceMesh(const IOobject& io)
         IOobject
         (
             io.name(),
-            io.time().findInstance(io.local(), word::null),
+            findLocalInstance(io),  // findInstance with parent searching
             io.local(),
             io.db(),
             io.readOpt(),
             io.writeOpt(),
             io.registerObject()
         ),
-        false
+        triSurfaceMesh::masterOnly    // allow parent searching
     ),
     dict_
     (
@@ -1348,34 +2535,84 @@ Foam::distributedTriSurfaceMesh::distributedTriSurfaceMesh(const IOobject& io)
             searchableSurface::instance(),
             searchableSurface::local(),
             searchableSurface::db(),
-            searchableSurface::readOpt(),
+            (
+                (
+                        searchableSurface::readOpt()
+                     == IOobject::MUST_READ
+                 ||     searchableSurface::readOpt()
+                     == IOobject::MUST_READ_IF_MODIFIED
+                )
+              ? IOobject::READ_IF_PRESENT
+              : searchableSurface::readOpt()
+            ),
             searchableSurface::writeOpt(),
             searchableSurface::registerObject()
-        )
-    )
+        ),
+        dictionary()
+    ),
+    currentDistType_(FROZEN)    // only used to trigger re-distribution
 {
+    // Read from the local, decomposed dictionary
     read();
 
     bounds().reduce();
 
-    if (debug)
-    {
-        InfoInFunction << "Read distributedTriSurface from " << io.objectPath()
-            << ':' << endl;
-        writeStats(Info);
+    const fileName actualFile(checkFile(io, true));
 
-        labelList nTris(Pstream::nProcs());
-        nTris[Pstream::myProcNo()] = triSurface::size();
-        Pstream::gatherList(nTris);
-        Pstream::scatterList(nTris);
+    if
+    (
+        actualFile != io.localFilePath(triSurfaceMesh::typeName)
+     && (distType_ == INDEPENDENT || distType_ == DISTRIBUTED)
+    )
+    {
+        if (debug)
+        {
+            InfoInFunction << "Read distributedTriSurface " << io.name()
+                << " from parent path " << actualFile << endl;
+        }
 
-        Info<< endl<< "\tproc\ttris\tbb" << endl;
-        forAll(nTris, proci)
+        if (Pstream::parRun())
         {
-            Info<< '\t' << proci << '\t' << nTris[proci]
-                 << '\t' << procBb_[proci] << endl;
+            // Distribute (checks that distType != currentDistType_ so should
+            // always trigger re-distribution)
+            List<treeBoundBox> bbs;
+            autoPtr<mapDistribute> faceMap;
+            autoPtr<mapDistribute> pointMap;
+            distribute
+            (
+                bbs,
+                true,       // keep unmapped triangles
+                faceMap,
+                pointMap
+            );
         }
-        Info<< endl;
+    }
+    else
+    {
+        if (debug)
+        {
+            InfoInFunction << "Read distributedTriSurface " << io.name()
+                << " from actual path " << actualFile << ':' << endl;
+
+            labelList nTris(Pstream::nProcs());
+            nTris[Pstream::myProcNo()] = triSurface::size();
+            Pstream::gatherList(nTris);
+            Pstream::scatterList(nTris);
+
+            Info<< endl<< "\tproc\ttris\tbb" << endl;
+            forAll(nTris, proci)
+            {
+                Info<< '\t' << proci << '\t' << nTris[proci]
+                     << '\t' << procBb_[proci] << endl;
+            }
+            Info<< endl;
+        }
+    }
+    if (debug)
+    {
+        InfoInFunction << "Read distributedTriSurface " << io.name() << ':'
+            << endl;
+        writeStats(Info);
     }
 }
 
@@ -1391,7 +2628,7 @@ Foam::distributedTriSurfaceMesh::distributedTriSurfaceMesh
         IOobject
         (
             io.name(),
-            io.time().findInstance(io.local(), word::null),
+            findLocalInstance(io),
             io.local(),
             io.db(),
             io.readOpt(),
@@ -1399,7 +2636,7 @@ Foam::distributedTriSurfaceMesh::distributedTriSurfaceMesh
             io.registerObject()
         ),
         dict,
-        false
+        triSurfaceMesh::masterOnly
     ),
     dict_
     (
@@ -1409,138 +2646,793 @@ Foam::distributedTriSurfaceMesh::distributedTriSurfaceMesh
             searchableSurface::instance(),
             searchableSurface::local(),
             searchableSurface::db(),
-            searchableSurface::readOpt(),
+            (
+                (
+                        searchableSurface::readOpt()
+                     == IOobject::MUST_READ
+                 ||     searchableSurface::readOpt()
+                     == IOobject::MUST_READ_IF_MODIFIED
+                )
+              ? IOobject::READ_IF_PRESENT
+              : searchableSurface::readOpt()
+            ),
             searchableSurface::writeOpt(),
             searchableSurface::registerObject()
-        )
+        ),
+        dictionary()
+    ),
+    currentDistType_(FROZEN)    // only used to trigger re-distribution
+{
+    // Read from the local, decomposed dictionary
+    read();
+
+    // Optionally override settings from provided dictionary
+    {
+        // Wanted distribution type
+        distType_ = distributionTypeNames_.lookupOrDefault
+        (
+            "distributionType",
+            dict_,
+            distType_
+        );
+
+        // Merge distance
+        dict_.readIfPresent("mergeDistance", mergeDist_);
+
+        // Distribution type
+        bool closed;
+        if (dict_.readIfPresent<bool>("closed", closed))
+        {
+            surfaceClosed_ = closed;
+        }
+
+        outsideVolType_ = volumeType::names.lookupOrDefault
+        (
+            "outsideVolumeType",
+            dict_,
+            outsideVolType_
+        );
+    }
+
+
+    bounds().reduce();
+
+    const fileName actualFile(checkFile(io, dict, true));
+
+    if
+    (
+        actualFile != io.localFilePath(triSurfaceMesh::typeName)
+     && (distType_ == INDEPENDENT || distType_ == DISTRIBUTED)
     )
+    {
+        if (debug)
+        {
+            InfoInFunction << "Read distributedTriSurface " << io.name()
+                << " from parent path " << actualFile
+                << " and dictionary" << endl;
+        }
+
+        if (Pstream::parRun())
+        {
+            // Distribute (checks that distType != currentDistType_ so should
+            // always trigger re-distribution)
+            List<treeBoundBox> bbs;
+            autoPtr<mapDistribute> faceMap;
+            autoPtr<mapDistribute> pointMap;
+            distribute
+            (
+                bbs,
+                true,       // keep unmapped triangles
+                faceMap,
+                pointMap
+            );
+        }
+    }
+    else
+    {
+        if (debug)
+        {
+            InfoInFunction << "Read distributedTriSurface " << io.name()
+                << " from actual path " << actualFile
+                << " and dictionary:" << endl;
+
+            labelList nTris(Pstream::nProcs());
+            nTris[Pstream::myProcNo()] = triSurface::size();
+            Pstream::gatherList(nTris);
+            Pstream::scatterList(nTris);
+
+            Info<< endl<< "\tproc\ttris\tbb" << endl;
+            forAll(nTris, proci)
+            {
+                Info<< '\t' << proci << '\t' << nTris[proci]
+                     << '\t' << procBb_[proci] << endl;
+            }
+            Info<< endl;
+        }
+    }
+    if (debug)
+    {
+        InfoInFunction << "Read distributedTriSurface " << io.name() << ':'
+            << endl;
+        writeStats(Info);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::distributedTriSurfaceMesh::~distributedTriSurfaceMesh()
+{
+    clearOut();
+}
+
+
+void Foam::distributedTriSurfaceMesh::clearOut()
+{
+    globalTris_.clear();
+    triSurfaceMesh::clearOut();
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+const Foam::globalIndex& Foam::distributedTriSurfaceMesh::globalTris() const
+{
+    if (!globalTris_.valid())
+    {
+        globalTris_.reset(new globalIndex(triSurface::size()));
+    }
+    return *globalTris_;
+}
+
+
+//void Foam::distributedTriSurfaceMesh::findNearest
+//(
+//    const pointField& samples,
+//    const scalarField& nearestDistSqr,
+//    List<pointIndexHit>& info
+//) const
+//{
+//    if (!Pstream::parRun())
+//    {
+//        triSurfaceMesh::findNearest(samples, nearestDistSqr, info);
+//        return;
+//    }
+//
+//    addProfiling
+//    (
+//        findNearest,
+//        "distributedTriSurfaceMesh::findNearest"
+//    );
+//
+//    if (debug)
+//    {
+//        Pout<< "distributedTriSurfaceMesh::findNearest :"
+//            << " trying to find nearest for " << samples.size()
+//            << " samples with max sphere "
+//            << (samples.size() ? Foam::sqrt(max(nearestDistSqr)) : Zero)
+//            << endl;
+//    }
+//
+//
+//    const indexedOctree<treeDataTriSurface>& octree = tree();
+//
+//    // Important:force synchronised construction of indexing
+//    const globalIndex& triIndexer = globalTris();
+//
+//
+//    // Initialise
+//    // ~~~~~~~~~~
+//
+//    info.setSize(samples.size());
+//    forAll(info, i)
+//    {
+//        info[i].setMiss();
+//    }
+//
+//
+//
+//    // Do any local queries
+//    // ~~~~~~~~~~~~~~~~~~~~
+//
+//    label nLocal = 0;
+//
+//    {
+//        // Work array - whether processor bb overlaps the bounding sphere.
+//        boolList procBbOverlaps(Pstream::nProcs());
+//
+//        forAll(samples, i)
+//        {
+//            // Find the processor this sample+radius overlaps.
+//            label nProcs = calcOverlappingProcs
+//            (
+//                samples[i],
+//                nearestDistSqr[i],
+//                procBbOverlaps
+//            );
+//
+//            // Overlaps local processor?
+//            if (procBbOverlaps[Pstream::myProcNo()])
+//            {
+//                info[i] = octree.findNearest(samples[i], nearestDistSqr[i]);
+//                if (info[i].hit())
+//                {
+//                    if
+//                    (
+//                        surfaceClosed_
+//                    && !contains(procBb_[proci], info[i].hitPoint())
+//                    )
+//                    {
+//                        // Nearest point is not on local processor so the
+//                        // the triangle is only there because some other bit
+//                        // of it
+//                        // is on it. Assume there is another processor that
+//                        // holds
+//                        // the full surrounding of the triangle so we can
+//                        // clear  this particular nearest.
+//                        info[i].setMiss();
+//                        info[i].setIndex(-1);
+//                    }
+//                    else
+//                    {
+//                        info[i].setIndex
+//                        (triIndexer.toGlobal(info[i].index()));
+//                    }
+//                }
+//                if (nProcs == 1)
+//                {
+//                    // Fully local
+//                    nLocal++;
+//                }
+//            }
+//        }
+//    }
+//
+//
+//    if
+//    (
+//        Pstream::parRun()
+//     && (
+//            returnReduce(nLocal, sumOp<label>())
+//          < returnReduce(samples.size(), sumOp<label>())
+//        )
+//    )
+//    {
+//        // Not all can be resolved locally. Build queries and map, send over
+//        // queries, do intersections, send back and merge.
+//
+//        // Calculate queries and exchange map
+//        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+//        pointField allCentres;
+//        scalarField allRadiusSqr;
+//        labelList allSegmentMap;
+//        autoPtr<mapDistribute> mapPtr
+//        (
+//            calcLocalQueries
+//            (
+//                false,    // exclude local processor since already done above
+//                samples,
+//                nearestDistSqr,
+//
+//                allCentres,
+//                allRadiusSqr,
+//                allSegmentMap
+//            )
+//        );
+//        const mapDistribute& map = mapPtr();
+//
+//
+//        // swap samples to local processor
+//        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+//        map.distribute(allCentres);
+//        map.distribute(allRadiusSqr);
+//
+//
+//        // Do my tests
+//        // ~~~~~~~~~~~
+//
+//        List<pointIndexHit> allInfo(allCentres.size());
+//        forAll(allInfo, i)
+//        {
+//            allInfo[i] = octree.findNearest
+//            (
+//                allCentres[i],
+//                allRadiusSqr[i]
+//            );
+//            if (allInfo[i].hit())
+//            {
+//                // We don't know if the nearest is on an edge/point. If
+//                // this is the case we preferentially want to return the
+//                // index on the processor that holds all surrounding triangles
+//                // so we can do e.g. follow-on inside/outside tests
+//                if
+//                (
+//                    surfaceClosed_
+//                && !contains
+//                    (
+//                        procBb_[Pstream::myProcNo()],
+//                        allInfo[i].hitPoint()
+//                    )
+//                )
+//                {
+//                    // Nearest point is not on local processor so the
+//                    // the triangle is only there because some other bit of it
+//                    // is on it. Assume there is another processor that holds
+//                    // the full surrounding of the triangle so we can clear
+//                    // this particular nearest.
+//                    allInfo[i].setMiss();
+//                    allInfo[i].setIndex(-1);
+//                }
+//                else
+//                {
+//                    allInfo[i].setIndex
+//                    (
+//                        triIndexer.toGlobal(allInfo[i].index())
+//                    );
+//                }
+//            }
+//        }
+//
+//
+//        // Send back results
+//        // ~~~~~~~~~~~~~~~~~
+//
+//        map.reverseDistribute(allSegmentMap.size(), allInfo);
+//
+//
+//        // Extract information
+//        // ~~~~~~~~~~~~~~~~~~~
+//
+//        forAll(allInfo, i)
+//        {
+//            if (allInfo[i].hit())
+//            {
+//                label pointi = allSegmentMap[i];
+//
+//                if (!info[pointi].hit())
+//                {
+//                    // No intersection yet so take this one
+//                    info[pointi] = allInfo[i];
+//                }
+//                else
+//                {
+//                    // Nearest intersection
+//                    if
+//                    (
+//                        magSqr(allInfo[i].hitPoint()-samples[pointi])
+//                      < magSqr(info[pointi].hitPoint()-samples[pointi])
+//                    )
+//                    {
+//                        info[pointi] = allInfo[i];
+//                    }
+//                }
+//            }
+//        }
+//    }
+//}
+
+
+void Foam::distributedTriSurfaceMesh::findNearest
+(
+    const pointField& samples,
+    const scalarField& nearestDistSqr,
+    List<pointIndexHit>& info
+) const
 {
-    read();
+    if (!Pstream::parRun())
+    {
+        triSurfaceMesh::findNearest(samples, nearestDistSqr, info);
+        return;
+    }
 
-    bounds().reduce();
+    addProfiling
+    (
+        findNearest,
+        "distributedTriSurfaceMesh::findNearest"
+    );
 
     if (debug)
     {
-        InfoInFunction << "Read distributedTriSurface from " << io.objectPath()
-            << " and dictionary:" << endl;
-        writeStats(Info);
+        Pout<< "distributedTriSurfaceMesh::findNearest :"
+            << " trying to find nearest for " << samples.size()
+            << " samples with max sphere "
+            << (samples.size() ? Foam::sqrt(max(nearestDistSqr)) : Zero)
+            << endl;
+    }
 
-        labelList nTris(Pstream::nProcs());
-        nTris[Pstream::myProcNo()] = triSurface::size();
-        Pstream::gatherList(nTris);
-        Pstream::scatterList(nTris);
+    const globalIndex& triIndexer = globalTris();
 
-        Info<< endl<< "\tproc\ttris\tbb" << endl;
-        forAll(nTris, proci)
-        {
-            Info<< '\t' << proci << '\t' << nTris[proci]
-                 << '\t' << procBb_[proci] << endl;
-        }
-        Info<< endl;
-    }
-}
+    // Two-pass searching:
+    // 1. send the sample to the processor whose bb contains it. This is
+    //    most likely also the one that holds the nearest triangle. (In case
+    //    there is no containing processor send to nearest processors. Note
+    //    that this might cause a lot of traffic if this is likely)
+    //    Send the resulting nearest point back.
+    // 2. with the find from 1 look at which other processors might have a
+    //    better triangle. Since hopefully step 1) will have produced a tight
+    //    bounding box this should limit the amount of points to be retested
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+    // 1. Test samples on processor(s) that contains them
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Foam::distributedTriSurfaceMesh::~distributedTriSurfaceMesh()
-{
-    clearOut();
-}
+    autoPtr<mapDistribute> map1Ptr;
+    scalarField distSqr(nearestDistSqr);
+    boolList procContains(Pstream::nProcs(), false);
+    boolList procOverlaps(Pstream::nProcs(), false);
 
+    label nOutside = 0;
+    {
+        List<DynamicList<label>> dynSendMap(Pstream::nProcs());
+        // Pre-size. Assume samples are uniformly distributed
+        forAll(dynSendMap, proci)
+        {
+            dynSendMap[proci].reserve(samples.size()/Pstream::nProcs());
+        }
 
-void Foam::distributedTriSurfaceMesh::clearOut()
-{
-    globalTris_.clear();
-    triSurfaceMesh::clearOut();
-}
+        forAll(samples, samplei)
+        {
+            label minProci = -1;
+            Tuple2<label, scalar> best = findBestProcs
+            (
+                samples[samplei],
+                distSqr[samplei],
+                procContains,
+                procOverlaps,
+                minProci
+            );
 
+            label nContains = 0;
+            forAll(procBb_, proci)
+            {
+                if (procContains[proci])
+                {
+                    nContains++;
+                    dynSendMap[proci].append(samplei);
+                    distSqr[samplei] = best.second();
+                }
+            }
+            if (nContains == 0)
+            {
+                nOutside++;
+                // Sample is outside all bb. Choices:
+                //  - send to all processors
+                //  - send to single processor
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+                //forAll(procOverlaps[proci])
+                //{
+                //    if (procOverlaps[proci])
+                //    {
+                //        dynSendMap[proci].append(samplei);
+                //        distSqr[samplei] = best.second();
+                //    }
+                //}
+                if (minProci != -1)
+                {
+                    dynSendMap[minProci].append(samplei);
+                    distSqr[samplei] = best.second();
+                }
+            }
+        }
 
-const Foam::globalIndex& Foam::distributedTriSurfaceMesh::globalTris() const
-{
-    if (!globalTris_.valid())
-    {
-        globalTris_.reset(new globalIndex(triSurface::size()));
+        labelListList sendMap(Pstream::nProcs());
+        forAll(sendMap, proci)
+        {
+            sendMap[proci].transfer(dynSendMap[proci]);
+        }
+        map1Ptr.set(new mapDistribute(std::move(sendMap)));
     }
-    return *globalTris_;
-}
+    const mapDistribute& map1 = map1Ptr();
 
 
-void Foam::distributedTriSurfaceMesh::findNearest
-(
-    const pointField& samples,
-    const scalarField& nearestDistSqr,
-    List<pointIndexHit>& info
-) const
-{
-    const indexedOctree<treeDataTriSurface>& octree = tree();
+    if (debug)
+    {
+        Pout<< "Pass1:"
+            << " of " << samples.size() << " samples sending to" << endl;
+        label nSend = 0;
+        forAll(map1.subMap(), proci)
+        {
+            Pout<< "    " << proci << "\t" << map1.subMap()[proci].size()
+                << endl;
+            nSend += map1.subMap()[proci].size();
+        }
+        Pout<< "    sum\t" << nSend << endl
+            << "    outside\t" << nOutside << endl;
+    }
 
-    // Important:force synchronised construction of indexing
-    const globalIndex& triIndexer = globalTris();
 
+    List<nearestAndDist> nearestInfo;
+    {
+        // Get the points I need to test and test locally
+        pointField localPoints(samples);
+        map1.distribute(localPoints);
+        scalarField localDistSqr(distSqr);
+        map1.distribute(localDistSqr);
+        List<pointIndexHit> localInfo;
+        triSurfaceMesh::findNearest(localPoints, localDistSqr, localInfo);
+        convertTriIndices(localInfo);
+
+        // Pack into structure for combining information from multiple
+        // processors
+        nearestInfo.setSize(localInfo.size());
+        nearestInfo = nearestAndDist(pointIndexHit(), Foam::sqr(GREAT));
+
+        label nHit = 0;
+        label nIgnoredHit = 0;
+
+        forAll(nearestInfo, i)
+        {
+            const pointIndexHit& info = localInfo[i];
+            if (info.hit())
+            {
+                nHit++;
 
-    // Initialise
-    // ~~~~~~~~~~
+                if
+                (
+                    surfaceClosed_
+                && !contains(procBb_[Pstream::myProcNo()], info.hitPoint())
+                )
+                {
+                    // Nearest point is not on local processor so the
+                    // the triangle is only there because some other bit
+                    // of it is on it. Assume there is another processor that
+                    // holds the full surrounding of the triangle so we can
+                    // ignore this particular nearest.
+                    nIgnoredHit++;
+                }
+                else
+                {
+                    nearestAndDist& ni = nearestInfo[i];
+                    ni.first() = info;
+                    ni.second() = magSqr(localPoints[i]-info.hitPoint());
+                }
+            }
+        }
 
-    info.setSize(samples.size());
-    forAll(info, i)
-    {
-        info[i].setMiss();
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::findNearest :"
+                << " searched locally for " << localPoints.size()
+                << " samples with max sphere "
+                << (localDistSqr.size() ? Foam::sqrt(max(localDistSqr)) : Zero)
+                << " found hits:" << nHit
+                << " of which outside local bb:" << nIgnoredHit
+                << endl;
+        }
     }
 
+    // Send back to originating processor. Choose best if sent to multiple
+    // processors. Note that afterwards all unused entries have the unique
+    // value nearestZero (distance < 0). This is used later on to see if
+    // the sample was sent to any processor.
+    mapDistributeBase::distribute
+    (
+        Pstream::commsTypes::nonBlocking,
+        List<labelPair>(0),
+        samples.size(),
+        map1.constructMap(),
+        map1.constructHasFlip(),
+        map1.subMap(),
+        map1.subHasFlip(),
+        nearestInfo,
+        nearestEqOp(),
+        noOp(),             // no flipping
+        nearestZero
+    );
 
 
-    // Do any local queries
-    // ~~~~~~~~~~~~~~~~~~~~
+    // 2. Test samples on other processor(s) that overlap
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    label nLocal = 0;
+    // Now we have (in nearestInfo) for every input sample the current best
+    // hit (on the processor that originates the sample). See if we can
+    // improve it by sending the queries to any other processors
 
+    autoPtr<mapDistribute> map2Ptr;
     {
+        List<DynamicList<label>> dynSendMap(Pstream::nProcs());
+
         // Work array - whether processor bb overlaps the bounding sphere.
         boolList procBbOverlaps(Pstream::nProcs());
 
-        forAll(samples, i)
+        label nFound = 0;
+
+        forAll(nearestInfo, samplei)
         {
-            // Find the processor this sample+radius overlaps.
-            label nProcs = calcOverlappingProcs
+            const point& sample = samples[samplei];
+            const nearestAndDist& ni = nearestInfo[samplei];
+            const pointIndexHit& info = ni.first();
+
+            if (info.hit())
+            {
+                nFound++;
+            }
+
+            scalar d2 =
             (
-                samples[i],
-                nearestDistSqr[i],
-                procBbOverlaps
+                info.hit()
+              ? ni.second()
+              : distSqr[samplei]
+            );
+
+            label hitProci =
+            (
+                info.hit()
+              ? triIndexer.whichProcID(info.index())
+              : -1
             );
 
-            // Overlaps local processor?
-            if (procBbOverlaps[Pstream::myProcNo()])
+            // Find the processors this sample+radius overlaps.
+            calcOverlappingProcs(sample, d2, procBbOverlaps);
+
+            forAll(procBbOverlaps, proci)
             {
-                info[i] = octree.findNearest(samples[i], nearestDistSqr[i]);
-                if (info[i].hit())
+                if (procBbOverlaps[proci])
                 {
-                    info[i].setIndex(triIndexer.toGlobal(info[i].index()));
-                }
-                if (nProcs == 1)
-                {
-                    // Fully local
-                    nLocal++;
+                    // Check this sample wasn't already handled above. This
+                    // could be improved since the sample might have been
+                    // searched on multiple processors. We now only exclude the
+                    // processor where the point was inside.
+                    if (proci != hitProci)
+                    {
+                        dynSendMap[proci].append(samplei);
+                    }
                 }
             }
         }
+
+        labelListList sendMap(Pstream::nProcs());
+        forAll(sendMap, proci)
+        {
+            sendMap[proci].transfer(dynSendMap[proci]);
+        }
+        map2Ptr.reset(new mapDistribute(std::move(sendMap)));
     }
 
+    const mapDistribute& map2 = map2Ptr();
 
-    if
+    if (debug)
+    {
+        Pout<< "Pass2:"
+            << " of " << samples.size() << " samples sending to" << endl;
+        label nSend = 0;
+        forAll(map2.subMap(), proci)
+        {
+            Pout<< "    " << proci << "\t" << map2.subMap()[proci].size()
+                << endl;
+            nSend += map2.subMap()[proci].size();
+        }
+        Pout<< "    sum\t" << nSend << endl;
+    }
+
+    // Send samples and current best distance
+    pointField localSamples(samples);
+    map2.distribute(localSamples);
+    scalarField localDistSqr(distSqr);
+    forAll(nearestInfo, samplei)
+    {
+        const nearestAndDist& ni = nearestInfo[samplei];
+        if (ni.first().hit())
+        {
+            localDistSqr[samplei] = ni.second();
+        }
+    }
+    map2.distribute(localDistSqr);
+
+    // Do local test
+    List<pointIndexHit> localInfo;
+    triSurfaceMesh::findNearest(localSamples, localDistSqr, localInfo);
+    convertTriIndices(localInfo);
+
+    // Pack and send back
+    List<nearestAndDist> localBest(localSamples.size());
+    label nHit = 0;
+    label nIgnoredHit = 0;
+    forAll(localInfo, i)
+    {
+        const pointIndexHit& info = localInfo[i];
+        if (info.hit())
+        {
+            nHit++;
+            if
+            (
+                surfaceClosed_
+            && !contains(procBb_[Pstream::myProcNo()], info.hitPoint())
+            )
+            {
+                // See above
+                nIgnoredHit++;
+            }
+            else
+            {
+                nearestAndDist& ni = localBest[i];
+                ni.first() = info;
+                ni.second() = magSqr(info.hitPoint()-localSamples[i]);
+            }
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::findNearest :"
+            << " searched locally for " << localSamples.size()
+            << " samples with max sphere "
+            << (localDistSqr.size() ? Foam::sqrt(max(localDistSqr)) : Zero)
+            << " found hits:" << nHit
+            << " of which outside local bb:" << nIgnoredHit
+            << endl;
+    }
+
+    mapDistributeBase::distribute
     (
-        Pstream::parRun()
-     && (
-            returnReduce(nLocal, sumOp<label>())
-          < returnReduce(samples.size(), sumOp<label>())
-        )
-    )
+        Pstream::commsTypes::nonBlocking,
+        List<labelPair>(0),
+        samples.size(),
+        map2.constructMap(),
+        map2.constructHasFlip(),
+        map2.subMap(),
+        map2.subHasFlip(),
+        localBest,
+        nearestEqOp(),
+        noOp(),             // no flipping
+        nearestZero
+    );
+
+    // Combine with nearestInfo
+    info.setSize(samples.size());
+    forAll(samples, samplei)
+    {
+        nearestAndDist ni(nearestInfo[samplei]);
+        nearestEqOp()(ni, localBest[samplei]);
+
+        info[samplei] = ni.first();
+    }
+}
+
+
+void Foam::distributedTriSurfaceMesh::findNearest
+(
+    const pointField& samples,
+    const scalarField& nearestDistSqr,
+    const labelList& regionIndices,
+    List<pointIndexHit>& info
+) const
+{
+    if (!Pstream::parRun())
+    {
+        triSurfaceMesh::findNearest
+        (
+            samples,
+            nearestDistSqr,
+            regionIndices,
+            info
+        );
+        return;
+    }
+
+    addProfiling
+    (
+        findNearestRegion,
+        "distributedTriSurfaceMesh::findNearestRegion"
+    );
+
+    if (debug)
     {
-        // Not all can be resolved locally. Build queries and map, send over
-        // queries, do intersections, send back and merge.
+        Pout<< "distributedTriSurfaceMesh::findNearest :"
+            << " trying to find nearest and region for " << samples.size()
+            << " samples with max sphere "
+            << (samples.size() ? Foam::sqrt(max(nearestDistSqr)) : Zero)
+            << endl;
+    }
 
+    if (regionIndices.empty())
+    {
+        findNearest(samples, nearestDistSqr, info);
+    }
+    else
+    {
         // Calculate queries and exchange map
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -1551,6 +3443,7 @@ void Foam::distributedTriSurfaceMesh::findNearest
         (
             calcLocalQueries
             (
+                true,      // also send to local processor
                 samples,
                 nearestDistSqr,
 
@@ -1573,16 +3466,37 @@ void Foam::distributedTriSurfaceMesh::findNearest
         // ~~~~~~~~~~~
 
         List<pointIndexHit> allInfo(allCentres.size());
+        triSurfaceMesh::findNearest
+        (
+            allCentres,
+            allRadiusSqr,
+            regionIndices,
+            allInfo
+        );
+        convertTriIndices(allInfo);
+
         forAll(allInfo, i)
         {
-            allInfo[i] = octree.findNearest
-            (
-                allCentres[i],
-                allRadiusSqr[i]
-            );
             if (allInfo[i].hit())
             {
-                allInfo[i].setIndex(triIndexer.toGlobal(allInfo[i].index()));
+                if
+                (
+                    surfaceClosed_
+                && !contains
+                    (
+                        procBb_[Pstream::myProcNo()],
+                        allInfo[i].hitPoint()
+                    )
+                )
+                {
+                    // Nearest point is not on local processor so the
+                    // the triangle is only there because some other bit of it
+                    // is on it. Assume there is another processor that holds
+                    // the full surrounding of the triangle so we can clear
+                    // this particular nearest.
+                    allInfo[i].setMiss();
+                    allInfo[i].setIndex(-1);
+                }
             }
         }
 
@@ -1632,13 +3546,20 @@ void Foam::distributedTriSurfaceMesh::findLine
     List<pointIndexHit>& info
 ) const
 {
-    findLine
-    (
-        true,   // nearestIntersection
-        start,
-        end,
-        info
-    );
+    if (!Pstream::parRun())
+    {
+        triSurfaceMesh::findLine(start, end, info);
+    }
+    else
+    {
+        findLine
+        (
+            true,   // nearestIntersection
+            start,
+            end,
+            info
+        );
+    }
 }
 
 
@@ -1649,13 +3570,20 @@ void Foam::distributedTriSurfaceMesh::findLineAny
     List<pointIndexHit>& info
 ) const
 {
-    findLine
-    (
-        true,   // nearestIntersection
-        start,
-        end,
-        info
-    );
+    if (!Pstream::parRun())
+    {
+        triSurfaceMesh::findLineAny(start, end, info);
+    }
+    else
+    {
+        findLine
+        (
+            true,   // nearestIntersection
+            start,
+            end,
+            info
+        );
+    }
 }
 
 
@@ -1666,6 +3594,25 @@ void Foam::distributedTriSurfaceMesh::findLineAll
     List<List<pointIndexHit>>& info
 ) const
 {
+    if (!Pstream::parRun())
+    {
+        triSurfaceMesh::findLineAll(start, end, info);
+        return;
+    }
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::findLineAll :"
+            << " intersecting with "
+            << start.size() << " rays" << endl;
+    }
+
+    addProfiling
+    (
+        findLineAll,
+        "distributedTriSurfaceMesh::findLineAll"
+    );
+
     // Reuse fineLine. We could modify all of findLine to do multiple
     // intersections but this would mean a lot of data transferred so
     // for now we just find nearest intersection and retest from that
@@ -1792,6 +3739,12 @@ void Foam::distributedTriSurfaceMesh::findLineAll
             break;
         }
     }
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::findLineAll :"
+            << " finished intersecting with "
+            << start.size() << " rays" << endl;
+    }
 }
 
 
@@ -1801,6 +3754,15 @@ void Foam::distributedTriSurfaceMesh::getRegion
     labelList& region
 ) const
 {
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getRegion :"
+            << " getting region for "
+            << info.size() << " triangles" << endl;
+    }
+
+    addProfiling(getRegion, "distributedTriSurfaceMesh::getRegion");
+
     if (!Pstream::parRun())
     {
         region.setSize(info.size());
@@ -1815,10 +3777,17 @@ void Foam::distributedTriSurfaceMesh::getRegion
                 region[i] = -1;
             }
         }
+
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::getRegion :"
+                << " finished getting region for "
+                << info.size() << " triangles" << endl;
+        }
+
         return;
     }
 
-
     // Get query data (= local index of triangle)
     // ~~~~~~~~~~~~~~
 
@@ -1852,6 +3821,13 @@ void Foam::distributedTriSurfaceMesh::getRegion
     // ~~~~~~~~~~~~~~~~~
 
     map.reverseDistribute(info.size(), region);
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getRegion :"
+            << " finished getting region for "
+            << info.size() << " triangles" << endl;
+    }
 }
 
 
@@ -1867,6 +3843,15 @@ void Foam::distributedTriSurfaceMesh::getNormal
         return;
     }
 
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getNormal :"
+            << " getting normal for "
+            << info.size() << " triangles" << endl;
+    }
+
+    addProfiling(getNormal, "distributedTriSurfaceMesh::getNormal");
+
 
     // Get query data (= local index of triangle)
     // ~~~~~~~~~~~~~~
@@ -1901,6 +3886,345 @@ void Foam::distributedTriSurfaceMesh::getNormal
     // ~~~~~~~~~~~~~~~~~
 
     map.reverseDistribute(info.size(), normal);
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getNormal :"
+            << " finished getting normal for "
+            << info.size() << " triangles" << endl;
+    }
+}
+
+
+//void Foam::distributedTriSurfaceMesh::getVolumeTypeUncached
+//(
+//    const pointField& samples,
+//    List<volumeType>& volType
+//) const
+//{
+//    if (!Pstream::parRun())
+//    {
+//        triSurfaceMesh::getVolumeType(samples, volType);
+//        return;
+//    }
+//
+//
+//    if (!hasVolumeType())
+//    {
+//        FatalErrorInFunction
+//            << "Volume type only supported for closed distributed surfaces."
+//            << exit(FatalError);
+//    }
+//
+//    // Trigger (so parallel synchronised) construction of outside type.
+//    // Normally this would get triggered from inside individual searches
+//    // so would not be parallel synchronised
+//    if (outsideVolType_ == volumeType::UNKNOWN)
+//    {
+//        // Determine nearest (in parallel)
+//        const point outsidePt(bounds().max() + 0.5*bounds().span());
+//        if (debug)
+//        {
+//            Pout<< "distributedTriSurfaceMesh::outsideVolumeType :"
+//                << " triggering outsidePoint" << outsidePt
+//                << " orientation" << endl;
+//        }
+//
+//        const pointField outsidePts(1, outsidePt);
+//        List<pointIndexHit> nearestInfo;
+//        findNearest
+//        (
+//            outsidePts,
+//            scalarField(1, Foam::sqr(GREAT)),
+//            nearestInfo
+//        );
+//
+//        List<volumeType> outsideVolTypes;
+//        surfaceSide(outsidePts, nearestInfo, outsideVolTypes);
+//        outsideVolType_ = outsideVolTypes[0];
+//
+//        if (debug)
+//        {
+//            Pout<< "distributedTriSurfaceMesh::outsideVolumeType :"
+//                << " determined outsidePoint" << outsidePt
+//                << " to be " << volumeType::names[outsideVolType_] << endl;
+//        }
+//    }
+//
+//    // Determine nearest (in parallel)
+//    List<pointIndexHit> nearestInfo(samples.size());
+//    findNearest
+//    (
+//        samples,
+//        scalarField(samples.size(), Foam::sqr(GREAT)),
+//        nearestInfo
+//    );
+//
+//    // Determine orientation (in parallel)
+//    surfaceSide(samples, nearestInfo, volType);
+//}
+
+
+void Foam::distributedTriSurfaceMesh::getVolumeType
+(
+    const pointField& samples,
+    List<volumeType>& volType
+) const
+{
+    if (!Pstream::parRun())
+    {
+        triSurfaceMesh::getVolumeType(samples, volType);
+        return;
+    }
+
+
+    if (!hasVolumeType())
+    {
+        FatalErrorInFunction
+            << "Volume type only supported for closed distributed surfaces."
+            << exit(FatalError);
+    }
+
+    // Trigger (so parallel synchronised) construction of outside type.
+    // Normally this would get triggered from inside individual searches
+    // so would not be parallel synchronised
+    if (outsideVolType_ == volumeType::UNKNOWN)
+    {
+        addProfiling
+        (
+            getVolumeType,
+            "distributedTriSurfaceMesh::getCachedVolumeType"
+        );
+
+        // Determine nearest (in parallel)
+        const point outsidePt(bounds().max() + 0.5*bounds().span());
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+                << " triggering outsidePoint" << outsidePt
+                << " orientation" << endl;
+        }
+
+        const pointField outsidePts(1, outsidePt);
+        List<pointIndexHit> nearestInfo;
+        findNearest
+        (
+            outsidePts,
+            scalarField(1, Foam::sqr(GREAT)),
+            nearestInfo
+        );
+
+        List<volumeType> outsideVolTypes;
+        surfaceSide(outsidePts, nearestInfo, outsideVolTypes);
+        outsideVolType_ = outsideVolTypes[0];
+
+        if (debug)
+        {
+            Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+                << " determined outsidePoint" << outsidePt
+                << " to be " << volumeType::names[outsideVolType_] << endl;
+        }
+
+        if
+        (
+            outsideVolType_ == volumeType::INSIDE
+         || outsideVolType_ == volumeType::OUTSIDE
+        )
+        {
+            // Get local tree
+            const indexedOctree<treeDataTriSurface>& t = tree();
+            PackedList<2>& nt = t.nodeTypes();
+            const List<indexedOctree<treeDataTriSurface>::node>& nodes =
+                t.nodes();
+            nt.setSize(nodes.size());
+            nt = volumeType::UNKNOWN;
+
+            // Collect midpoints
+            DynamicField<point> midPoints(label(0.5*nodes.size()));
+            collectLeafMids(0, midPoints);
+
+            if (debug)
+            {
+                Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+                    << " triggering orientation caching for "
+                    << midPoints.size() << " leaf mids" << endl;
+            }
+
+            // Get volume type of mid points
+            List<volumeType> midVolTypes;
+            getVolumeType(midPoints, midVolTypes);
+
+            // Cache on local tree
+            label index = 0;
+            calcVolumeType
+            (
+                midVolTypes,
+                index,
+                nt,
+                0               // nodeI
+            );
+            if (debug)
+            {
+                Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+                    << " done orientation caching for "
+                    << midPoints.size() << " leaf mids" << endl;
+            }
+        }
+    }
+
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+            << " finding orientation for " << samples.size()
+            << " samples" << endl;
+    }
+
+    addProfiling
+    (
+        getVolumeType,
+        "distributedTriSurfaceMesh::getVolumeType"
+    );
+
+
+    DynamicList<label> outsideSamples;
+
+    // Distribute samples to relevant processors
+    autoPtr<mapDistribute> mapPtr;
+    {
+        labelListList sendMap(Pstream::nProcs());
+        {
+            // 1. Count
+            labelList nSend(Pstream::nProcs(), 0);
+            forAll(samples, samplei)
+            {
+                // Find the processors this sample overlaps.
+                label nOverlap = 0;
+                forAll(procBb_, proci)
+                {
+                    if (contains(procBb_[proci], samples[samplei]))
+                    {
+                        nSend[proci]++;
+                        nOverlap++;
+                    }
+                }
+
+                // Special case: point is outside all bbs. These would not
+                // get sent to anyone so handle locally. Note that is the
+                // equivalent of the test in triSurfaceMesh against the local
+                // tree bb
+                if (nOverlap == 0)
+                {
+                    outsideSamples.append(samplei);
+                }
+            }
+
+            forAll(nSend, proci)
+            {
+                sendMap[proci].setSize(nSend[proci]);
+            }
+            nSend = 0;
+
+            // 2. Fill
+            forAll(samples, samplei)
+            {
+                // Find the processors this sample overlaps.
+                forAll(procBb_, proci)
+                {
+                    if (contains(procBb_[proci], samples[samplei]))
+                    {
+                        labelList& procSend = sendMap[proci];
+                        procSend[nSend[proci]++] = samplei;
+                    }
+                }
+            }
+        }
+
+        mapPtr.set(new mapDistribute(std::move(sendMap)));
+    }
+    const mapDistribute& map = mapPtr();
+
+    // Get the points I need to test
+    pointField localPoints(samples);
+    map.distribute(localPoints);
+
+    volType.setSize(localPoints.size());
+    volType = volumeType::UNKNOWN;
+
+    // Split the local queries into those that I can look up on the tree and
+    // those I need to search the nearest for
+    DynamicField<point> fullSearchPoints(localPoints.size());
+    DynamicList<label> fullSearchMap(localPoints.size());
+    forAll(localPoints, i)
+    {
+        volType[i] = cachedVolumeType(0, localPoints[i]);
+        if (volType[i] == volumeType::UNKNOWN)
+        {
+            fullSearchMap.append(i);
+            fullSearchPoints.append(localPoints[i]);
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+            << " original samples:" << samples.size()
+            << " resulting in local queries:"
+            << localPoints.size()
+            << " of which cached:" << localPoints.size()-fullSearchPoints.size()
+            << endl;
+    }
+
+    // Determine nearest (in parallel)
+    List<pointIndexHit> nearestInfo;
+    findNearest
+    (
+        fullSearchPoints,
+        scalarField(fullSearchPoints.size(), Foam::sqr(GREAT)),
+        nearestInfo
+    );
+
+    // Determine orientation (in parallel)
+    List<volumeType> fullSearchType;
+    surfaceSide(fullSearchPoints, nearestInfo, fullSearchType);
+
+    // Combine
+    forAll(fullSearchMap, i)
+    {
+        volType[fullSearchMap[i]] = fullSearchType[i];
+    }
+
+    // Send back to originator. In case of multiple answers choose inside or
+    // outside
+    const volumeType zero(volumeType::UNKNOWN);
+    mapDistributeBase::distribute
+    (
+        Pstream::commsTypes::nonBlocking,
+        List<labelPair>(0),
+        samples.size(),
+        map.constructMap(),
+        map.constructHasFlip(),
+        map.subMap(),
+        map.subHasFlip(),
+        volType,
+        volumeCombineOp(),
+        noOp(),           // no flipping
+        zero
+    );
+
+
+    // Add the points outside the bounding box
+    for (label samplei : outsideSamples)
+    {
+        volType[samplei] = outsideVolType_;
+    }
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getVolumeType :"
+            << " finished finding orientation for " << samples.size()
+            << " samples" << endl;
+    }
 }
 
 
@@ -1916,6 +4240,15 @@ void Foam::distributedTriSurfaceMesh::getField
         return;
     }
 
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getField :"
+            << " retrieving field for "
+            << info.size() << " triangles" << endl;
+    }
+
+    addProfiling(getField, "distributedTriSurfaceMesh::getField");
+
     const auto* fldPtr = findObject<triSurfaceLabelField>("values");
 
     if (fldPtr)
@@ -1954,33 +4287,26 @@ void Foam::distributedTriSurfaceMesh::getField
 
         map.reverseDistribute(info.size(), values);
     }
-}
 
-
-void Foam::distributedTriSurfaceMesh::getVolumeType
-(
-    const pointField& points,
-    List<volumeType>& volType
-) const
-{
-    FatalErrorInFunction
-        << "Volume type not supported for distributed surfaces."
-        << exit(FatalError);
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::getField :"
+            << " finished retrieving field for "
+            << info.size() << " triangles" << endl;
+    }
 }
 
 
-// Subset the part of surface that is overlapping bb.
-Foam::triSurface Foam::distributedTriSurfaceMesh::overlappingSurface
+void Foam::distributedTriSurfaceMesh::overlappingSurface
 (
     const triSurface& s,
     const List<treeBoundBox>& bbs,
-
-    labelList& subPointMap,
-    labelList& subFaceMap
+    boolList& includedFace
 )
 {
     // Determine what triangles to keep.
-    boolList includedFace(s.size(), false);
+    includedFace.setSize(s.size());
+    includedFace = false;
 
     // Create a slightly larger bounding box.
     List<treeBoundBox> bbsX(bbs.size());
@@ -2006,7 +4332,22 @@ Foam::triSurface Foam::distributedTriSurfaceMesh::overlappingSurface
             includedFace[trii] = true;
         }
     }
+}
+
+
+// Subset the part of surface that is overlapping bb.
+Foam::triSurface Foam::distributedTriSurfaceMesh::overlappingSurface
+(
+    const triSurface& s,
+    const List<treeBoundBox>& bbs,
 
+    labelList& subPointMap,
+    labelList& subFaceMap
+)
+{
+    // Determine what triangles to keep.
+    boolList includedFace;
+    overlappingSurface(s, bbs, includedFace);
     return subsetMesh(s, includedFace, subPointMap, subFaceMap);
 }
 
@@ -2019,6 +4360,22 @@ void Foam::distributedTriSurfaceMesh::distribute
     autoPtr<mapDistribute>& pointMap
 )
 {
+    if (!Pstream::parRun())
+    {
+        return;
+    }
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::distribute :"
+            << " distributing surface according to method:"
+            << distributionTypeNames_[distType_]
+            << " follow bbs:" << flatOutput(bbs) << endl;
+    }
+
+    addProfiling(distribute, "distributedTriSurfaceMesh::distribute");
+
+
     // Get bbs of all domains
     // ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2038,6 +4395,11 @@ void Foam::distributedTriSurfaceMesh::distribute
             break;
 
             case INDEPENDENT:
+            case DISTRIBUTED:
+                if (currentDistType_ == distType_)
+                {
+                    return;
+                }
                 newProcBb = independentlyDistributedBbs(*this);
             break;
 
@@ -2097,13 +4459,6 @@ void Foam::distributedTriSurfaceMesh::distribute
             pointSendMap[proci],
             faceSendMap[proci]
         );
-
-        if (debug)
-        {
-            //Pout<< "Overlapping with proc " << proci
-            //    << " faces:" << faceSendMap[proci].size()
-            //    << " points:" << pointSendMap[proci].size() << endl << endl;
-        }
     }
 
     if (keepNonLocal)
@@ -2147,15 +4502,8 @@ void Foam::distributedTriSurfaceMesh::distribute
     // Send over how many faces/points i need to receive
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    labelListList faceSendSizes(Pstream::nProcs());
-    faceSendSizes[Pstream::myProcNo()].setSize(Pstream::nProcs());
-    forAll(faceSendMap, proci)
-    {
-        faceSendSizes[Pstream::myProcNo()][proci] = faceSendMap[proci].size();
-    }
-    Pstream::gatherList(faceSendSizes);
-    Pstream::scatterList(faceSendSizes);
-
+    labelList faceRecvSizes;
+    Pstream::exchangeSizes(faceSendMap, faceRecvSizes);
 
 
     // Exchange surfaces
@@ -2204,11 +4552,11 @@ void Foam::distributedTriSurfaceMesh::distribute
 
     PstreamBuffers pBufs(Pstream::defaultCommsType);
 
-    forAll(faceSendSizes, proci)
+    forAll(faceSendMap, proci)
     {
         if (proci != Pstream::myProcNo())
         {
-            if (faceSendSizes[Pstream::myProcNo()][proci] > 0)
+            if (faceSendMap[proci].size() > 0)
             {
                 UOPstream str(proci, pBufs);
 
@@ -2222,15 +4570,6 @@ void Foam::distributedTriSurfaceMesh::distribute
                         pointMap
                     )
                 );
-
-                //if (debug)
-                //{
-                //    Pout<< "Sending to " << proci
-                //        << " faces:" << faceSendMap[proci].size()
-                //        << " points:" << subSurface.points().size() << endl
-                //        << endl;
-                //}
-
                 str << subSurface;
             }
         }
@@ -2242,25 +4581,17 @@ void Foam::distributedTriSurfaceMesh::distribute
     // Receive and merge all
     // ~~~~~~~~~~~~~~~~~~~~~
 
-    forAll(faceSendSizes, proci)
+    forAll(faceRecvSizes, proci)
     {
         if (proci != Pstream::myProcNo())
         {
-            if (faceSendSizes[proci][Pstream::myProcNo()] > 0)
+            if (faceRecvSizes[proci] > 0)
             {
                 UIPstream str(proci, pBufs);
 
                 // Receive
                 triSurface subSurface(str);
 
-                //if (debug)
-                //{
-                //    Pout<< "Received from " << proci
-                //        << " faces:" << subSurface.size()
-                //        << " points:" << subSurface.points().size() << endl
-                //        << endl;
-                //}
-
                 // Merge into allSurf
                 merge
                 (
@@ -2273,12 +4604,6 @@ void Foam::distributedTriSurfaceMesh::distribute
                     faceConstructMap[proci],
                     pointConstructMap[proci]
                 );
-
-                //if (debug)
-                //{
-                //    Pout<< "Current merged surface : faces:" << allTris.size()
-                //        << " points:" << allPoints.size() << endl << endl;
-                //}
             }
         }
     }
@@ -2306,11 +4631,13 @@ void Foam::distributedTriSurfaceMesh::distribute
     // Construct triSurface. Reuse storage.
     triSurface::operator=(triSurface(allTris, patches(), allPoints, true));
 
+    // Clear trees, preserve topological info (surfaceClosed, outsidePointType)
     clearOut();
 
     // Set the bounds() value to the boundBox of the undecomposed surface
     bounds() = boundBox(points(), true);
 
+    currentDistType_ = distType_;
 
     // Regions stays same
     // Volume type stays same.
@@ -2338,20 +4665,49 @@ void Foam::distributedTriSurfaceMesh::distribute
         }
         Info<< endl;
 
-        OBJstream str(searchableSurface::time().path()/"after.obj");
-        Info<< "Writing local bounding box to " << str.name() << endl;
-        const List<treeBoundBox>& myBbs = procBb_[Pstream::myProcNo()];
-        forAll(myBbs, i)
+        if (debug & 2)
+        {
+            OBJstream str(searchableSurface::time().path()/"after.obj");
+            Info<< "Writing local bounding box to " << str.name() << endl;
+            const List<treeBoundBox>& myBbs = procBb_[Pstream::myProcNo()];
+            forAll(myBbs, i)
+            {
+                pointField pts(myBbs[i].points());
+                const edgeList& es = treeBoundBox::edges;
+                forAll(es, ei)
+                {
+                    const edge& e = es[ei];
+                    str.write(linePointRef(pts[e[0]], pts[e[1]]));
+                }
+            }
+        }
+        if (debug & 2)
         {
-            pointField pts(myBbs[i].points());
-            const edgeList& es = treeBoundBox::edges;
-            forAll(es, ei)
+            OBJstream str(searchableSurface::time().path()/"after_all.obj");
+            Info<< "Writing all bounding boxes to " << str.name() << endl;
+            for (auto myBbs : procBb_)
             {
-                const edge& e = es[ei];
-                str.write(linePointRef(pts[e[0]], pts[e[1]]));
+                forAll(myBbs, i)
+                {
+                    pointField pts(myBbs[i].points());
+                    const edgeList& es = treeBoundBox::edges;
+                    forAll(es, ei)
+                    {
+                        const edge& e = es[ei];
+                        str.write(linePointRef(pts[e[0]], pts[e[1]]));
+                    }
+                }
             }
         }
     }
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::distribute :"
+            << " done distributing surface according to method:"
+            << distributionTypeNames_[distType_]
+            << " follow bbs:" << flatOutput(bbs) << endl;
+    }
 }
 
 
@@ -2363,6 +4719,12 @@ bool Foam::distributedTriSurfaceMesh::writeObject
     const bool valid
 ) const
 {
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::writeObject :"
+            << " writing surface valid:" << valid << endl;
+    }
+
     // Make sure dictionary goes to same directory as surface
     const_cast<fileName&>(dict_.instance()) = searchableSurface::instance();
 
@@ -2387,7 +4749,15 @@ bool Foam::distributedTriSurfaceMesh::writeObject
     }
 
     // Dictionary needs to be written in ascii - binary output not supported.
-    return dict_.writeObject(IOstream::ASCII, ver, cmp, true);
+    bool ok = dict_.writeObject(IOstream::ASCII, ver, cmp, true);
+
+    if (debug)
+    {
+        Pout<< "distributedTriSurfaceMesh::writeObject :"
+            << " done writing surface" << endl;
+    }
+
+    return ok;
 }
 
 
@@ -2401,7 +4771,10 @@ void Foam::distributedTriSurfaceMesh::writeStats(Ostream& os) const
     os  << "Triangles    : " << returnReduce(triSurface::size(), sumOp<label>())
         << endl
         << "Vertices     : " << returnReduce(nPoints, sumOp<label>()) << endl
-        << "Bounding Box : " << bb << endl;
+        << "Bounding Box : " << bb << endl
+        << "Closed       : " << surfaceClosed_ << endl
+        << "Outside point: " << volumeType::names[outsideVolType_] << endl
+        << "Distribution : " << distributionTypeNames_[distType_] << endl;
 }
 
 
diff --git a/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.H b/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.H
index 174819bda46..95ffdce4ea8 100644
--- a/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.H
+++ b/src/parallel/distributed/distributedTriSurfaceMesh/distributedTriSurfaceMesh.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
                             | Copyright (C) 2011-2017 OpenFOAM Foundation
@@ -42,6 +42,15 @@ Description
     more communication.
     - frozen : no change
 
+    Note: addressing used:
+    distributedTriSurfaceMesh: none
+
+    triSurfaceMesh:
+    - surf.pointFaces()     : edge addressing (for volume tests only)
+    - surf.edges()          : edgeTree
+    - surf.faceFaces()      : only if minQuality > 0
+
+
 SourceFiles
     distributedTriSurfaceMesh.C
 
@@ -55,6 +64,7 @@ SourceFiles
 #include "IOdictionary.H"
 #include "Pair.H"
 #include "globalIndex.H"
+#include "DynamicField.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -86,7 +96,8 @@ public:
         {
             FOLLOW = 0,
             INDEPENDENT = 1,
-            FROZEN = 2
+            DISTRIBUTED = 2,
+            FROZEN = 3
         };
 
         static const Enum<distributionType> distributionTypeNames_;
@@ -112,14 +123,22 @@ private:
         //- Global triangle numbering
         mutable autoPtr<globalIndex> globalTris_;
 
-        //- The distribution type.
+        //- The (wanted) distribution type.
         distributionType distType_;
 
+        //- The (current) distribution type. Used to trigger re-distribution
+        //  when starting from undecomposed surface.
+        distributionType currentDistType_;
+
 
     // Private Member Functions
 
         // Read
 
+            //- Search for io.local directory (=triSurface) either in case
+            //  directory or in parent directory
+            static word findLocalInstance(const IOobject& io);
+
             //- Read my additional data
             bool read();
 
@@ -183,6 +202,9 @@ private:
 
         // Triangle index
 
+            //- Helper: convert local triangle indices to global ones
+            void convertTriIndices(List<pointIndexHit>& info) const;
+
             //- Obtains global indices from pointIndexHit and swaps them back
             //  to their original processor. Used to calculate local region
             //  and normal.
@@ -195,6 +217,24 @@ private:
 
         // Nearest
 
+            //- Is location inside any of the bounding boxes
+            bool contains
+            (
+                const List<treeBoundBox>& bbs,
+                const point& sample
+            ) const;
+
+            //- Is location inside any of the processors bb or if not
+            //  does it overlap
+            Tuple2<label, scalar> findBestProcs
+            (
+                const point& centre,
+                const scalar radiusSqr,
+                boolList& procContains,
+                boolList& procOverlaps,
+                label& minProci
+            ) const;
+
             label calcOverlappingProcs
             (
                 const point& centre,
@@ -202,8 +242,10 @@ private:
                 boolList& overlaps
             ) const;
 
+            //- Calculate map to send centres+radius to processors
             autoPtr<mapDistribute> calcLocalQueries
             (
+                const bool includeLocalProcessor,
                 const pointField& centres,
                 const scalarField& radiusSqr,
 
@@ -213,8 +255,71 @@ private:
             ) const;
 
 
+        // Side
+
+            //- Side of nearest point w.r.t. edge between face0 and face1
+            volumeType edgeSide
+            (
+                const point& sample,
+                const point& nearestPoint,
+                const label face0,
+                const label face1
+            ) const;
+
+            //- Find edge-connected face
+            label findOtherFace
+            (
+                const labelListList& pointFaces,
+                const label nearFacei,
+                const label nearLabel
+            ) const;
+
+            //- Side of nearest point on faces w.r.t. samples. Handles nearest
+            //  on edge/point
+            void surfaceSide
+            (
+                const pointField& samples,
+                const List<pointIndexHit>& nearestInfo,
+                List<volumeType>& region
+            ) const;
+
+
+            // Caching of volume type (based on indexedOctree)
+
+            //- Collect mid points of tree boxes
+            void collectLeafMids
+            (
+                const label nodeI,
+                DynamicField<point>& midPoints
+            ) const;
+
+            //- Find volume type of tree boxes
+            volumeType calcVolumeType
+            (
+                const List<volumeType>& midPointTypes,
+                label& index,
+                PackedList<2>& nodeTypes,
+                const label nodeI
+            ) const;
+
+            //- Look up any cached data. Return unknown if cannot be determined.
+            volumeType cachedVolumeType
+            (
+                const label nodeI,
+                const point& sample
+            ) const;
+
+
         // Surface redistribution
 
+            //- Calculate face-faces
+            static void calcFaceFaces
+            (
+                const triSurface& s,
+                const labelListList& pointFaces,
+                labelListList& faceFaces
+            );
+
             //- Finds new bounds based on an independent decomposition.
             List<List<treeBoundBox>> independentlyDistributedBbs
             (
@@ -344,13 +449,6 @@ public:
 
         // searchableSurface implementation
 
-            //- Whether supports volume type below. I.e. whether is closed.
-            //  Not supported.
-            virtual bool hasVolumeType() const
-            {
-                return false;
-            }
-
             //- Range of global indices that can be returned.
             virtual label globalSize() const
             {
@@ -364,6 +462,16 @@ public:
                 List<pointIndexHit>&
             ) const;
 
+            //- Find the nearest locations for the supplied points to a
+            //  particular region in the searchable surface.
+            virtual void findNearest
+            (
+                const pointField& samples,
+                const scalarField& nearestDistSqr,
+                const labelList& regionIndices,
+                List<pointIndexHit>& info
+            ) const;
+
             virtual void findLine
             (
                 const pointField& start,
@@ -434,6 +542,14 @@ public:
             //  indices) get the specified field. Misses do not get set.
             virtual void getField(const List<pointIndexHit>&, labelList&) const;
 
+            //- Calculate the triangles that are overlapping bounds.
+            static void overlappingSurface
+            (
+                const triSurface&,
+                const List<treeBoundBox>&,
+                boolList& includedFace
+            );
+
             //- Subset the part of surface that is overlapping bounds.
             static triSurface overlappingSurface
             (
diff --git a/tutorials/incompressible/simpleFoam/motorBike/Allrun b/tutorials/incompressible/simpleFoam/motorBike/Allrun
index e0e6038798e..4654c95afe1 100755
--- a/tutorials/incompressible/simpleFoam/motorBike/Allrun
+++ b/tutorials/incompressible/simpleFoam/motorBike/Allrun
@@ -19,7 +19,8 @@ runApplication $decompDict decomposePar
 if foamDictionary -entry geometry -value system/snappyHexMeshDict | \
    grep -q distributedTriSurfaceMesh
 then
-    runParallel $decompDict surfaceRedistributePar motorBike.obj independent
+    echo "surfaceRedistributePar does not need to be run anymore"
+    echo " - distributedTriSurfaceMesh will do on-the-fly redistribution"
 fi
 
 runParallel $decompDict snappyHexMesh -overwrite
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allclean b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allclean
new file mode 100755
index 00000000000..a13b9c2ab8d
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allclean
@@ -0,0 +1,9 @@
+#!/bin/sh
+cd ${0%/*} || exit 1                        # Run from this directory
+. $WM_PROJECT_DIR/bin/tools/CleanFunctions  # Tutorial clean functions
+
+cleanCase
+
+(cd constant/triSurface && rm -f box_12_*.obj box.obj box_trans.obj)
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allrun b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allrun
new file mode 100755
index 00000000000..f3be65de24c
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/Allrun
@@ -0,0 +1,53 @@
+#!/bin/sh
+cd ${0%/*} || exit 1                        # Run from this directory
+. $WM_PROJECT_DIR/bin/tools/RunFunctions    # Tutorial run functions
+
+runApplication blockMesh
+
+# Create fine surface
+runApplication -s 1 surfaceRefineRedGreen \
+    constant/triSurface/box_12.obj constant/triSurface/box_12_1.obj
+
+runApplication -s 2 surfaceRefineRedGreen \
+    constant/triSurface/box_12_1.obj constant/triSurface/box_12_2.obj
+
+runApplication -s 3 surfaceRefineRedGreen \
+    constant/triSurface/box_12_2.obj constant/triSurface/box_12_3.obj
+
+runApplication -s 4 surfaceRefineRedGreen \
+    constant/triSurface/box_12_3.obj constant/triSurface/box_12_4.obj
+
+runApplication -s 5 surfaceRefineRedGreen \
+    constant/triSurface/box_12_4.obj constant/triSurface/box_12_5.obj
+
+runApplication -s 6 surfaceRefineRedGreen \
+    constant/triSurface/box_12_5.obj constant/triSurface/box_12_6.obj
+
+runApplication -s 7 surfaceRefineRedGreen \
+    constant/triSurface/box_12_6.obj constant/triSurface/box_12_7.obj
+
+runApplication -s 8 surfaceRefineRedGreen \
+    constant/triSurface/box_12_7.obj constant/triSurface/box_12_8.obj
+
+runApplication -s 9 surfaceRefineRedGreen \
+    constant/triSurface/box_12_8.obj constant/triSurface/box_12_9.obj
+
+runApplication -s 10 surfaceRefineRedGreen \
+    constant/triSurface/box_12_9.obj constant/triSurface/box.obj
+
+
+runApplication surfaceTransformPoints \
+    -rotate '((1 0 0)(1 0.8 0.9))' \
+    -translate '(0.1 0.101 0.103)' \
+    constant/triSurface/box.obj constant/triSurface/box_trans.obj
+
+
+# Run non-parallel
+runApplication snappyHexMesh
+
+# Run parallel
+runApplication decomposePar
+
+runParallel -s parallel snappyHexMesh
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/constant/triSurface/box_12.obj b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/constant/triSurface/box_12.obj
new file mode 100644
index 00000000000..d3e271f814c
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/constant/triSurface/box_12.obj
@@ -0,0 +1,34 @@
+# Wavefront OBJ file written 2018-09-13T12:59:07
+o box_12
+
+# points : 8
+# faces  : 12
+# zones  : 1
+#   0  patch0  (nFaces: 12)
+
+# <points count="8">
+v -0.420753 -0.63726 -0.408494
+v 0.228766 -0.76226 0.341506
+v 0.420753 0.63726 0.408494
+v -0.228766 0.76226 -0.341506
+v -0.204247 -0.0122595 0.841506
+v 0.204247 0.0122595 -0.841506
+v -0.853766 0.11274 0.0915064
+v 0.853766 -0.11274 -0.0915064
+# </points>
+
+# <faces count="12">
+g patch0
+f 7 1 5
+f 2 5 1
+f 8 6 3
+f 4 3 6
+f 8 2 6
+f 1 6 2
+f 7 5 4
+f 3 4 5
+f 4 6 7
+f 1 7 6
+f 2 8 5
+f 3 5 8
+# </faces>
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/blockMeshDict b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/blockMeshDict
new file mode 100644
index 00000000000..961bf87e809
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/blockMeshDict
@@ -0,0 +1,98 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      blockMeshDict;
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+scale   1;
+
+vertices
+(
+    (-1 -1  -1)
+    ( 1 -1  -1)
+    ( 1  1  -1)
+    (-1  1  -1)
+    (-1 -1   1)
+    ( 1 -1   1)
+    ( 1  1   1)
+    (-1  1   1)
+);
+
+blocks
+(
+    hex (0 1 2 3 4 5 6 7) (4 4 4) simpleGrading (1 1 1)
+);
+
+edges
+(
+);
+
+boundary
+(
+    maxY
+    {
+        type patch;
+        faces
+        (
+            (3 7 6 2)
+        );
+    }
+
+    minX
+    {
+        type patch;
+        faces
+        (
+            (0 4 7 3)
+        );
+    }
+
+    maxX
+    {
+        type patch;
+        faces
+        (
+            (2 6 5 1)
+        );
+    }
+
+    minY
+    {
+        type patch;
+        faces
+        (
+            (1 5 4 0)
+        );
+    }
+
+    minZ
+    {
+        type patch;
+        faces
+        (
+            (0 3 2 1)
+        );
+    }
+
+    maxZ
+    {
+        type patch;
+        faces
+        (
+            (4 5 6 7)
+        );
+    }
+);
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/controlDict b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/controlDict
new file mode 100644
index 00000000000..a0d442b8909
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/controlDict
@@ -0,0 +1,57 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806 s                               |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      controlDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+DebugSwitches
+{
+    //triSurfaceMesh              1;
+    //distributedTriSurfaceMesh   1;
+    //triSurface                  1;
+    //PrimitivePatch              1;
+}
+
+application     snappyHexMesh;
+
+startFrom       startTime;
+
+startTime       0;
+
+stopAt          endTime;
+
+endTime         2000;
+
+deltaT          1;
+
+writeControl    timeStep;
+
+writeInterval   100;
+
+purgeWrite      0;
+
+writeFormat     binary;
+
+writePrecision  6;
+
+writeCompression off;
+
+timeFormat      general;
+
+timePrecision   6;
+
+runTimeModifiable true;
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/decomposeParDict b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/decomposeParDict
new file mode 100644
index 00000000000..daa976064a2
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/decomposeParDict
@@ -0,0 +1,27 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      decomposeParDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+numberOfSubdomains 8;
+
+method          hierarchical;
+
+coeffs
+{
+    n           (2 2 2);
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSchemes b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSchemes
new file mode 100644
index 00000000000..8ef0651ea17
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSchemes
@@ -0,0 +1,57 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      fvSchemes;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+ddtSchemes
+{
+    default         steadyState;
+}
+
+gradSchemes
+{
+    default         Gauss linear;
+}
+
+divSchemes
+{
+    default         none;
+
+    div(phi,U)      bounded Gauss upwind;
+    div(phi,T)      bounded Gauss upwind;
+    div(phi,k)      bounded Gauss upwind;
+    div(phi,epsilon) bounded Gauss upwind;
+    div(phi,R)      bounded Gauss upwind;
+    div(R)          Gauss linear;
+    div((nuEff*dev(T(grad(U))))) Gauss linear;
+}
+
+laplacianSchemes
+{
+    default         Gauss linear limited corrected 0.333;
+}
+
+interpolationSchemes
+{
+    default         linear;
+}
+
+snGradSchemes
+{
+    default         limited corrected 0.333;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSolution b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSolution
new file mode 100644
index 00000000000..b8ea24dd900
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/fvSolution
@@ -0,0 +1,69 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      fvSolution;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+solvers
+{
+    p_rgh
+    {
+        solver          PCG;
+        preconditioner  DIC;
+        tolerance       1e-08;
+        relTol          0.01;
+    }
+
+    "(U|T|k|epsilon)"
+    {
+        solver          PBiCGStab;
+        preconditioner  DILU;
+        tolerance       1e-07;
+        relTol          0.1;
+    }
+}
+
+SIMPLE
+{
+    nNonOrthogonalCorrectors 2;
+    pRefCell        0;
+    pRefValue       0;
+
+    residualControl
+    {
+        p_rgh           1e-2;
+        U               1e-4;
+        T               1e-3;
+
+        // possibly check turbulence fields
+        "(k|epsilon|omega)" 1e-3;
+    }
+}
+
+relaxationFactors
+{
+    fields
+    {
+        p_rgh           0.7;
+    }
+    equations
+    {
+        U               0.2;
+        T               0.5;
+        "(k|epsilon)"   0.7;
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/meshQualityDict b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/meshQualityDict
new file mode 100644
index 00000000000..f94681c006d
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/meshQualityDict
@@ -0,0 +1,21 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      meshQualityDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Include defaults parameters from master dictionary
+#includeEtc "caseDicts/meshQualityDict"
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/snappyHexMeshDict b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/snappyHexMeshDict
new file mode 100644
index 00000000000..d54c3807f57
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/distributedTriSurfaceMesh/system/snappyHexMeshDict
@@ -0,0 +1,287 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1806                               |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      snappyHexMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Which of the steps to run
+castellatedMesh true;
+snap            true;
+addLayers       false;
+
+
+// Geometry. Definition of all surfaces. All surfaces are of class
+// searchableSurface.
+// Surfaces are used
+// - to specify refinement for any mesh cell intersecting it
+// - to specify refinement for any mesh cell inside/outside/near
+// - to 'snap' the mesh boundary to the surface
+geometry
+{
+    box
+    {
+        type distributedTriSurfaceMesh;
+        file "box.obj"; //"box_12_2.obj";
+    }
+    box_trans
+    {
+        file "box_trans.obj";
+        type distributedTriSurfaceMesh;
+    }
+};
+
+
+
+// Settings for the castellatedMesh generation.
+castellatedMeshControls
+{
+
+    // Refinement parameters
+    // ~~~~~~~~~~~~~~~~~~~~~
+
+    // If local number of cells is >= maxLocalCells on any processor
+    // switches from from refinement followed by balancing
+    // (current method) to (weighted) balancing before refinement.
+    maxLocalCells 100000;
+
+    // Overall cell limit (approximately). Refinement will stop immediately
+    // upon reaching this number so a refinement level might not complete.
+    // Note that this is the number of cells before removing the part which
+    // is not 'visible' from the keepPoint. The final number of cells might
+    // actually be a lot less.
+    maxGlobalCells 20000000;
+
+    // The surface refinement loop might spend lots of iterations refining just a
+    // few cells. This setting will cause refinement to stop if <= minimumRefine
+    // are selected for refinement. Note: it will at least do one iteration
+    // (unless the number of cells to refine is 0)
+    minRefinementCells 0;
+
+    // Number of buffer layers between different levels.
+    // 1 means normal 2:1 refinement restriction, larger means slower
+    // refinement.
+    nCellsBetweenLevels 1;
+
+
+
+    // Explicit feature edge refinement
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Specifies a level for any cell intersected by its edges.
+    // This is a featureEdgeMesh, read from constant/triSurface for now.
+    features
+    (
+    );
+
+
+
+    // Surface based refinement
+    // ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Specifies two levels for every surface. The first is the minimum level,
+    // every cell intersecting a surface gets refined up to the minimum level.
+    // The second level is the maximum level. Cells that 'see' multiple
+    // intersections where the intersections make an
+    // angle > resolveFeatureAngle get refined up to the maximum level.
+
+    refinementSurfaces
+    {
+        box
+        {
+            // Surface-wise min and max refinement level
+            level (4 4);
+        }
+        box_trans
+        {
+            level (5 5);
+            faceZone box_trans;
+            cellZone box_trans;
+            cellZoneInside inside;
+        }
+    }
+
+    // Resolve sharp angles
+    resolveFeatureAngle 60;
+
+
+    // Region-wise refinement
+    // ~~~~~~~~~~~~~~~~~~~~~~
+
+    // Specifies refinement level for cells in relation to a surface. One of
+    // three modes
+    // - distance. 'levels' specifies per distance to the surface the
+    //   wanted refinement level. The distances need to be specified in
+    //   descending order.
+    // - inside. 'levels' is only one entry and only the level is used. All
+    //   cells inside the surface get refined up to the level. The surface
+    //   needs to be closed for this to be possible.
+    // - outside. Same but cells outside.
+
+    refinementRegions
+    {
+    }
+
+
+    // Mesh selection
+    // ~~~~~~~~~~~~~~
+
+    // After refinement patches get added for all refinementSurfaces and
+    // all cells intersecting the surfaces get put into these patches. The
+    // section reachable from the locationInMesh is kept.
+    // NOTE: This point should never be on a face, always inside a cell, even
+    // after refinement.
+    locationInMesh (1e-5 1e-5 1e-5);
+
+
+    // Whether any faceZones (as specified in the refinementSurfaces)
+    // are only on the boundary of corresponding cellZones or also allow
+    // free-standing zone faces. Not used if there are no faceZones.
+    allowFreeStandingZoneFaces false;
+}
+
+
+
+// Settings for the snapping.
+snapControls
+{
+    //- Number of patch smoothing iterations before finding correspondence
+    //  to surface
+    nSmoothPatch 3;
+
+    //- Relative distance for points to be attracted by surface feature point
+    //  or edge. True distance is this factor times local
+    //  maximum edge length.
+    tolerance 1.0;
+
+    //- Number of mesh displacement relaxation iterations.
+    nSolveIter 30;
+
+    //- Maximum number of snapping relaxation iterations. Should stop
+    //  before upon reaching a correct mesh.
+    nRelaxIter 5;
+
+
+    // Feature snapping
+
+        //- Number of feature edge snapping iterations.
+        //  Leave out altogether to disable.
+        nFeatureSnapIter 2;
+
+        //- Detect (geometric) features by sampling the surface (default=false)
+        implicitFeatureSnap true;
+
+        //- Use castellatedMeshControls::features (default = true)
+        explicitFeatureSnap false;
+}
+
+
+
+// Settings for the layer addition.
+addLayersControls
+{
+    // Are the thickness parameters below relative to the undistorted
+    // size of the refined cell outside layer (true) or absolute sizes (false).
+    relativeSizes true;
+
+    // Per final patch (so not geometry!) the layer information
+    layers
+    {
+    }
+
+    // Expansion factor for layer mesh
+    expansionRatio 1.0;
+
+    // Wanted thickness of final added cell layer. If multiple layers
+    // is the thickness of the layer furthest away from the wall.
+    // Relative to undistorted size of cell outside layer.
+    // is the thickness of the layer furthest away from the wall.
+    // See relativeSizes parameter.
+    finalLayerThickness 0.5;
+
+    // Minimum thickness of cell layer. If for any reason layer
+    // cannot be above minThickness do not add layer.
+    // Relative to undistorted size of cell outside layer.
+    // See relativeSizes parameter.
+    minThickness 0.25;
+
+    // If points get not extruded do nGrow layers of connected faces that are
+    // also not grown. This helps convergence of the layer addition process
+    // close to features.
+    // Note: changed(corrected) w.r.t 1.7.x! (didn't do anything in 1.7.x)
+    nGrow 0;
+
+
+    // Advanced settings
+
+    // When not to extrude surface. 0 is flat surface, 90 is when two faces
+    // are perpendicular
+    featureAngle 60;
+
+    // Maximum number of snapping relaxation iterations. Should stop
+    // before upon reaching a correct mesh.
+    nRelaxIter 5;
+
+    // Number of smoothing iterations of surface normals
+    nSmoothSurfaceNormals 1;
+
+    // Number of smoothing iterations of interior mesh movement direction
+    nSmoothNormals 3;
+
+    // Smooth layer thickness over surface patches
+    nSmoothThickness 10;
+
+    // Stop layer growth on highly warped cells
+    maxFaceThicknessRatio 0.5;
+
+    // Reduce layer growth where ratio thickness to medial
+    // distance is large
+    maxThicknessToMedialRatio 0.3;
+
+    // Angle used to pick up medial axis points
+    // Note: changed(corrected) w.r.t 16x! 90 degrees corresponds to 130 in 16x.
+    minMedianAxisAngle 90;
+
+    // Create buffer region for new layer terminations
+    nBufferCellsNoExtrude 0;
+
+
+    // Overall max number of layer addition iterations. The mesher will exit
+    // if it reaches this number of iterations; possibly with an illegal
+    // mesh.
+    nLayerIter 50;
+}
+
+
+
+// Generic mesh quality settings. At any undoable phase these determine
+// where to undo.
+meshQualityControls
+{
+    #include "meshQualityDict"
+
+    // Advanced
+
+    //- Number of error distribution iterations
+    nSmoothScale 4;
+    //- amount to scale back displacement at error points
+    errorReduction 0.75;
+}
+
+
+// Advanced
+
+// Merge tolerance. Is fraction of overall bounding box of initial mesh.
+// Note: the write tolerance needs to be higher than this.
+mergeTolerance 1e-6;
+
+// ************************************************************************* //
-- 
GitLab