diff --git a/applications/test/searchableSphere/Make/files b/applications/test/searchableSphere/Make/files
new file mode 100644
index 0000000000..6f2f01cd45
--- /dev/null
+++ b/applications/test/searchableSphere/Make/files
@@ -0,0 +1,3 @@
+Test-searchableSphere.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-searchableSphere
diff --git a/applications/test/searchableSphere/Make/options b/applications/test/searchableSphere/Make/options
new file mode 100644
index 0000000000..b6fa4bff16
--- /dev/null
+++ b/applications/test/searchableSphere/Make/options
@@ -0,0 +1,9 @@
+EXE_INC = \
+ -I$(LIB_SRC)/fileFormats/lnInclude \
+ -I$(LIB_SRC)/surfMesh/lnInclude \
+ -I$(LIB_SRC)/meshTools/lnInclude
+
+EXE_LIBS = \
+ -lfileFormats \
+ -lsurfMesh \
+ -lmeshTools
diff --git a/applications/test/searchableSphere/Test-searchableSphere.C b/applications/test/searchableSphere/Test-searchableSphere.C
new file mode 100644
index 0000000000..5522aa5457
--- /dev/null
+++ b/applications/test/searchableSphere/Test-searchableSphere.C
@@ -0,0 +1,226 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2020 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 .
+
+Application
+ Test-searchableSphere
+
+Description
+ Basic tests for searchable sphere
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "Time.H"
+#include "searchableSphere.H"
+#include "unitConversion.H"
+#include "Random.H"
+
+using namespace Foam;
+using namespace Foam::constant::mathematical;
+
+//- Evaluate using implicit form of the spheroid equation.
+scalar evaluate(const point& p, const searchableSphere& sph)
+{
+ return
+ (
+ sqr((p.x() - sph.centre().x()) / sph.radii().x())
+ + sqr((p.y() - sph.centre().y()) / sph.radii().y())
+ + sqr((p.z() - sph.centre().z()) / sph.radii().z())
+ );
+}
+
+
+void doTest1(const searchableSphere& sph)
+{
+ Info<< nl << "origin:" << sph.centre() << " radius:" << sph.radius();
+
+ if (sph.shape() == searchableSphere::SPHERE)
+ {
+ Info<< " [sphere]" << nl;
+ }
+ else
+ {
+ Info<< " radii:" << sph.radii() << nl;
+ }
+
+// boundBox bb(point(0, 0, 0), point(30, 30, 30));
+// Info<< "overlaps: " << Switch(sph.overlaps(bb)) << endl;
+
+ Pair angles(-pi/2, pi/2);
+
+ point surfPt = sph.surfacePoint(angles.first(), angles.second());
+ vector norm = sph.surfaceNormal(angles.first(), angles.second());
+
+ Info<< "point at ("
+ << radToDeg(angles.first()) << ' '
+ << radToDeg(angles.second()) << ") deg" << nl;
+
+ Info<< "surface" << nl
+ << " eval: " << evaluate(surfPt, sph) << nl
+ << " point:" << surfPt << nl
+ << " normal:" << norm << nl;
+
+ {
+ List hits(1);
+ vectorField norms;
+
+ hits[0].hitPoint(surfPt, 0);
+
+ sph.getNormal(hits, norms);
+
+ Info<< " normal:" << norms[0] << nl;
+ }
+
+
+ Random rndGen;
+
+ point testPt1 =
+ (
+ surfPt + 0.1*sph.radius()*norm
+ + 0.01*rndGen.sample01()
+ );
+
+
+ // Scale by max radius and shift by origin
+ const auto adjustPoint =
+ [&](point& p) -> void
+ {
+ p *= sph.radius();
+ p += sph.centre();
+ };
+
+
+ List hits;
+ pointField query
+ ({
+ testPt1,
+ point(-2, -2, -2)
+ });
+
+ forAll(query, pointi)
+ {
+ if (pointi) adjustPoint(query[pointi]);
+ }
+
+ sph.findNearest
+ (
+ query,
+ scalarField(query.size(), GREAT),
+ hits
+ );
+
+ Info<< "query:" << nl;
+
+ forAll(hits, pointi)
+ {
+ // Should never miss
+ Info<< " "
+ << query[pointi] << " => "
+ << hits[pointi].hitPoint() << nl;
+ }
+
+
+ pointField lineBeg
+ ({
+ point(-2, -2, -2),
+ point(0,0,0),
+ point(0,0,0),
+ point(0,0,0)
+ });
+ pointField lineEnd
+ ({
+ point(2, 2, 2),
+ point(2, 0, 0),
+ point(0, 2, 0),
+ point(0, 0, 4)
+ });
+
+ for (point& p : lineBeg)
+ {
+ adjustPoint(p);
+ }
+
+ for (point& p : lineEnd)
+ {
+ adjustPoint(p);
+ }
+
+ sph.findLineAny
+ (
+ lineBeg,
+ lineEnd,
+ hits
+ );
+
+ Info<< "lines:" << nl;
+
+ forAll(hits, pointi)
+ {
+ // Should never miss
+ Info<< " "
+ << lineBeg[pointi] << " -> "
+ << lineEnd[pointi] << " = "
+ << hits[pointi].hitPoint() << nl;
+ }
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+
+ argList::noBanner();
+ #include "setRootCase.H"
+
+ // Use dummy Time for objectRegistry
+ autoPtr