diff --git a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
index cc6d7b563e..45310e16d2 100644
--- a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
@@ -31,6 +31,7 @@ License
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+#include "FaceInteraction.H"
#include "FacePostProcessing.H"
#include "ParticleCollector.H"
#include "ParticleErosion.H"
@@ -50,6 +51,7 @@ License
\
makeCloudFunctionObject(CloudType); \
\
+ makeCloudFunctionObjectType(FaceInteraction, CloudType); \
makeCloudFunctionObjectType(FacePostProcessing, CloudType); \
makeCloudFunctionObjectType(ParticleCollector, CloudType); \
makeCloudFunctionObjectType(ParticleErosion, CloudType); \
diff --git a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
index bc5bb9ec07..61e0e413cd 100644
--- a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
@@ -31,6 +31,7 @@ License
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+#include "FaceInteraction.H"
#include "FacePostProcessing.H"
#include "ParticleCollector.H"
#include "ParticleErosion.H"
@@ -53,6 +54,7 @@ License
\
makeCloudFunctionObject(CloudType); \
\
+ makeCloudFunctionObjectType(FaceInteraction, CloudType); \
makeCloudFunctionObjectType(FacePostProcessing, CloudType); \
makeCloudFunctionObjectType(ParticleCollector, CloudType); \
makeCloudFunctionObjectType(ParticleErosion, CloudType); \
diff --git a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
index 635e809c8d..4b641e275b 100644
--- a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
@@ -30,6 +30,7 @@ License
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+#include "FaceInteraction.H"
#include "FacePostProcessing.H"
#include "ParticleCollector.H"
#include "ParticleErosion.H"
@@ -51,6 +52,7 @@ License
\
makeCloudFunctionObject(CloudType); \
\
+ makeCloudFunctionObjectType(FaceInteraction, CloudType); \
makeCloudFunctionObjectType(FacePostProcessing, CloudType); \
makeCloudFunctionObjectType(ParticleCollector, CloudType); \
makeCloudFunctionObjectType(ParticleErosion, CloudType); \
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/FaceInteraction/FaceInteraction.C b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/FaceInteraction/FaceInteraction.C
new file mode 100644
index 0000000000..1f246cbc10
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/FaceInteraction/FaceInteraction.C
@@ -0,0 +1,329 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "FaceInteraction.H"
+#include "faceZoneMesh.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+template
+const Foam::Enum::interactionType>
+Foam::FaceInteraction::interactionTypeNames_
+({
+ { interactionType::STICK, "stick" },
+ { interactionType::ESCAPE, "escape" },
+ { interactionType::REBOUND, "rebound" },
+});
+
+
+// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
+
+template
+bool Foam::FaceInteraction::processParticle
+(
+ const parcelType& p,
+ const label localZonei
+)
+{
+ if (!faceZoneBBs_[localZonei].contains(p.position()))
+ {
+ // Quick reject if the particle is not in the face zone bound box
+ return false;
+ }
+
+ if ((p.d() > dMax_) || (p.d() < dMin_))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+template
+void Foam::FaceInteraction::write()
+{
+ const fvMesh& mesh = this->owner().mesh();
+ const faceZoneMesh& fzm = mesh.faceZones();
+
+ Info<< type() << " output:" << nl;
+
+ // Retrieve any stored data
+ const label nZones = faceZoneIDs_.size();
+ labelList npe0(nZones, Zero);
+ labelList nps0(nZones, Zero);
+ labelList npr0(nZones, Zero);
+
+ this->getModelProperty("nEscape", npe0);
+ this->getModelProperty("nStick", nps0);
+ this->getModelProperty("nRebound", npr0);
+
+ // Accumulate current data
+ labelList npe(returnReduce(nEscapeParticles_, sumOp()));
+ labelList nps(returnReduce(nStickParticles_, sumOp()));
+ labelList npr(returnReduce(nReboundParticles_, sumOp()));
+ forAll(npe, i)
+ {
+ npe[i] = npe[i] + npe0[i];
+ nps[i] = nps[i] + nps0[i];
+ npr[i] = npr[i] + npr0[i];
+ }
+
+
+ // Write to log/file
+ forAll(faceZoneIDs_, i)
+ {
+ const label zonei = faceZoneIDs_[i];
+ Info<< " Zone : " << fzm[zonei].name() << nl
+ << " Escape : " << npe[i] << nl
+ << " Stick : " << nps[i] << nl
+ << " Rebound : " << npr[i] << nl;
+
+ if (this->writeToFile())
+ {
+ auto& os = filePtrs_[i];
+
+ writeCurrentTime(os);
+
+ // Note - writing as scalar for better formatting
+ os << tab << scalar(npe[i])
+ << tab << scalar(nps[i])
+ << tab << scalar(npr[i])
+ << endl;
+ }
+ }
+ Info<< endl;
+
+ // Set restart data
+ this->setModelProperty("nEscape", npe);
+ this->setModelProperty("nStick", nps);
+ this->setModelProperty("nRebound", npr);
+
+ nEscapeParticles_ = Zero;
+ nStickParticles_ = Zero;
+ nReboundParticles_ = Zero;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+template
+Foam::FaceInteraction::FaceInteraction
+(
+ const dictionary& dict,
+ CloudType& owner,
+ const word& modelName
+)
+:
+ CloudFunctionObject(dict, owner, modelName, typeName),
+ functionObjects::writeFile
+ (
+ owner,
+ this->localPath(),
+ typeName,
+ this->coeffDict()
+ ),
+ faceZoneIDs_(),
+ faceZoneBBs_(),
+ faceZoneInteraction_(),
+ filePtrs_(),
+ nEscapeParticles_(),
+ nStickParticles_(),
+ nReboundParticles_(),
+ dMin_(this->coeffDict().getOrDefault("dMin", -GREAT)),
+ dMax_(this->coeffDict().getOrDefault("dMax", GREAT))
+{
+ const List> nameAndInteraction
+ (
+ this->coeffDict().lookup("faceZones")
+ );
+
+ filePtrs_.setSize(nameAndInteraction.size());
+
+ faceZoneBBs_.setSize(nameAndInteraction.size());
+ faceZoneInteraction_.setSize(nameAndInteraction.size());
+
+ DynamicList