diff --git a/python/mesa_pd.py b/python/mesa_pd.py
index 949463d66847775c975ed0425ec70f7b159ff493..d356d40abd4a2b125008f67fbd1b4e248ae97a4d 100755
--- a/python/mesa_pd.py
+++ b/python/mesa_pd.py
@@ -27,7 +27,7 @@ if __name__ == '__main__':
    os.makedirs(args.path + "/src/mesa_pd/mpi/notifications", exist_ok = True)
    os.makedirs(args.path + "/src/mesa_pd/vtk", exist_ok = True)
 
-   shapes = ["Sphere", "HalfSpace"]
+   shapes = ["Sphere", "HalfSpace", "CylindricalBoundary", "Box"]
 
    ps    = data.ParticleStorage()
    ch    = data.ContactHistory()
diff --git a/src/core/math/Rot3.h b/src/core/math/Rot3.h
index 71f8d629c6be292f6efd0ee2cfa7f71a86027ab2..b0ab7ecdbfd2c0a53a660b0009c59d70a92441e3 100644
--- a/src/core/math/Rot3.h
+++ b/src/core/math/Rot3.h
@@ -57,6 +57,7 @@ public:
 
    void rotate(const Vector3<Type>& rot);
    void rotate(const Vector3<Type>& axis, const real_t& angle);
+   void rotate(const Rot3<Type>& rot);
 private:
    Quaternion<Type> quat_;
    Matrix3<Type>    mat_;
@@ -115,6 +116,14 @@ inline void Rot3<Type>::rotate(const Vector3<Type>& axis, const real_t& angle)
    }
 }
 
+template< typename Type >  // floating point type
+inline void Rot3<Type>::rotate(const Rot3<Type>& rot)
+{
+   quat_ = rot.quat_ * quat_;
+   mat_  = quat_.toRotationMatrix();
+   WALBERLA_ASSERT_FLOAT_EQUAL( mat_.getDeterminant(), real_t(1), "Corrupted rotation matrix determinant" );
+}
+
 template< typename Type >  // floating point type
 inline std::ostream& operator<<( std::ostream& os, const Rot3<Type>& r )
 {
diff --git a/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h b/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h
index 62f4f30e5bf8dcbb0b65de3176b16013ef676466..bcf3baf63b2d70d05a9992929cd3f52b4cac67af 100644
--- a/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h
+++ b/src/mesa_pd/collision_detection/AnalyticCollisionFunctions.h
@@ -34,9 +34,9 @@ bool detectSphereSphereCollision( const Vec3&   pos1,
                                   const real_t& radius1,
                                   const Vec3&   pos2,
                                   const real_t& radius2,
-                                        Vec3&   contactPoint,
-                                        Vec3&   contactNormal,
-                                        real_t& penetrationDepth,
+                                  Vec3&   contactPoint,
+                                  Vec3&   contactNormal,
+                                  real_t& penetrationDepth,
                                   const real_t& contactThreshold)
 {
    contactNormal         = pos1 - pos2;
@@ -53,14 +53,128 @@ bool detectSphereSphereCollision( const Vec3&   pos1,
    return false;
 }
 
+inline
+bool detectSphereBoxCollision( const Vec3&   pos1,
+                               const real_t& radius1,
+                               const Vec3&   pos2,
+                               const Vec3&   edgeLength2,
+                               const Rot3&   rot2,
+                               Vec3&   contactPoint,
+                               Vec3&   contactNormal,
+                               real_t& penetrationDepth,
+                               const real_t& contactThreshold)
+{
+   //WALBERLA_ABORT(s << b);
+   const Vec3  l( real_t(0.5)*edgeLength2 );  // Box half side lengths
+   const Mat3& R( rot2.getMatrix() );         // Rotation of the box
+
+   bool outside( false );
+
+   // Calculating the distance between the sphere and the box
+   const Vec3 d( pos1 - pos2 );
+
+   // Calculating the the sphere/box distance in the box's frame of reference
+   Vec3 p( d[0]*R[0] + d[1]*R[3] + d[2]*R[6],
+         d[0]*R[1] + d[1]*R[4] + d[2]*R[7],
+         d[0]*R[2] + d[1]*R[5] + d[2]*R[8] );
+
+   // Projection of the x-component
+   if     ( p[0] < -l[0] ) { p[0] = -l[0]; outside = true; }
+   else if( p[0] >  l[0] ) { p[0] =  l[0]; outside = true; }
+
+   // Projection of the y-component
+   if     ( p[1] < -l[1] ) { p[1] = -l[1]; outside = true; }
+   else if( p[1] >  l[1] ) { p[1] =  l[1]; outside = true; }
+
+   // Projection of the z-component
+   if     ( p[2] < -l[2] ) { p[2] = -l[2]; outside = true; }
+   else if( p[2] >  l[2] ) { p[2] =  l[2]; outside = true; }
+
+   // Special treatment if the sphere's center of mass is inside the box
+   // In this case, a contact at the sphere's center of mass with a normal away from
+   // the closest face of the box is generated.
+   if( !outside )
+   {
+      real_t dist( std::fabs(p[0]) - l[0] );
+      size_t face( 0 );
+
+      for( size_t i=1; i<3; ++i ) {
+         const real_t tmp( std::fabs(p[i]) - l[i] );
+         if( dist < tmp ) {
+            dist = tmp;
+            face = i;
+         }
+      }
+
+      // Calculating the normal direction of the contact
+      Vec3 n;
+      n[face] = math::sign( p[face] );
+      n = R * n;
+
+      // Calculating the contact distance
+      dist -= radius1;
+
+      // Creating a single contact between the sphere and the box
+      contactPoint     = pos1;
+      contactNormal    = n;
+      penetrationDepth = dist;
+      return true;
+   }
+
+   const Vec3 q( R * p );  // Transformation from the projection to the global world frame
+   const Vec3 n( d - q );  // Normal direction of the contact (pointing from the box to the sphere)
+
+   const real_t dist( n.length() - radius1 );  // Distance between the sphere and the box
+
+   // Creating a single contact between the sphere and the box
+   if( dist < contactThreshold )
+   {
+      contactPoint     = pos2+q;
+      contactNormal    = n.getNormalizedOrZero();
+      if (contactNormal.sqrLength() < real_t(0.1))
+      {
+         contactNormal = Vec3(1,0,0);
+      }
+      penetrationDepth = dist;
+      return true;
+   }
+   return false;
+}
+
+inline
+bool detectSphereCylindricalBoundaryCollision( const Vec3&   pos1,
+                                               const real_t& radius1,
+                                               const Vec3&   pos2,
+                                               const real_t& radius2,
+                                               const Vec3&   axis2,
+                                               Vec3&         contactPoint,
+                                               Vec3&         contactNormal,
+                                               real_t&       penetrationDepth,
+                                               const real_t& contactThreshold)
+{
+   WALBERLA_ASSERT_FLOAT_EQUAL( axis2.sqrLength(), real_t(1));
+   WALBERLA_ASSERT_GREATER( radius2, radius1 );
+
+   const Vec3   dist      = (pos1 - pos2) - ((pos1 - pos2) * axis2) * axis2;
+   const real_t effRadius = radius2 - radius1;
+   if( effRadius * effRadius - dist.sqrLength() < contactThreshold ) {
+      contactNormal    = -dist.getNormalized();
+      penetrationDepth = effRadius - dist.length();
+      contactPoint     = ( pos1 - ( radius1 + penetrationDepth ) * contactNormal );
+
+      return true;
+   }
+   return false;
+}
+
 inline
 bool detectSphereHalfSpaceCollision( const Vec3&   pos1,
                                      const real_t& radius1,
                                      const Vec3&   pos2,
                                      const Vec3&   normal2,
-                                           Vec3&   contactPoint,
-                                           Vec3&   contactNormal,
-                                           real_t& penetrationDepth,
+                                     Vec3&   contactPoint,
+                                     Vec3&   contactNormal,
+                                     real_t& penetrationDepth,
                                      const real_t& contactThreshold)
 {
    /**
@@ -86,7 +200,7 @@ bool detectSphereHalfSpaceCollision( const Vec3&   pos1,
 }
 
 
-} //namespace collision_detection
 } //namespace analytic
+} //namespace collision_detection
 } //namespace mesa_pd
 } //namespace walberla
diff --git a/src/mesa_pd/collision_detection/AnalyticContactDetection.h b/src/mesa_pd/collision_detection/AnalyticContactDetection.h
index dd5656bf571fcc53d8824d1dca58fdeadddc4a00..cbc5b34d4168d4368b5fb232cb6b2dccef4251ba 100644
--- a/src/mesa_pd/collision_detection/AnalyticContactDetection.h
+++ b/src/mesa_pd/collision_detection/AnalyticContactDetection.h
@@ -23,6 +23,8 @@
 #include <mesa_pd/collision_detection/AnalyticCollisionFunctions.h>
 #include <mesa_pd/data/DataTypes.h>
 #include <mesa_pd/data/shape/BaseShape.h>
+#include <mesa_pd/data/shape/Box.h>
+#include <mesa_pd/data/shape/CylindricalBoundary.h>
 #include <mesa_pd/data/shape/HalfSpace.h>
 #include <mesa_pd/data/shape/Sphere.h>
 
@@ -72,6 +74,34 @@ public:
                     const data::Sphere& geo2,
                     Accessor& ac);
 
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::Sphere& s,
+                    const data::Box& bx,
+                    Accessor& ac );
+
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::Box& bx,
+                    const data::Sphere& s,
+                    Accessor& ac);
+
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::Sphere& s,
+                    const data::CylindricalBoundary& cb,
+                    Accessor& ac );
+
+   template <typename Accessor>
+   bool operator()( const size_t idx1,
+                    const size_t idx2,
+                    const data::CylindricalBoundary& cb,
+                    const data::Sphere& s,
+                    Accessor& ac);
+
    template <typename Accessor>
    bool operator()( const size_t idx1,
                     const size_t idx2,
@@ -131,6 +161,68 @@ inline bool AnalyticContactDetection::operator()( const size_t idx1,
                                                 getContactThreshold());
 }
 
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::Sphere& s,
+                                                  const data::Box& bx,
+                                                  Accessor& ac )
+{
+   getIdx1() = idx1;
+   getIdx2() = idx2;
+   return analytic::detectSphereBoxCollision(ac.getPosition(getIdx1()),
+                                             s.getRadius(),
+                                             ac.getPosition(getIdx2()),
+                                             bx.getEdgeLength(),
+                                             ac.getRotation(getIdx2()),
+                                             getContactPoint(),
+                                             getContactNormal(),
+                                             getPenetrationDepth(),
+                                             getContactThreshold());
+}
+
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::Box& bx,
+                                                  const data::Sphere& s,
+                                                  Accessor& ac)
+{
+   return operator()(idx2, idx1, s, bx, ac);
+}
+
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::Sphere& s,
+                                                  const data::CylindricalBoundary& cb,
+                                                  Accessor& ac )
+{
+   WALBERLA_ASSERT_UNEQUAL(idx1, idx2, "colliding with itself!");
+
+   getIdx1() = idx1;
+   getIdx2() = idx2;
+   return analytic::detectSphereCylindricalBoundaryCollision(ac.getPosition(getIdx1()),
+                                                             s.getRadius(),
+                                                             ac.getPosition(getIdx2()),
+                                                             cb.getRadius(),
+                                                             cb.getAxis(),
+                                                             getContactPoint(),
+                                                             getContactNormal(),
+                                                             getPenetrationDepth(),
+                                                             getContactThreshold());
+}
+
+template <typename Accessor>
+inline bool AnalyticContactDetection::operator()( const size_t idx1,
+                                                  const size_t idx2,
+                                                  const data::CylindricalBoundary& cb,
+                                                  const data::Sphere& s,
+                                                  Accessor& ac)
+{
+   return operator()(idx2, idx1, s, cb, ac);
+}
+
 template <typename Accessor>
 inline bool AnalyticContactDetection::operator()( const size_t idx1,
                                                   const size_t idx2,
diff --git a/src/mesa_pd/collision_detection/BroadPhase.h b/src/mesa_pd/collision_detection/BroadPhase.h
index 07fe49801d983c29e35988e8437b389b6de8387a..4bad17b24d100f93c28e79b0934cb7e4db090ef2 100644
--- a/src/mesa_pd/collision_detection/BroadPhase.h
+++ b/src/mesa_pd/collision_detection/BroadPhase.h
@@ -20,6 +20,8 @@
 
 #pragma once
 
+#include <mesa_pd/data/Flags.h>
+
 namespace walberla {
 namespace mesa_pd {
 namespace collision_detection {
diff --git a/src/mesa_pd/data/ShapeStorage.h b/src/mesa_pd/data/ShapeStorage.h
index 867098b379a89cc24bb46f75c87deeee4b6c0d81..4ab850ff7ac86d53bb05d0ff21082c2f34465466 100644
--- a/src/mesa_pd/data/ShapeStorage.h
+++ b/src/mesa_pd/data/ShapeStorage.h
@@ -31,6 +31,8 @@
 #include <mesa_pd/data/shape/BaseShape.h>
 #include <mesa_pd/data/shape/Sphere.h>
 #include <mesa_pd/data/shape/HalfSpace.h>
+#include <mesa_pd/data/shape/CylindricalBoundary.h>
+#include <mesa_pd/data/shape/Box.h>
 
 #include <core/Abort.h>
 #include <core/debug/Debug.h>
@@ -58,6 +60,8 @@ struct ShapeStorage
 };
 //Make sure that no two different shapes have the same unique identifier!
 static_assert( Sphere::SHAPE_TYPE != HalfSpace::SHAPE_TYPE, "Shape types have to be different!" );
+static_assert( Sphere::SHAPE_TYPE != CylindricalBoundary::SHAPE_TYPE, "Shape types have to be different!" );
+static_assert( Sphere::SHAPE_TYPE != Box::SHAPE_TYPE, "Shape types have to be different!" );
 
 template <typename ShapeT, typename... Args>
 size_t ShapeStorage::create(Args&&... args)
@@ -75,6 +79,8 @@ ReturnType ShapeStorage::singleDispatch( ParticleStorage& ps, size_t idx, func&
    {
       case Sphere::SHAPE_TYPE : return f(ps, idx, *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()));
       case HalfSpace::SHAPE_TYPE : return f(ps, idx, *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()));
+      case CylindricalBoundary::SHAPE_TYPE : return f(ps, idx, *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idx)].get()));
+      case Box::SHAPE_TYPE : return f(ps, idx, *static_cast<Box*>(shapes[ps.getShapeID(idx)].get()));
       default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idx)]->getShapeType() << ") could not be determined!");
    }
 }
@@ -100,6 +106,16 @@ ReturnType ShapeStorage::doubleDispatch( ParticleStorage& ps, size_t idx, size_t
                                                    *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()),
                                                    idy,
                                                    *static_cast<HalfSpace*>(shapes[ps.getShapeID(idy)].get()));
+            case CylindricalBoundary::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idy)].get()));
+            case Box::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idy)].get()));
             default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
          }
       case HalfSpace::SHAPE_TYPE :
@@ -115,6 +131,66 @@ ReturnType ShapeStorage::doubleDispatch( ParticleStorage& ps, size_t idx, size_t
                                                    *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()),
                                                    idy,
                                                    *static_cast<HalfSpace*>(shapes[ps.getShapeID(idy)].get()));
+            case CylindricalBoundary::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idy)].get()));
+            case Box::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idy)].get()));
+            default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
+         }
+      case CylindricalBoundary::SHAPE_TYPE :
+         switch (shapes[ps.getShapeID(idy)]->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idy)].get()));
+            case HalfSpace::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idy)].get()));
+            case CylindricalBoundary::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idy)].get()));
+            case Box::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idy)].get()));
+            default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
+         }
+      case Box::SHAPE_TYPE :
+         switch (shapes[ps.getShapeID(idy)]->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Sphere*>(shapes[ps.getShapeID(idy)].get()));
+            case HalfSpace::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(shapes[ps.getShapeID(idy)].get()));
+            case CylindricalBoundary::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(shapes[ps.getShapeID(idy)].get()));
+            case Box::SHAPE_TYPE : return f(ps,
+                                                   idx,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idx)].get()),
+                                                   idy,
+                                                   *static_cast<Box*>(shapes[ps.getShapeID(idy)].get()));
             default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idy)]->getShapeType() << ") could not be determined!");
          }
       default : WALBERLA_ABORT("Shape type (" << shapes[ps.getShapeID(idx)]->getShapeType() << ") could not be determined!");
diff --git a/src/mesa_pd/data/shape/Box.h b/src/mesa_pd/data/shape/Box.h
new file mode 100644
index 0000000000000000000000000000000000000000..7a3de3a9a761fffe61cc479dd47fa24df7239125
--- /dev/null
+++ b/src/mesa_pd/data/shape/Box.h
@@ -0,0 +1,65 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file Box.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/shape/BaseShape.h>
+
+#include <core/math/Constants.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class Box : public BaseShape
+{
+public:
+   explicit Box(const Vec3& edgeLength)
+      : BaseShape(Box::SHAPE_TYPE)
+      , edgeLength_(edgeLength)
+   {}
+
+   const Vec3&   getEdgeLength() const   { return edgeLength_; }
+
+   real_t getVolume() const override { return edgeLength_[0] * edgeLength_[1] * edgeLength_[2]; };
+   void   updateMassAndInertia(const real_t density) override;
+
+   static const int SHAPE_TYPE = 3; ///< Unique shape type identifier for boxes.\ingroup mesa_pd_shape
+
+private:
+   Vec3   edgeLength_;   ///< edge length of the box
+};
+
+inline
+void Box::updateMassAndInertia(const real_t density)
+{
+   const real_t m = getVolume() * density;
+   const Mat3   I = Mat3::makeDiagonalMatrix(
+         edgeLength_[1]*edgeLength_[1] + edgeLength_[2]*edgeLength_[2] ,
+         edgeLength_[0]*edgeLength_[0] + edgeLength_[2]*edgeLength_[2] ,
+         edgeLength_[0]*edgeLength_[0] + edgeLength_[1]*edgeLength_[1] ) * (m / static_cast<real_t>( 12 ));
+
+   getInvMass()      = real_t(1.0) / m;
+   getInvInertiaBF() = I.getInverse();
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/data/shape/CylindricalBoundary.h b/src/mesa_pd/data/shape/CylindricalBoundary.h
new file mode 100644
index 0000000000000000000000000000000000000000..dc2f3e67cc816b4ac275e2df7befcf80ec8ac624
--- /dev/null
+++ b/src/mesa_pd/data/shape/CylindricalBoundary.h
@@ -0,0 +1,64 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file CylindricalBoundary.h
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <mesa_pd/data/shape/BaseShape.h>
+
+#include <core/math/Constants.h>
+
+namespace walberla {
+namespace mesa_pd {
+namespace data {
+
+class CylindricalBoundary : public BaseShape
+{
+public:
+   explicit CylindricalBoundary(const real_t& radius, const Vec3& axis)
+      : BaseShape(CylindricalBoundary::SHAPE_TYPE)
+      , radius_(radius)
+      , axis_(axis)
+   {
+      WALBERLA_CHECK_FLOAT_EQUAL(axis.sqrLength(), real_t(1.0), "Axis has to be normalized!");
+   }
+
+   const real_t& getRadius() const { return radius_; }
+   const Vec3&   getAxis() const   { return axis_; }
+
+   real_t getVolume() const override { return std::numeric_limits<real_t>::infinity(); };
+   void   updateMassAndInertia(const real_t density) override;
+
+   static const int SHAPE_TYPE = 2; ///< Unique shape type identifier for cylindrical boundaries.\ingroup mesa_pd_shape
+
+private:
+      real_t radius_; ///< radius of the cylinder
+      Vec3   axis_;   ///< axis of the cylinder
+};
+
+inline
+void CylindricalBoundary::updateMassAndInertia(const real_t /*density*/)
+{
+   getInvMass()      = real_t(0.0);
+   getInvInertiaBF() = Mat3(real_t(0.0));
+}
+
+} //namespace data
+} //namespace mesa_pd
+} //namespace walberla
diff --git a/src/mesa_pd/kernel/DoubleCast.h b/src/mesa_pd/kernel/DoubleCast.h
index 4d0b09ab6dfde3f571cc194fb23bdb95f4e600d3..5b4cee0e212f0cdb259be97fea78b6b1ffc2312d 100644
--- a/src/mesa_pd/kernel/DoubleCast.h
+++ b/src/mesa_pd/kernel/DoubleCast.h
@@ -31,6 +31,8 @@
 #include <mesa_pd/data/shape/BaseShape.h>
 #include <mesa_pd/data/shape/Sphere.h>
 #include <mesa_pd/data/shape/HalfSpace.h>
+#include <mesa_pd/data/shape/CylindricalBoundary.h>
+#include <mesa_pd/data/shape/Box.h>
 
 #include <core/Abort.h>
 #include <core/debug/Debug.h>
@@ -76,6 +78,16 @@ auto DoubleCast::operator()( size_t idx, size_t idy, Accessor& ac, func& f, Args
                                                    *static_cast<Sphere*>(ac.getShape(idx)),
                                                    *static_cast<HalfSpace*>(ac.getShape(idy)),
                                                    std::forward<Args>(args)...);
+            case CylindricalBoundary::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Sphere*>(ac.getShape(idx)),
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case Box::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Sphere*>(ac.getShape(idx)),
+                                                   *static_cast<Box*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
             default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
          }
       case HalfSpace::SHAPE_TYPE :
@@ -91,6 +103,66 @@ auto DoubleCast::operator()( size_t idx, size_t idy, Accessor& ac, func& f, Args
                                                    *static_cast<HalfSpace*>(ac.getShape(idx)),
                                                    *static_cast<HalfSpace*>(ac.getShape(idy)),
                                                    std::forward<Args>(args)...);
+            case CylindricalBoundary::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(ac.getShape(idx)),
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case Box::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<HalfSpace*>(ac.getShape(idx)),
+                                                   *static_cast<Box*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
+         }
+      case CylindricalBoundary::SHAPE_TYPE :
+         switch (ac.getShape(idy)->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idx)),
+                                                   *static_cast<Sphere*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case HalfSpace::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idx)),
+                                                   *static_cast<HalfSpace*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case CylindricalBoundary::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idx)),
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case Box::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idx)),
+                                                   *static_cast<Box*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
+         }
+      case Box::SHAPE_TYPE :
+         switch (ac.getShape(idy)->getShapeType())
+         {
+            case Sphere::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Box*>(ac.getShape(idx)),
+                                                   *static_cast<Sphere*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case HalfSpace::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Box*>(ac.getShape(idx)),
+                                                   *static_cast<HalfSpace*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case CylindricalBoundary::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Box*>(ac.getShape(idx)),
+                                                   *static_cast<CylindricalBoundary*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
+            case Box::SHAPE_TYPE : return f(idx,
+                                                   idy,
+                                                   *static_cast<Box*>(ac.getShape(idx)),
+                                                   *static_cast<Box*>(ac.getShape(idy)),
+                                                   std::forward<Args>(args)...);
             default : WALBERLA_ABORT("Shape type (" << ac.getShape(idy)->getShapeType() << ") could not be determined!");
          }
       default : WALBERLA_ABORT("Shape type (" << ac.getShape(idx)->getShapeType() << ") could not be determined!");
diff --git a/src/mesa_pd/kernel/SingleCast.h b/src/mesa_pd/kernel/SingleCast.h
index 0514d5a6519fcdc26754d42c08fbf18d49f1940f..316fbb62a847fb4fce90b181c263d8e53ff90cf1 100644
--- a/src/mesa_pd/kernel/SingleCast.h
+++ b/src/mesa_pd/kernel/SingleCast.h
@@ -31,6 +31,8 @@
 #include <mesa_pd/data/shape/BaseShape.h>
 #include <mesa_pd/data/shape/Sphere.h>
 #include <mesa_pd/data/shape/HalfSpace.h>
+#include <mesa_pd/data/shape/CylindricalBoundary.h>
+#include <mesa_pd/data/shape/Box.h>
 
 #include <core/Abort.h>
 #include <core/debug/Debug.h>
@@ -64,6 +66,8 @@ auto SingleCast::operator()( size_t idx, Accessor& ac, func& f, Args&&... args )
    {
       case Sphere::SHAPE_TYPE : return f(idx, *static_cast<Sphere*>(ac.getShape(idx)), std::forward<Args>(args)...);
       case HalfSpace::SHAPE_TYPE : return f(idx, *static_cast<HalfSpace*>(ac.getShape(idx)), std::forward<Args>(args)...);
+      case CylindricalBoundary::SHAPE_TYPE : return f(idx, *static_cast<CylindricalBoundary*>(ac.getShape(idx)), std::forward<Args>(args)...);
+      case Box::SHAPE_TYPE : return f(idx, *static_cast<Box*>(ac.getShape(idx)), std::forward<Args>(args)...);
       default : WALBERLA_ABORT("Shape type (" << ac.getShape(idx)->getShapeType() << ") could not be determined!");
    }
 }
diff --git a/tests/mesa_pd/CMakeLists.txt b/tests/mesa_pd/CMakeLists.txt
index 1938034f1336141423ab5b677e64d5aeec8621a2..9abfc2b20472f2decf915335dc799689b9d5ecfd 100644
--- a/tests/mesa_pd/CMakeLists.txt
+++ b/tests/mesa_pd/CMakeLists.txt
@@ -4,6 +4,9 @@
 #
 ###################################################################################################
 
+waLBerla_compile_test( NAME   MESA_PD_COLLISIONDETECTION_AnalyticCollisionFunctions FILES collision_detection/AnalyticCollisionFunctions.cpp DEPENDS core )
+waLBerla_execute_test( NAME   MESA_PD_COLLISIONDETECTION_AnalyticCollisionFunctions )
+
 waLBerla_compile_test( NAME   MESA_PD_COLLISIONDETECTION_AnalyticContactDetection FILES collision_detection/AnalyticContactDetection.cpp DEPENDS core )
 waLBerla_execute_test( NAME   MESA_PD_COLLISIONDETECTION_AnalyticContactDetection )
 
diff --git a/tests/mesa_pd/collision_detection/AnalyticCollisionFunctions.cpp b/tests/mesa_pd/collision_detection/AnalyticCollisionFunctions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1dc914aaea5572e16e06f75cbfeea0430fd02a51
--- /dev/null
+++ b/tests/mesa_pd/collision_detection/AnalyticCollisionFunctions.cpp
@@ -0,0 +1,186 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla 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.
+//
+//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file   AnalyticCollisionFunctions.cpp
+//! \author Sebastian Eibl <sebastian.eibl@fau.de>
+//
+//======================================================================================================================
+
+#include <mesa_pd/collision_detection/AnalyticCollisionFunctions.h>
+#include <mesa_pd/data/DataTypes.h>
+
+#include <core/Environment.h>
+#include <core/logging/Logging.h>
+
+#include <cmath>
+#include <iostream>
+
+namespace walberla {
+namespace mesa_pd {
+
+void checkSphereSphereCollision( )
+{
+   using namespace walberla::mesa_pd::collision_detection::analytic;
+
+   const real_t radius  = real_t(0.5);
+   auto dir = Vec3(real_t(1), real_t(2), real_t(3)).getNormalized();
+   real_t shift = real_c(0.75);
+
+   Vec3   contactPoint;
+   Vec3   contactNormal;
+   real_t penetrationDepth;
+
+   //check two spheres in contact
+   WALBERLA_CHECK( detectSphereSphereCollision( Vec3(real_t(0), real_t(0), real_t(0)),
+                                                radius,
+                                                dir * shift,
+                                                radius,
+                                                contactPoint,
+                                                contactNormal,
+                                                penetrationDepth,
+                                                real_t(0) ) );
+   WALBERLA_CHECK_FLOAT_EQUAL( contactPoint, dir * shift * real_t(0.5));
+   WALBERLA_CHECK_FLOAT_EQUAL( contactNormal, -dir );
+   WALBERLA_CHECK_FLOAT_EQUAL( penetrationDepth, shift - real_t(1) );
+
+   //no collision
+   WALBERLA_CHECK( !detectSphereSphereCollision( Vec3(real_t(0), real_t(0), real_t(0)),
+                                                 radius,
+                                                 dir * real_t(1.1),
+                                                 radius,
+                                                 contactPoint,
+                                                 contactNormal,
+                                                 penetrationDepth,
+                                                 real_t(0) ) );
+}
+
+void checkSphereCylindricalBoundaryCollision( )
+{
+   using namespace walberla::mesa_pd::collision_detection::analytic;
+
+   const real_t radius  = real_t(0.5);
+   auto pos = Vec3(real_t(6), real_t(-2), real_t(2));
+   auto dir = Vec3(real_t(1), real_t(1), real_t(1)).getNormalized();
+
+   Vec3   contactPoint;
+   Vec3   contactNormal;
+   real_t penetrationDepth;
+
+   //check two spheres in contact
+   WALBERLA_CHECK( detectSphereCylindricalBoundaryCollision( pos,
+                                                             radius,
+                                                             Vec3(real_t(0), real_t(0), real_t(0)),
+                                                             real_t(5),
+                                                             dir,
+                                                             contactPoint,
+                                                             contactNormal,
+                                                             penetrationDepth,
+                                                             real_t(0) ) );
+   auto nearestPoint = dot(pos, dir) * dir;
+   WALBERLA_CHECK_FLOAT_EQUAL( contactPoint, (pos - nearestPoint).getNormalized() * real_t(5) + nearestPoint);
+   WALBERLA_CHECK_FLOAT_EQUAL( contactNormal, (nearestPoint - pos).getNormalized() );
+   WALBERLA_CHECK_FLOAT_EQUAL( penetrationDepth, -((pos - nearestPoint).length() + radius - real_t(5)) );
+
+   //no collision
+   WALBERLA_CHECK( !detectSphereCylindricalBoundaryCollision( Vec3(real_t(0), real_t(0), real_t(0)),
+                                                              radius,
+                                                              Vec3(real_t(0), real_t(0), real_t(0)),
+                                                              real_t(5),
+                                                              Vec3(real_t(1), real_t(1), real_t(1)).getNormalized(),
+                                                              contactPoint,
+                                                              contactNormal,
+                                                              penetrationDepth,
+                                                              real_t(0) ) );
+}
+
+void checkSphereBoxCollision( )
+{
+   using namespace walberla::mesa_pd::collision_detection::analytic;
+
+   Vec3   contactPoint;
+   Vec3   contactNormal;
+   real_t penetrationDepth;
+
+   //check two spheres in contact
+   WALBERLA_CHECK( detectSphereBoxCollision( Vec3(real_t(2), real_t(2), real_t(2)),
+                                             real_c(std::sqrt(3)) + real_t(0.1),
+                                             Vec3(real_t(0), real_t(0), real_t(0)),
+                                             Vec3(real_t(2), real_t(2), real_t(2)),
+                                             Rot3(),
+                                             contactPoint,
+                                             contactNormal,
+                                             penetrationDepth,
+                                             real_t(0) ) );
+   WALBERLA_CHECK_FLOAT_EQUAL( contactPoint, Vec3(real_t(1), real_t(1), real_t(1)));
+   WALBERLA_CHECK_FLOAT_EQUAL( contactNormal, Vec3(real_t(2), real_t(2), real_t(2)).getNormalized() );
+   WALBERLA_CHECK_FLOAT_EQUAL( penetrationDepth, -real_t(0.1) );
+}
+
+void checkSphereHalfSpaceCollision( )
+{
+   using namespace walberla::mesa_pd::collision_detection::analytic;
+
+   const real_t radius  = real_t(1.0);
+   auto dir = Vec3(real_t(1), real_t(2), real_t(3)).getNormalized();
+   real_t shift = real_c(0.75);
+
+   Vec3   contactPoint;
+   Vec3   contactNormal;
+   real_t penetrationDepth;
+
+   //check sphere - half space contact
+   WALBERLA_CHECK( detectSphereHalfSpaceCollision( Vec3(real_t(0), real_t(0), real_t(0)),
+                                                   radius,
+                                                   dir * shift,
+                                                   -dir,
+                                                   contactPoint,
+                                                   contactNormal,
+                                                   penetrationDepth,
+                                                   real_t(0) ) );
+   WALBERLA_CHECK_FLOAT_EQUAL( contactPoint, dir * shift * real_t(1.0));
+   WALBERLA_CHECK_FLOAT_EQUAL( contactNormal, -dir );
+   WALBERLA_CHECK_FLOAT_EQUAL( penetrationDepth, shift - real_t(1) );
+
+
+   auto pos = Vec3(shift, real_t(0), real_t(0));
+   WALBERLA_CHECK( detectSphereHalfSpaceCollision( Vec3(real_t(0), real_t(0), real_t(0)),
+                                                   radius,
+                                                   pos,
+                                                   -Vec3(real_t(1), real_t(0), real_t(0)),
+                                                   contactPoint,
+                                                   contactNormal,
+                                                   penetrationDepth,
+                                                   real_t(0) ) );
+   WALBERLA_CHECK_FLOAT_EQUAL( contactPoint, pos);
+   WALBERLA_CHECK_FLOAT_EQUAL( contactNormal, -Vec3(real_t(1), real_t(0), real_t(0)) );
+   WALBERLA_CHECK_FLOAT_EQUAL( penetrationDepth, pos[0] - real_t(1) );
+}
+
+} //namespace mesa_pd
+} //namespace walberla
+
+int main( int argc, char ** argv )
+{
+   walberla::Environment env(argc, argv);
+   WALBERLA_UNUSED(env);
+   walberla::mpi::MPIManager::instance()->useWorldComm();
+
+   walberla::mesa_pd::checkSphereSphereCollision();
+   walberla::mesa_pd::checkSphereCylindricalBoundaryCollision();
+   walberla::mesa_pd::checkSphereBoxCollision();
+   walberla::mesa_pd::checkSphereHalfSpaceCollision();
+
+   return EXIT_SUCCESS;
+}
diff --git a/tests/mesa_pd/kernel/DoubleCast.cpp b/tests/mesa_pd/kernel/DoubleCast.cpp
index ec429e4edf5a0aebdd30b5a8af89943138457ba1..4e2c11cdc050541f58d2109820e47996b7594b3b 100644
--- a/tests/mesa_pd/kernel/DoubleCast.cpp
+++ b/tests/mesa_pd/kernel/DoubleCast.cpp
@@ -48,6 +48,11 @@ public:
 class ShapeTester
 {
 public:
+   void operator()(const size_t /*idx0*/, const size_t /*idx1*/, data::BaseShape& /*hs*/, data::BaseShape& /*hs*/)
+   {
+      WALBERLA_ABORT("Not implemented!");
+   }
+
    void operator()(const size_t idx0, const size_t idx1, data::HalfSpace& /*hs*/, data::HalfSpace& /*hs*/)
    {
       WALBERLA_CHECK_EQUAL(idx0, 0);
diff --git a/tests/mesa_pd/kernel/SingleCast.cpp b/tests/mesa_pd/kernel/SingleCast.cpp
index c8ea556da8ce4f5bef07a993fffe47e5494b632c..cf137df8c68077d14a6c18f5dd464823ef383223 100644
--- a/tests/mesa_pd/kernel/SingleCast.cpp
+++ b/tests/mesa_pd/kernel/SingleCast.cpp
@@ -48,6 +48,11 @@ public:
 class ShapeTester
 {
 public:
+   void operator()(const size_t /*idx*/, data::BaseShape& /*hs*/)
+   {
+      WALBERLA_ABORT("Not implemented!");
+   }
+
    void operator()(const size_t idx, data::HalfSpace& /*hs*/)
    {
       WALBERLA_CHECK_EQUAL(idx, 0);