diff --git a/.gitignore b/.gitignore
index 9dc5287059e5c93198c1cd4418b7fa972676751f..80be18e0c806e91e88bd5ae7fd8a10d5e1b0ea32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@ ui_*
 qrc_*
 *~
 
+# macOS
+**/.DS_Store
+
 # Backup files of kate/kwrite
 
 # Generated files
diff --git a/apps/tutorials/basics/01_BlocksAndFields.cpp b/apps/tutorials/basics/01_BlocksAndFields.cpp
index 7290160ded73ad63e87b97612a69a6ea793d17d5..8b54bf73752b99036b0fa302479e05e6e7ba55a3 100644
--- a/apps/tutorials/basics/01_BlocksAndFields.cpp
+++ b/apps/tutorials/basics/01_BlocksAndFields.cpp
@@ -24,36 +24,30 @@
 #include "gui/Gui.h"
 #include "timeloop/SweepTimeloop.h"
 
-
 using namespace walberla;
 
-
-Field<real_t,1> * createFields( IBlock * const block, StructuredBlockStorage * const storage )
-{
-   return new Field<real_t,1> ( storage->getNumberOfXCells( *block ), // number of cells in x direction for this block
-                                storage->getNumberOfYCells( *block ), // number of cells in y direction for this block
-                                storage->getNumberOfZCells( *block ), // number of cells in z direction for this block
-                                real_c(0) );                          // initial value
+Field<real_t, 1>* createFields(IBlock* const block, StructuredBlockStorage * const storage) {
+   return new Field<real_t,1>(storage->getNumberOfXCells(*block),
+                              storage->getNumberOfYCells(*block),
+                              storage->getNumberOfZCells(*block),
+                              real_c(0));
 }
 
-
 int main( int argc, char ** argv )
 {
    walberla::Environment env( argc, argv );
-
-   // create blocks
-   shared_ptr< StructuredBlockForest > blocks = blockforest::createUniformBlockGrid(
-             uint_c( 3), uint_c(2), uint_c( 4), // number of blocks in x,y,z direction
-             uint_c(10), uint_c(8), uint_c(12), // how many cells per block (x,y,z)
-             real_c(0.5),                       // dx: length of one cell in physical coordinates
-             false,                             // one block per process? - "false" means all blocks to one process
-             false, false, false );             // no periodicity
-
-   // add a field to all blocks
+   
+   shared_ptr<StructuredBlockForest> blocks = blockforest::createUniformBlockGrid(
+                                                                                  uint_c(3), uint_c(2), uint_c(4),
+                                                                                  uint_c(10), uint_c(8), uint_c(12),
+                                                                                  real_c(0.5),
+                                                                                  false,
+                                                                                  false, false, false);
+   
    blocks->addStructuredBlockData< Field<real_t,1> >( &createFields, "My Field" );
 
+   
    SweepTimeloop timeloop( blocks, uint_c(1) );
-
    GUI gui( timeloop, blocks, argc, argv );
    gui.run();
 
diff --git a/apps/tutorials/pe/02_ConfinedGasExtended.cfg b/apps/tutorials/pe/02_ConfinedGasExtended.cfg
index 078bffef5e7c5e9d082e11bb32fb3af2ee63002f..f06e81138cd2e4f879e6d1f04d6267e279ea4598 100644
--- a/apps/tutorials/pe/02_ConfinedGasExtended.cfg
+++ b/apps/tutorials/pe/02_ConfinedGasExtended.cfg
@@ -5,5 +5,27 @@ ConfinedGasExtended
    simulationDomain < 20, 20, 20 >;
    blocks < 2, 2, 2 >;
    isPeriodic < 0, 0, 0 >;
+   setupRun false;
+   simulationSteps 100;
+   visSpacing 10;
+}
 
+Raytracing 
+{
+   image_x 640;
+   image_y 480;
+   fov_vertical 49.13;
+   image_output_directory .;
+   cameraPosition < -25, 10, 10 >;
+   lookAt < -5, 10, 10 >;
+   upVector < 0, 0, 1 >;
+   antiAliasFactor 2;
+   reductionMethod MPI_REDUCE;
+   backgroundColor < 0.18, 0.18, 0.18 >;
+   Lighting {
+      pointLightOrigin < -10, 10, 15 >;
+      ambientColor < 0.3, 0.3, 0.3 >;
+      diffuseColor < 1, 1, 1 >;
+      specularColor < 1, 1, 1 >;
+   }
 }
diff --git a/apps/tutorials/pe/02_ConfinedGasExtended.cpp b/apps/tutorials/pe/02_ConfinedGasExtended.cpp
index da217ac17fb4b79b99c68324228d7a497e8b4b2d..f0e4edc205c368ad609c2683a652defcadc8f5f7 100644
--- a/apps/tutorials/pe/02_ConfinedGasExtended.cpp
+++ b/apps/tutorials/pe/02_ConfinedGasExtended.cpp
@@ -21,6 +21,7 @@
 #include <pe/basic.h>
 #include <pe/statistics/BodyStatistics.h>
 #include <pe/vtk/SphereVtkOutput.h>
+#include <pe/raytracing/Raytracer.h>
 
 #include <core/Abort.h>
 #include <core/Environment.h>
@@ -37,6 +38,7 @@
 using namespace walberla;
 using namespace walberla::pe;
 using namespace walberla::timing;
+using namespace walberla::pe::raytracing;
 
 typedef boost::tuple<Sphere, Plane> BodyTuple ;
 
@@ -168,6 +170,19 @@ int main( int argc, char ** argv )
       syncCallWithoutTT = std::bind( pe::syncShadowOwners<BodyTuple>, std::ref(*forest), storageID, static_cast<WcTimingTree*>(NULL), real_c(0.0), false );
    }
    //! [Bind Sync Call]
+   
+   WALBERLA_LOG_INFO_ON_ROOT("*** RAYTRACER ***");
+   //! [Raytracer Init]
+   std::function<ShadingParameters (const BodyID body)> customShadingFunction = [](const BodyID body) {
+      if (body->getTypeID() == Sphere::getStaticTypeID()) {
+         return processRankDependentShadingParams(body).makeGlossy();
+      }
+      return defaultBodyTypeDependentShadingParams(body);
+   };
+   Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                       cfg->getBlock("Raytracing"),
+                       customShadingFunction);
+   //! [Raytracer Init]
 
    WALBERLA_LOG_INFO_ON_ROOT("*** VTK ***");
    //! [VTK Domain Output]
@@ -244,6 +259,9 @@ int main( int argc, char ** argv )
          vtkDomainOutput->write( );
          vtkSphereOutput->write( );
          //! [VTK Output]
+         //! [Image Output]
+         raytracer.generateImage<BodyTuple>(size_t(i), &tt);
+         //! [Image Output]
       }
    }
    tp["Total"].end();
diff --git a/apps/tutorials/pe/02_ConfinedGasExtended.dox b/apps/tutorials/pe/02_ConfinedGasExtended.dox
index 45c6cc4c9ca90be9cf6bb29f1026ab9df8640be2..89cd4a837becfdef61bc1c66212e73cafafb65f7 100644
--- a/apps/tutorials/pe/02_ConfinedGasExtended.dox
+++ b/apps/tutorials/pe/02_ConfinedGasExtended.dox
@@ -91,6 +91,51 @@ depending on the type of information you want to store.
 You can then dump the information to disc. timing::TimingPool and timing::TimingTree already have predefined save
 functions so you do not have to extract all the information yourself and save it in the property array.
 \snippet 02_ConfinedGasExtended.cpp SQL Save
+
+\section tutorial_pe_02_raytracing Image Output
+Using the pe::raytracing::Raytracer you can generate images of the simulation for each timestep and output them as PNG files.
+Setup the raytracer by reading a config object and optionally supply a shading and a visibility function, 
+as it is done in the second pe tutorial. The shading function will be called during rendering for each body
+to apply user defined coloring to bodies, the visibility function to determine if a body should be visible or not.
+\snippet 02_ConfinedGasExtended.cpp Raytracer Init
+Alternatively it may also be setup entirely in code:
+\code
+Lighting lighting(Vec3(-12, 12, 12),
+                  Color(1, 1, 1),
+                  Color(1, 1, 1),
+                  Color(real_t(0.4), real_t(0.4), real_t(0.4)));
+Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                    640, 480,
+                    real_t(49.13), 2,
+                    Vec3(-25, 10, 10), Vec3(-5, 10, 10), Vec3(0, 0, 1),
+                    lighting,
+                    Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                    radius,
+                    customShadingFunction);
+\endcode
+After the configuration is done, images can be generated each timestep by calling Raytracer::generateImage<BodyTuple>()
+which will be output to the specified directory.
+\snippet 02_ConfinedGasExtended.cpp Image Output
+To hide certain bodies during rendering, the visibility function will be called with a BodyID as its sole argument 
+and should return true if the object is supposed to be visible, false if not:
+\code
+// [...]
+std::function<bool (const BodyID body)> customVisibilityFunction = [](const BodyID body) {
+  if (body->getTypeID() == Plane::getStaticTypeID()) {
+     // hide all planes
+     return false;
+  }
+  return true;
+};
+Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                   cfg->getBlock("Raytracing"),
+                   customShadingFunction,
+                   customVisibilityFunction);
+\endcode
+For an overview over predefined shading functions, visit the file ShadingFunctions.h.
+For further information see the documentation for the classes pe::raytracing::Raytracer, pe::raytracing::Lighting and 
+pe::raytracing::Color, the pe::raytracing::ShadingParameters struct and ShadingFunctions.h file may also be useful.
+
 */
 
 }
diff --git a/src/core/mpi/MPIWrapper.h b/src/core/mpi/MPIWrapper.h
index f97cb06b197565e4fdbb15f8e1983a614053e250..3cdc7d54626747d8a6d9010a2211e4ccfbbeb54d 100644
--- a/src/core/mpi/MPIWrapper.h
+++ b/src/core/mpi/MPIWrapper.h
@@ -91,6 +91,7 @@ typedef int MPI_File;
 typedef int MPI_Offset;
 typedef int MPI_Info;
 typedef int MPI_Aint;
+typedef void (MPI_User_function) (void* a, void* b, int* len, MPI_Datatype*);
 
 struct MPI_Status
 {
@@ -245,6 +246,10 @@ inline int MPI_Type_commit( MPI_Datatype* ) { WALBERLA_MPI_FUNCTION_ERROR }
 inline int MPI_Type_free( MPI_Datatype* ) { WALBERLA_MPI_FUNCTION_ERROR }
 inline int MPI_Type_create_resized( MPI_Datatype, MPI_Aint, MPI_Aint, MPI_Datatype* ) { WALBERLA_MPI_FUNCTION_ERROR }
 inline int MPI_Type_size( MPI_Datatype, int * ) { WALBERLA_MPI_FUNCTION_ERROR }
+inline int MPI_Type_get_extent(MPI_Datatype, MPI_Aint*, MPI_Aint*) { WALBERLA_MPI_FUNCTION_ERROR }
+inline int MPI_Type_create_struct(int, const int[], const MPI_Aint[], const MPI_Datatype[], MPI_Datatype*) { WALBERLA_MPI_FUNCTION_ERROR }
+
+inline int MPI_Op_create(MPI_User_function*, int, MPI_Op*) { WALBERLA_MPI_FUNCTION_ERROR }
 
 inline int MPI_Get_processor_name( char*, int* ) { WALBERLA_MPI_FUNCTION_ERROR }
 
diff --git a/src/pe/ccd/HashGrids.cpp b/src/pe/ccd/HashGrids.cpp
index ce6b8c993353b82f5bbd6cb9926184884eff97c8..3eee9158b6fb6ce554e507fa3d2ce316295049eb 100644
--- a/src/pe/ccd/HashGrids.cpp
+++ b/src/pe/ccd/HashGrids.cpp
@@ -313,6 +313,21 @@ void HashGrids::HashGrid::initializeNeighborOffsets()
  *
  * \param body The body whose hash value is about to be calculated.
  * \return The hash value (=cell association) of the body.
+ */
+size_t HashGrids::HashGrid::hash( BodyID body ) const
+{
+   const AABB& bodyAABB = body->getAABB();
+   return hashPoint(bodyAABB.xMin(), bodyAABB.yMin(), bodyAABB.zMin());
+}
+//*************************************************************************************************
+
+   
+/*!\brief Computes the hash for a given point.
+ *
+ * \param x X value of the point.
+ * \param y Y value of the point.
+ * \param y Z value of the point.
+ * \return The hash value (=cell association) of the point.
  *
  * The hash calculation uses modulo operations in order to spatially map entire blocks of connected
  * cells to the origin of the coordinate system. This block of cells at the origin of the coordinate
@@ -324,16 +339,11 @@ void HashGrids::HashGrid::initializeNeighborOffsets()
  * Note that the modulo calculations are replaced with fast bitwise AND operations - hence, the
  * spatial dimensions of the hash grid must be restricted to powers of two!
  */
-size_t HashGrids::HashGrid::hash( BodyID body ) const
-{
-   real_t x = body->getAABB().xMin();
-   real_t y = body->getAABB().yMin();
-   real_t z = body->getAABB().zMin();
-
+size_t HashGrids::HashGrid::hashPoint(real_t x, real_t y, real_t z) const {
    size_t xHash;
    size_t yHash;
    size_t zHash;
-
+   
    if( x < 0 ) {
       real_t i = ( -x ) * inverseCellSpan_;
       xHash  = xCellCount_ - 1 - ( static_cast<size_t>( i ) & xHashMask_ );
@@ -342,7 +352,7 @@ size_t HashGrids::HashGrid::hash( BodyID body ) const
       real_t i = x * inverseCellSpan_;
       xHash  = static_cast<size_t>( i ) & xHashMask_;
    }
-
+   
    if( y < 0 ) {
       real_t i = ( -y ) * inverseCellSpan_;
       yHash  = yCellCount_ - 1 - ( static_cast<size_t>( i ) & yHashMask_ );
@@ -351,7 +361,7 @@ size_t HashGrids::HashGrid::hash( BodyID body ) const
       real_t i = y * inverseCellSpan_;
       yHash  = static_cast<size_t>( i ) & yHashMask_;
    }
-
+   
    if( z < 0 ) {
       real_t i = ( -z ) * inverseCellSpan_;
       zHash  = zCellCount_ - 1 - ( static_cast<size_t>( i ) & zHashMask_ );
@@ -360,11 +370,9 @@ size_t HashGrids::HashGrid::hash( BodyID body ) const
       real_t i = z * inverseCellSpan_;
       zHash  = static_cast<size_t>( i ) & zHashMask_;
    }
-
+   
    return xHash + yHash * xCellCount_ + zHash * xyCellCount_;
 }
-//*************************************************************************************************
-
 
 //*************************************************************************************************
 /*!\brief Adds a body to a specific cell in this hash grid.
@@ -1168,6 +1176,8 @@ bool HashGrids::powerOfTwo( size_t number )
 //*************************************************************************************************
 
 
+uint64_t HashGrids::intersectionTestCount = 0; //ToDo remove again
+   
 //=================================================================================================
 //
 //  CONSTANTS
diff --git a/src/pe/ccd/HashGrids.h b/src/pe/ccd/HashGrids.h
index b1ab8198be32c6ea707e58011b048b9e66a0304d..49c40b5ce0154261d9b7e3b9f714e6b957cc83f6 100644
--- a/src/pe/ccd/HashGrids.h
+++ b/src/pe/ccd/HashGrids.h
@@ -40,6 +40,12 @@
 #include <sstream>
 #include <vector>
 
+#include <unordered_set>
+#include <pe/raytracing/Ray.h>
+#include <pe/utility/BodyCast.h>
+#include <pe/raytracing/Intersects.h>
+
+#define BLOCKCELL_NORMAL_INDETERMINATE 3
 
 namespace walberla{
 namespace pe{
@@ -93,7 +99,9 @@ public:
    static const size_t gridActivationThreshold;
    static const real_t hierarchyFactor;
    //**********************************************************************************************
-
+   
+   static uint64_t intersectionTestCount; // ToDo remove again
+   
 private:
    //**Type definitions****************************************************************************
    //! Vector for storing (handles to) rigid bodies.
@@ -176,11 +184,23 @@ private:
 
       template< typename Contacts >
       void   processBodies( BodyID* bodies, size_t bodyCount, Contacts& contacts ) const;
-
+      
+      template<typename BodyTuple>
+      BodyID getRayIntersectingBody(const raytracing::Ray& ray, const AABB& blockAABB, real_t& t, Vec3& n,
+                                    std::function<bool (const BodyID body)> isBodyVisibleFunc) const;
+      
+      template<typename BodyTuple>
+      BodyID getBodyIntersectionForBlockCell(const Vector3<int32_t>& blockCell,
+                                             const int8_t cellNormalAxis, const int8_t cellNormalDir,
+                                             const raytracing::Ray& ray,
+                                             real_t& t_closest, Vec3& n_closest,
+                                             std::function<bool (const BodyID body)> isBodyVisibleFunc) const;
+      
       void clear();
       //@}
       //*******************************************************************************************
 
+
     private:
       //**Utility functions************************************************************************
       /*!\name Utility functions */
@@ -188,11 +208,13 @@ private:
       void initializeNeighborOffsets();
 
       size_t hash( BodyID body ) const;
+      size_t hashPoint(real_t x, real_t y, real_t z) const;
 
       void add   ( BodyID body, Cell* cell );
       void remove( BodyID body, Cell* cell );
 
       void enlarge();
+      
       //@}
       //*******************************************************************************************
 
@@ -276,12 +298,17 @@ public:
    //@}
    //**********************************************************************************************
 
-   void    update(WcTimingTree* tt);
    //**Implementation of ICCD interface ********************************************************
    virtual PossibleContacts& generatePossibleContacts( WcTimingTree* tt = NULL );
-
+   void update(WcTimingTree* tt = NULL);
+   
    bool active() const { return gridActive_; }
-
+   
+   template<typename BodyTuple>
+   BodyID getClosestBodyIntersectingWithRay(const raytracing::Ray& ray, const AABB& blockAABB,
+                                            real_t& t, Vec3& n,
+                                            std::function<bool (const BodyID body)> isBodyVisibleFunc) const;
+   
 protected:
    //**Utility functions***************************************************************************
    /*!\name Utility functions */
@@ -472,7 +499,308 @@ void HashGrids::HashGrid::processBodies( BodyID* bodies, size_t bodyCount, Conta
 }
 //*************************************************************************************************
 
+/*!\brief Computes closest ray-body intersection of cell with center at point x,y,z and neighboring ones.
+ *
+ * \param blockCell index of cell within block grid.
+ * \param blockAABB AABB of the block this grid corresponds to.
+ * \param ray Ray being casted trough grid.
+ * \param t_closest Distance of closest object from ray origin. Will be updated if closer body found.
+ * \param n_closest Normal of intersection point.
+ *
+ * \return BodyID of intersected body, NULL if no intersection found.
+ *
+ * Inserts bodies of specified cell into bodiesContainer and additionally considers bodies in neighboring cells
+ * in all negative coordinate direction to take bodies in consideration, which protrude from their
+ * cell into the intersected cell (and thus, possibly intersect with the ray as well).
+ */
+template<typename BodyTuple>
+BodyID HashGrids::HashGrid::getBodyIntersectionForBlockCell(const Vector3<int32_t>& blockCell,
+                                                            const int8_t cellNormalAxis, const int8_t cellNormalDir,
+                                                            const raytracing::Ray& ray,
+                                                            real_t& t_closest, Vec3& n_closest,
+                                                            std::function<bool (const BodyID body)> isBodyVisibleFunc) const {
+   real_t t_local;
+   Vec3 n_local;
+   BodyID body = NULL;
+   
+   raytracing::IntersectsFunctor intersectsFunc(ray, t_local, n_local);
+   
+   // calculate center coordinates of the cell in the block
+   real_t x = (real_c(blockCell[0]) + real_t(0.5)) * cellSpan_;
+   real_t y = (real_c(blockCell[1]) + real_t(0.5)) * cellSpan_;
+   real_t z = (real_c(blockCell[2]) + real_t(0.5)) * cellSpan_;
+   
+   // hash of cell in the hashgrid
+   size_t cellHash = hashPoint(x, y, z);
+   
+   const Cell& centerCell = cell_[cellHash];
+   
+   std::vector<offset_t> relevantNeighborIndices;
+   
+   if (cellNormalAxis == 0) {
+      if (cellNormalDir == -1) {
+         relevantNeighborIndices = {2,5,8, 11,14,17, 20,23,26};
+      } else {
+         relevantNeighborIndices = {0,3,6, 9,12,15, 18,21,24};
+      }
+   } else if (cellNormalAxis == 1) {
+      if (cellNormalDir == -1) {
+         relevantNeighborIndices = {6,7,8, 15,16,17, 24,25,26};
+      } else {
+         relevantNeighborIndices = {0,1,2, 9,10,11, 18,19,20};
+      }
+   } else if (cellNormalAxis == 2) {
+      if (cellNormalDir == -1) {
+         relevantNeighborIndices = {18,19,20,21,22,23,24,25,26};
+      } else {
+         relevantNeighborIndices = {0,1,2,3,4,5,6,7,8};
+      }
+   } else {
+      // cellNormalAxis == BLOCKCELL_NORMAL_INDETERMINATE
+      relevantNeighborIndices = {
+         0, 1, 2, 3, 4, 5, 6, 7, 8,
+         9, 10, 11, 12, 13, 14, 15, 16, 17,
+         18, 19, 20, 21, 22, 23, 24, 25, 26
+      };
+   }
+   
+#ifdef HASHGRIDS_RAYTRACING_CHECK_ALL_NEIGHBORS
+   relevantNeighborIndices = {
+      0, 1, 2, 3, 4, 5, 6, 7, 8,
+      9, 10, 11, 12, 13, 14, 15, 16, 17,
+      18, 19, 20, 21, 22, 23, 24, 25, 26
+   };
+#endif
+   
+   for (uint_t i = 0; i < relevantNeighborIndices.size(); ++i) {
+      const offset_t neighborIndex = relevantNeighborIndices[i];
+      const Cell* nbCell = &centerCell + centerCell.neighborOffset_[neighborIndex];
+      const BodyVector* nbBodies = nbCell->bodies_;
+      
+      if (nbBodies != NULL) {
+         for (const BodyID& cellBody: *nbBodies) {
+            if (cellBody->isRemote()) {
+               continue;
+            }
+            if (!isBodyVisibleFunc(cellBody)) {
+               continue;
+            }
+               
+            HashGrids::intersectionTestCount++;
+            bool intersects = SingleCast<BodyTuple, raytracing::IntersectsFunctor, bool>::execute(cellBody, intersectsFunc);
+            if (intersects && t_local < t_closest) {
+               body = cellBody;
+               t_closest = t_local;
+               n_closest = n_local;
+            }
+         }
+      }
+   }
+      
+   return body;
+}
 
+/*!\brief Calculates ray-cell intersections and determines the closest body to the ray origin.
+ *
+ * \param ray Ray getting shot through this grid.
+ * \param blockAABB AABB of the block this grid corresponds to.
+ * \param t Reference for the distance.
+ * \param n Reference for the intersetion normal.
+ * \return BodyID of closest body, NULL if none found.
+ *
+ * This function calculates ray-cell intersections and the closest body in those cells. Also, neighboring
+ * cells in all negative coordinate directions are regarded to take bodies in consideration, which
+ * protrude from their cell into the intersected cell (and thus, possibly intersect with the ray as well).
+ */
+template<typename BodyTuple>
+BodyID HashGrids::HashGrid::getRayIntersectingBody(const raytracing::Ray& ray, const AABB& blockAABB,
+                                                   real_t& t_closest, Vec3& n_closest,
+                                                   std::function<bool (const BodyID body)> isBodyVisibleFunc) const {
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   
+   BodyID body_local = NULL;
+   BodyID body_closest = NULL;
+   
+   int32_t blockXCellCountMin = int32_c(blockAABB.xMin() * inverseCellSpan_) - 1;
+   int32_t blockXCellCountMax = int32_c(std::ceil(blockAABB.xMax() * inverseCellSpan_)) + 1;
+   int32_t blockYCellCountMin = int32_c(blockAABB.yMin() * inverseCellSpan_) - 1;
+   int32_t blockYCellCountMax = int32_c(std::ceil(blockAABB.yMax() * inverseCellSpan_)) + 1;
+   int32_t blockZCellCountMin = int32_c(blockAABB.zMin() * inverseCellSpan_) - 1;
+   int32_t blockZCellCountMax = int32_c(std::ceil(blockAABB.zMax() * inverseCellSpan_)) + 1;
+   
+   Vec3 firstPoint;
+   Vec3 firstPointCenteredInCell;
+   real_t tRayOriginToGrid = 0;
+   if (blockAABB.contains(ray.getOrigin(), cellSpan_)) {
+      firstPoint = ray.getOrigin();
+      firstPointCenteredInCell = firstPoint;
+   } else {
+      real_t t_start;
+      Vec3 firstPointNormal;
+      if (intersects(blockAABB, ray, t_start, cellSpan_, &firstPointNormal)) {
+         firstPoint = ray.getOrigin() + ray.getDirection()*t_start;
+         firstPointCenteredInCell = firstPoint - firstPointNormal * (cellSpan_/real_t(2));
+         tRayOriginToGrid = (ray.getOrigin() - firstPoint).length();
+      } else {
+         return NULL;
+      }
+   }
+   
+   Vector3<int32_t> firstCell(int32_c(std::floor(firstPointCenteredInCell[0]*inverseCellSpan_)),
+                              int32_c(std::floor(firstPointCenteredInCell[1]*inverseCellSpan_)),
+                              int32_c(std::floor(firstPointCenteredInCell[2]*inverseCellSpan_)));
+   
+   const int8_t stepX = ray.xDir() >= 0 ? 1 : -1;
+   const int8_t stepY = ray.yDir() >= 0 ? 1 : -1;
+   const int8_t stepZ = ray.zDir() >= 0 ? 1 : -1;
+   
+   Vec3 nearPoint((stepX >= 0) ? real_c(firstCell[0]+1)*cellSpan_-firstPoint[0] : firstPoint[0]-real_c(firstCell[0])*cellSpan_,
+                  (stepY >= 0) ? real_c(firstCell[1]+1)*cellSpan_-firstPoint[1] : firstPoint[1]-real_c(firstCell[1])*cellSpan_,
+                  (stepZ >= 0) ? real_c(firstCell[2]+1)*cellSpan_-firstPoint[2] : firstPoint[2]-real_c(firstCell[2])*cellSpan_);
+   
+   // tMax: distance along the ray to the next cell change in the axis direction
+   real_t tMaxX = (!realIsEqual(ray.xDir(), 0)) ? std::abs(nearPoint[0]*ray.xInvDir()) : realMax;
+   real_t tMaxY = (!realIsEqual(ray.yDir(), 0)) ? std::abs(nearPoint[1]*ray.yInvDir()) : realMax;
+   real_t tMaxZ = (!realIsEqual(ray.zDir(), 0)) ? std::abs(nearPoint[2]*ray.zInvDir()) : realMax;
+   
+   // tDelta: how far along the ray must be moved to encounter a new cell in the specified axis direction
+   real_t tDeltaX = (!realIsEqual(ray.xDir(), 0)) ? std::abs(cellSpan_*ray.xInvDir()) : realMax;
+   real_t tDeltaY = (!realIsEqual(ray.yDir(), 0)) ? std::abs(cellSpan_*ray.yInvDir()) : realMax;
+   real_t tDeltaZ = (!realIsEqual(ray.zDir(), 0)) ? std::abs(cellSpan_*ray.zInvDir()) : realMax;
+   
+   Vector3<int32_t> currentCell = firstCell;
+   
+   // First cell needs extra treatment, as it might lay out of the blocks upper bounds
+   // due to the nature of how it is calculated: If the first point lies on a upper border
+   // it maps to the cell "above" the grid.
+   if (currentCell[0] < blockXCellCountMax &&
+       currentCell[1] < blockYCellCountMax &&
+       currentCell[2] < blockZCellCountMax) {
+      body_local = getBodyIntersectionForBlockCell<BodyTuple>(currentCell, BLOCKCELL_NORMAL_INDETERMINATE, 0,
+                                                              ray, t_closest, n_closest,
+                                                              isBodyVisibleFunc);
+      if (body_local != NULL) {
+         body_closest = body_local;
+      }
+   }
+   
+   int8_t blockCellNormalAxis;
+   int8_t blockCellNormalDir;
+   
+   while (true) {
+      if (tMaxX < tMaxY) {
+         if (tMaxX < tMaxZ) {
+#if !defined(HASHGRIDS_DISABLE_EARLY_CUTOFF)
+            if (tRayOriginToGrid+tMaxX-tDeltaX > t_closest) {
+               break;
+            }
+#endif
+            tMaxX += tDeltaX;
+            currentCell[0] += stepX;
+            blockCellNormalAxis = 0;
+            blockCellNormalDir = int8_c(-stepX);
+            if (currentCell[0] >= blockXCellCountMax || currentCell[0] < blockXCellCountMin) {
+               break;
+            }
+         } else {
+#if !defined(HASHGRIDS_DISABLE_EARLY_CUTOFF)
+            if (tRayOriginToGrid+tMaxZ-tDeltaZ > t_closest) {
+               break;
+            }
+#endif
+            tMaxZ += tDeltaZ;
+            currentCell[2] += stepZ;
+            blockCellNormalAxis = 2;
+            blockCellNormalDir = int8_c(-stepZ);
+            if (currentCell[2] >= blockZCellCountMax || currentCell[2] < blockZCellCountMin) {
+               break;
+            }
+         }
+      } else {
+         if (tMaxY < tMaxZ) {
+#if !defined(HASHGRIDS_DISABLE_EARLY_CUTOFF)
+            if (tRayOriginToGrid+tMaxY-tDeltaY > t_closest) {
+               break;
+            }
+#endif
+            tMaxY += tDeltaY;
+            currentCell[1] += stepY;
+            blockCellNormalAxis = 1;
+            blockCellNormalDir = int8_c(-stepY);
+            if (currentCell[1] >= blockYCellCountMax || currentCell[1] < blockYCellCountMin) {
+               break;
+            }
+         } else {
+#if !defined(HASHGRIDS_DISABLE_EARLY_CUTOFF)
+            if (tRayOriginToGrid+tMaxZ-tDeltaZ > t_closest) {
+               break;
+            }
+#endif
+            tMaxZ += tDeltaZ;
+            currentCell[2] += stepZ;
+            blockCellNormalAxis = 2;
+            blockCellNormalDir = int8_c(-stepZ);
+            if (currentCell[2] >= blockZCellCountMax || currentCell[2] < blockZCellCountMin) {
+               break;
+            }
+         }
+      }
+      
+      body_local = getBodyIntersectionForBlockCell<BodyTuple>(currentCell, blockCellNormalAxis, blockCellNormalDir,
+                                                              ray, t_closest, n_closest,
+                                                              isBodyVisibleFunc);
+      if (body_local != NULL) {
+         body_closest = body_local;
+      }
+   }
+   
+   return body_closest;
+}
+
+/*!\brief Determines the closest intersecting body in the underlying hash grids.
+ *
+ * \param ray Ray getting shot through those grids.
+ * \param blockAABB AABB of the block the grids correspond to.
+ * \param t Reference for the distance.
+ * \param n Reference for the intersetion normal.
+ * \return BodyID of closest body, NULL if none found.
+ */
+template<typename BodyTuple>
+BodyID HashGrids::getClosestBodyIntersectingWithRay(const raytracing::Ray& ray, const AABB& blockAABB,
+                                                    real_t& t, Vec3& n,
+                                                    std::function<bool (const BodyID body)> isBodyVisibleFunc) const {
+   const real_t realMax = std::numeric_limits<real_t>::max();
+
+   BodyID body_closest = NULL;
+   real_t t_closest = realMax;
+   Vec3 n_closest;
+   
+   BodyID body_local;
+   real_t t_local = realMax;
+   Vec3 n_local;
+   
+   raytracing::IntersectsFunctor intersectsFunc(ray, t_local, n_local);
+   for(auto body: nonGridBodies_) {
+      bool intersects = SingleCast<BodyTuple, raytracing::IntersectsFunctor, bool>::execute(body, intersectsFunc);
+      if (intersects && t_local < t_closest) {
+         body_closest = body;
+         t_closest = t_local;
+         n_closest = n_local;
+      }
+   }
+   
+   for(auto grid: gridList_) {
+      body_local = grid->getRayIntersectingBody<BodyTuple>(ray, blockAABB, t_closest, n_closest, isBodyVisibleFunc);
+      if (body_local != NULL){
+         body_closest = body_local;
+      }
+   }
+   
+   t = t_closest;
+   n = n_closest;
+
+   return body_closest;
+}
 
 
 //=================================================================================================
diff --git a/src/pe/raytracing/Color.cpp b/src/pe/raytracing/Color.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4e9deab3f029827d88226d6ab353d144e33bc165
--- /dev/null
+++ b/src/pe/raytracing/Color.cpp
@@ -0,0 +1,80 @@
+//======================================================================================================================
+//
+//  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   Color.cpp
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#include "Color.h"
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+/*!\brief Create a Color object from HSV values.
+ * \param hue Hue value in degrees from 0-360
+ * \param saturation Saturation value from 0-1
+ * \param value Value from 0-1
+ */
+Color Color::colorFromHSV(real_t hue, real_t saturation, real_t value) {
+   // based on Max K. Agoston: Computer Graphics and Geometric Modeling - Implementation and Algorithms
+   real_t r, g, b;
+   
+   if (realIsEqual(hue, real_t(360))) {
+      hue = real_t(0);
+   } else {
+      hue /= real_t(60);
+   }
+   real_t fract = hue - std::floor(hue);
+   
+   real_t P = value*(real_t(1) - saturation);
+   real_t Q = value*(real_t(1) - saturation*fract);
+   real_t T = value*(real_t(1) - saturation*(real_t(1) - fract));
+   
+   if (real_t(0) <= hue && hue < real_t(1)) {
+      r = value;
+      g = T;
+      b = P;
+   } else if (real_t(1) <= hue && hue < real_t(2)) {
+      r = Q;
+      g = value,
+      b = P;
+   } else if (real_t(2) <= hue && hue < real_t(3)) {
+      r = P;
+      g = value;
+      b = T;
+   } else if (real_t(3) <= hue && hue < real_t(4)) {
+      r = P;
+      g = Q;
+      b = value;
+   } else if (real_t(4) <= hue && hue < real_t(5)) {
+      r = T;
+      g = P;
+      b = value;
+   } else if (real_t(5) <= hue && hue < real_t(6)) {
+      r = value;
+      g = P;
+      b = Q;
+   } else {
+      r = g = b = real_t(0);
+   }
+   
+   return Color(r, g, b);
+}
+
+}
+}
+}
diff --git a/src/pe/raytracing/Color.h b/src/pe/raytracing/Color.h
new file mode 100644
index 0000000000000000000000000000000000000000..cf599930460172403014367436679d9304d194f3
--- /dev/null
+++ b/src/pe/raytracing/Color.h
@@ -0,0 +1,83 @@
+//======================================================================================================================
+//
+//  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   Color.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <pe/Types.h>
+#include "core/math/Vector3.h"
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+class Color: public Vector3<real_t> {
+public:
+   /*!\name Constructors */
+   //@{
+   /*!\brief Instantiation constructor for the Color class. Defaults to white.
+    */
+   Color () : Color(1, 1, 1) {
+
+   }
+   
+   /*!\brief Instantiation constructor for the Color class.
+   * \param r Red component
+   * \param g Green component
+   * \param b Blue component
+   * Instantiation constructor for the Color class with RGB components. Each value should be between 0 and 1 (soft limits)
+   */
+   Color (real_t r, real_t g, real_t b) : Vector3<real_t>(r, g, b) {
+      
+   }
+   
+   /*!\brief Instantiation constructor for the Color class.
+    * \param r Red component
+    * \param g Green component
+    * \param b Blue component
+    * Instantiation constructor for the Color class with RGB components. Each value should be between 0 and 1 (soft limits)
+    */
+   Color (const Vec3& vector) : Color(vector[0], vector[1], vector[2]) {
+      
+   }
+   //@}
+   
+   /*!\brief Multiply this color with another component wise.
+    * \return Color with components of this and other multiplied.
+    */
+   inline Color mulComponentWise(const Color& other) const {
+      return Color((*this)[0]*other[0],
+                   (*this)[1]*other[1],
+                   (*this)[2]*other[2]);
+   }
+   
+   /*!\brief Clamps this colors component values between 0 and 1.
+    */
+   inline void clamp() {
+      (*this)[0] = std::min(std::max((*this)[0], real_t(0)), real_t(1));
+      (*this)[1] = std::min(std::max((*this)[1], real_t(0)), real_t(1));
+      (*this)[2] = std::min(std::max((*this)[2], real_t(0)), real_t(1));
+   }
+   
+   static Color colorFromHSV(real_t hue, real_t saturation, real_t value);
+};
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/Intersects.h b/src/pe/raytracing/Intersects.h
new file mode 100644
index 0000000000000000000000000000000000000000..836ee8dc6af313d2b3b25841900cf61d1a927abc
--- /dev/null
+++ b/src/pe/raytracing/Intersects.h
@@ -0,0 +1,468 @@
+//======================================================================================================================
+//
+//  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 Intersects.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <pe/Types.h>
+#include "pe/rigidbody/Box.h"
+#include "pe/rigidbody/Capsule.h"
+#include "pe/rigidbody/CylindricalBoundary.h"
+#include "pe/rigidbody/Plane.h"
+#include "pe/rigidbody/Sphere.h"
+#include "pe/rigidbody/Ellipsoid.h"
+#include "pe/rigidbody/Union.h"
+#include "pe/utility/BodyCast.h"
+#include <core/math/Utility.h>
+#include <boost/tuple/tuple.hpp>
+
+#include <pe/raytracing/Ray.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+inline bool intersects(const SphereID sphere, const Ray& ray, real_t& t, Vec3& n);
+inline bool intersects(const PlaneID plane, const Ray& ray, real_t& t, Vec3& n);
+inline bool intersects(const BoxID box, const Ray& ray, real_t& t, Vec3& n);
+inline bool intersects(const CapsuleID capsule, const Ray& ray, real_t& t, Vec3& n);
+inline bool intersects(const EllipsoidID ellipsoid, const Ray& ray, real_t& t, Vec3& n);
+
+inline bool intersects(const BodyID body, const Ray& ray, real_t& t, Vec3& n);
+   
+inline bool intersects(const AABB& aabb, const Ray& ray, real_t& t, real_t padding = real_t(0.0), Vec3* n = NULL);
+inline bool intersectsSphere(const Vec3& gpos, real_t radius, const Ray& ray, real_t& t0, real_t& t1);
+
+struct IntersectsFunctor
+{
+   const Ray& ray_;
+   real_t& t_;
+   Vec3& n_;
+   
+   IntersectsFunctor(const Ray& ray, real_t& t, Vec3& n) : ray_(ray), t_(t), n_(n) {}
+   
+   template< typename BodyType >
+   bool operator()( BodyType* bd1 ) {
+      return intersects( bd1, ray_, t_, n_);
+   }
+};
+
+const real_t discriminantEps = real_t(1e-4);
+   
+inline bool intersects(const SphereID sphere, const Ray& ray, real_t& t, Vec3& n) {
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   
+   real_t t0, t1;
+   if (!intersectsSphere(sphere->getPosition(), sphere->getRadius(), ray, t0, t1)) {
+      t = realMax;
+      return false;
+   }
+   
+   t = (t0 < t1) ? t0 : t1; // assign the closest hit point to t
+   if (t < 0) {
+      t = t1; // if t0 < 0, t1 is > 0 (else intersectsSphere would have returned false)
+   }
+   
+   Vec3 intersectionPoint = ray.getOrigin() + ray.getDirection()*t;
+   n = (intersectionPoint - sphere->getPosition()).getNormalized();
+
+   return true;
+}
+
+inline bool intersects(const PlaneID plane, const Ray& ray, real_t& t, Vec3& n) {
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   const Vec3& direction = ray.getDirection();
+   const Vec3& origin = ray.getOrigin();
+   const Vec3& planeNormal = plane->getNormal();
+   real_t denominator = planeNormal * direction;
+   if (std::abs(denominator) > discriminantEps) {
+      real_t t_;
+      t_ = ((plane->getPosition() - origin) * planeNormal) / denominator;
+      if (t_ > 0) {
+         t = t_;
+         n = planeNormal * walberla::math::sign(-denominator);
+         return true;
+      } else {
+         t = realMax;
+      }
+   }
+   t = realMax;
+   return false;
+}
+
+inline bool intersects(const BoxID box, const Ray& ray, real_t& t, Vec3& n) {
+   Ray transformedRay = ray.transformedToBF(box);
+   
+   const Vec3& lengths = box->getLengths();
+   const Vec3 lengthsHalved = lengths/real_t(2);
+   
+   const Vec3 bounds[2] = {
+      -lengthsHalved,
+      lengthsHalved
+   };
+   
+   const Vector3<int8_t>& sign = transformedRay.getInvDirectionSigns();
+   const Vec3& invDirection = transformedRay.getInvDirection();
+   const Vec3& origin = transformedRay.getOrigin();
+   
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   
+   size_t tminAxis = 0, tmaxAxis = 0;
+   real_t txmin, txmax;
+   real_t tmin = txmin = (bounds[sign[0]][0] - origin[0]) * invDirection[0];
+   real_t tmax = txmax = (bounds[1-sign[0]][0] - origin[0]) * invDirection[0];
+   real_t tymin = (bounds[sign[1]][1] - origin[1]) * invDirection[1];
+   real_t tymax = (bounds[1-sign[1]][1] - origin[1]) * invDirection[1];
+   if (tmin > tymax || tymin > tmax) {
+      t = realMax;
+      return false;
+   }
+   if (tymin > tmin) {
+      tminAxis = 1;
+      tmin = tymin;
+   }
+   if (tymax < tmax) {
+      tmaxAxis = 1;
+      tmax = tymax;
+   }
+   real_t tzmin = (bounds[sign[2]][2] - origin[2]) * invDirection[2];
+   real_t tzmax = (bounds[1-sign[2]][2] - origin[2]) * invDirection[2];
+   if (tmin > tzmax || tzmin > tmax) {
+      t = realMax;
+      return false;
+   }
+   if (tzmin > tmin) {
+      tminAxis = 2;
+      tmin = tzmin;
+   }
+   if (tzmax < tmax) {
+      tmaxAxis = 2;
+      tmax = tzmax;
+   }
+   
+   n[0] = n[1] = n[2] = real_t(0);
+   real_t t_;
+   if (tmin > 0) {
+      // ray hit box from outside
+      t_ = tmin;
+      n[tminAxis] = real_t(1);
+   } else if (tmax < 0) {
+      // tmin and tmax are smaller than 0 -> box is in rays negative direction
+      t = realMax;
+      return false;
+   } else {
+      // ray origin within box
+      t_ = tmax;
+      n[tmaxAxis] = real_t(1);
+   }
+   
+   if (transformedRay.getDirection() * n > 0) {
+      n = -n;
+   }
+   n = box->vectorFromBFtoWF(n);
+   
+   t = t_;
+   return true;
+}
+   
+inline bool intersects(const CapsuleID capsule, const Ray& ray, real_t& t, Vec3& n) {
+   const Ray transformedRay = ray.transformedToBF(capsule);
+   const Vec3& direction = transformedRay.getDirection();
+   const Vec3& origin = transformedRay.getOrigin();
+   real_t halfLength = capsule->getLength()/real_t(2);
+
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   t = realMax;
+   
+   bool t0hit = false, t1hit = false;
+   size_t intersectedPrimitive = 0; // 1 for capsule, 2 for left half sphere, 3 for right half sphere
+
+   real_t a = direction[2]*direction[2] + direction[1]*direction[1];
+   real_t b = real_t(2)*origin[2]*direction[2] + real_t(2)*origin[1]*direction[1];
+   real_t c = origin[2]*origin[2] + origin[1]*origin[1] - capsule->getRadius()*capsule->getRadius();
+   real_t discriminant = b*b - real_t(4.)*a*c;
+   if (std::abs(discriminant) >= discriminantEps) {
+      // With discriminant smaller than 0, cylinder is not hit by ray (no solution for quadratic equation).
+      // Thus only enter this section if the equation is actually solvable.
+      
+      real_t root = real_t(std::sqrt(discriminant));
+      real_t t0 = (-b - root) / (real_t(2) * a); // Distance to point where the ray enters the cylinder
+      real_t t1 = (-b + root) / (real_t(2) * a); // Distance to point where the ray leaves the cylinder
+
+      real_t tx0 = origin[0] + direction[0]*t0;
+      real_t tx1 = origin[0] + direction[0]*t1;
+      
+      if (t0 > 0 && tx0 >= -halfLength && tx0 <= halfLength) {
+         t0hit = true;
+         intersectedPrimitive = 1;
+         t = t0;
+      }
+      if (t1 > 0 && tx1 >= -halfLength && tx1 <= halfLength && t1 < t) {
+         t1hit = true;
+         if (t1 < t) {
+            intersectedPrimitive = 1;
+            t = t1;
+         }
+      }
+   }
+   
+   // Check now for end capping half spheres.
+   // Only check them if the ray didnt both enter and leave the cylinder part of the capsule already (t0hit && t1hit).
+   if (!t0hit || !t1hit) {
+      real_t t0_left, t1_left;
+      Vec3 leftSpherePos(-halfLength, 0, 0);
+      if (intersectsSphere(leftSpherePos, capsule->getRadius(), transformedRay, t0_left, t1_left)) {
+         // at least one of t0_left and t1_left are not behind the rays origin
+         real_t t0x_left = origin[0] + direction[0]*t0_left;
+         real_t t1x_left = origin[0] + direction[0]*t1_left;
+         
+         real_t t_left = realMax;
+         if (t0_left > 0 && t0x_left < -halfLength) {
+            t_left = t0_left;
+         }
+         if (t1_left > 0 && t1x_left < -halfLength && t1_left < t_left) {
+            t_left = t1_left;
+         }
+         if (t_left < t) {
+            intersectedPrimitive = 2;
+            t = t_left;
+         }
+      }
+      
+      real_t t0_right, t1_right;
+      Vec3 rightSpherePos(halfLength, 0, 0);
+      if (intersectsSphere(rightSpherePos, capsule->getRadius(), transformedRay, t0_right, t1_right)) {
+         // At least one of t0_right and t1_right are not behind the rays origin
+         real_t t0x_right = origin[0] + direction[0]*t0_right;
+         real_t t1x_right = origin[0] + direction[0]*t1_right;
+         
+         real_t t_right = realMax;
+         if (t0_right > 0 && t0x_right > halfLength) {
+            t_right = t0_right;
+         }
+         if (t1_right > 0 && t1x_right > halfLength && t1_right < t_right) {
+            t_right = t1_right;
+         }
+         if (t_right < t) {
+            intersectedPrimitive = 3;
+            t = t_right;
+         }
+      }
+      
+      if (realIsIdentical(t, realMax)) {
+         return false;
+      }
+      
+      Vec3 intersectionPoint = origin + direction*t;
+      if (intersectedPrimitive == 2) {
+         n = (intersectionPoint - leftSpherePos).getNormalized();
+      } else if (intersectedPrimitive == 3) {
+         n = (intersectionPoint - rightSpherePos).getNormalized();
+      }
+   }
+   
+   WALBERLA_ASSERT(intersectedPrimitive != 0);
+   
+   if (intersectedPrimitive == 1) {
+      Vec3 intersectionPoint = origin + direction*t;
+      Vec3 intersectionPointOnXAxis(intersectionPoint[0], real_t(0), real_t(0));
+      n = (intersectionPoint - intersectionPointOnXAxis).getNormalized();
+   }
+   
+   n = capsule->vectorFromBFtoWF(n);
+   
+   return true;
+}
+   
+inline bool intersects(const EllipsoidID ellipsoid, const Ray& ray, real_t& t, Vec3& n) {
+   const real_t realMax = std::numeric_limits<real_t>::max();
+
+   const Ray transformedRay = ray.transformedToBF(ellipsoid);
+   const Vec3& semiAxes = ellipsoid->getSemiAxes();
+   
+   const Mat3 M = Mat3::makeDiagonalMatrix(real_t(1)/semiAxes[0], real_t(1)/semiAxes[1], real_t(1)/semiAxes[2]);
+   
+   const Vec3 d_M = M*transformedRay.getDirection();
+   const Vec3 P_M = M*transformedRay.getOrigin();
+   
+   const real_t a = d_M*d_M;
+   const real_t b = real_t(2)*P_M*d_M;
+   const real_t c = P_M*P_M - 1;
+   
+   const real_t discriminant = b*b - real_t(4.)*a*c;
+   if (discriminant < 0) {
+      // with discriminant smaller than 0, sphere is not hit by ray
+      // (no solution for quadratic equation)
+      t = realMax;
+      return false;
+   }
+   
+   const real_t root = real_t(std::sqrt(discriminant));
+   const real_t t0 = (-b - root) / (real_t(2.) * a); // distance to point where the ray enters the sphere
+   const real_t t1 = (-b + root) / (real_t(2.) * a); // distance to point where the ray leaves the sphere
+   
+   if (t0 < 0 && t1 < 0) {
+      return false;
+   }
+   t = (t0 < t1) ? t0 : t1; // assign the closest distance to t
+   if (t < 0) {
+      // at least one of the calculated distances is behind the rays origin
+      if (t1 < 0) {
+         // both of the points are behind the origin (ray does not hit sphere)
+         return false;
+      } else {
+         t = t1;
+      }
+   }
+   
+   const Vec3 transformedN = transformedRay.getOrigin() + t*transformedRay.getDirection();
+   const Mat3 M_inv = M.getInverse();
+   n = ellipsoid->vectorFromBFtoWF((M_inv*transformedN).getNormalized());
+   
+   return true;
+}
+   
+inline bool intersects(const BodyID body, const Ray& ray, real_t& t, Vec3& n) {
+   WALBERLA_UNUSED(body);
+   WALBERLA_UNUSED(ray);
+   WALBERLA_UNUSED(t);
+   WALBERLA_UNUSED(n);
+   WALBERLA_ABORT("This ray - body intersection test is not implemented yet!");
+   return false;
+}
+   
+inline bool intersectsSphere(const Vec3& gpos, real_t radius, const Ray& ray, real_t& t0, real_t& t1) {
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   
+   const Vec3& direction = ray.getDirection();
+   Vec3 displacement = ray.getOrigin() - gpos;
+   
+   real_t a = direction * direction;
+   real_t b = real_t(2.) * (displacement * direction);
+   real_t c = (displacement * displacement) - (radius * radius);
+   real_t discriminant = b*b - real_t(4.)*a*c;
+   if (discriminant < 0) {
+      // with discriminant smaller than 0, sphere is not hit by ray
+      // (no solution for quadratic equation)
+      t0 = realMax;
+      t1 = realMax;
+      return false;
+   }
+   
+   real_t root = real_t(std::sqrt(discriminant));
+   t0 = (-b - root) / (real_t(2.) * a); // distance to point where the ray enters the sphere
+   t1 = (-b + root) / (real_t(2.) * a); // distance to point where the ray leaves the sphere
+   
+   if (t0 < 0 && t1 < 0) {
+      return false;
+   }
+   real_t t = (t0 < t1) ? t0 : t1; // assign the closest distance to t
+   if (t < 0) {
+      // at least one of the calculated distances is behind the rays origin
+      if (t1 < 0) {
+         // both of the points are behind the origin (ray does not hit sphere)
+         return false;
+      }
+   }
+   
+   return true;
+}
+
+inline bool intersects(const AABB& aabb, const Ray& ray, real_t& t, real_t padding, Vec3* n) {
+   // An Efficient and Robust Ray–Box Intersection Algorithm: http://people.csail.mit.edu/amy/papers/box-jgt.pdf
+   const Vec3 paddingVector(padding, padding, padding);
+   Vec3 bounds[2] = {
+      aabb.min() - paddingVector,
+      aabb.max() + paddingVector
+   };
+   
+   const Vector3<int8_t>& sign = ray.getInvDirectionSigns();
+   const Vec3& invDirection = ray.getInvDirection();
+   const Vec3& origin = ray.getOrigin();
+
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   
+   size_t tminAxis = 0, tmaxAxis = 0;
+   real_t txmin, txmax;
+   real_t tmin = txmin = (bounds[sign[0]][0] - origin[0]) * invDirection[0];
+   real_t tmax = txmax = (bounds[1-sign[0]][0] - origin[0]) * invDirection[0];
+   real_t tymin = (bounds[sign[1]][1] - origin[1]) * invDirection[1];
+   real_t tymax = (bounds[1-sign[1]][1] - origin[1]) * invDirection[1];
+   if (tmin > tymax || tymin > tmax) {
+      t = realMax;
+      return false;
+   }
+   if (tymin > tmin) {
+      tminAxis = 1;
+      tmin = tymin;
+   }
+   if (tymax < tmax) {
+      tmaxAxis = 1;
+      tmax = tymax;
+   }
+   real_t tzmin = (bounds[sign[2]][2] - origin[2]) * invDirection[2];
+   real_t tzmax = (bounds[1-sign[2]][2] - origin[2]) * invDirection[2];
+   if (tmin > tzmax || tzmin > tmax) {
+      t = realMax;
+      return false;
+   }
+   if (tzmin > tmin) {
+      tminAxis = 2;
+      tmin = tzmin;
+   }
+   if (tzmax < tmax) {
+      tmaxAxis = 2;
+      tmax = tzmax;
+   }
+   
+   if (n != NULL) {
+      (*n)[0] = (*n)[1] = (*n)[2] = real_t(0);
+   }
+   real_t t_;
+   if (tmin > 0) {
+      // ray hit box from outside
+      t_ = tmin;
+      if (n != NULL) {
+         (*n)[tminAxis] = real_t(1);
+      }
+   } else if (tmax < 0) {
+      // tmin and tmax are smaller than 0 -> box is in rays negative direction
+      t = realMax;
+      return false;
+   } else {
+      // ray origin within box
+      t_ = tmax;
+      if (n != NULL) {
+         (*n)[tmaxAxis] = real_t(1);
+      }
+   }
+   
+   if (n != NULL) {
+      if (ray.getDirection() * (*n) > 0) {
+         *n = -(*n);
+      }
+   }
+   
+   t = t_;
+   return true;
+}
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/Lighting.h b/src/pe/raytracing/Lighting.h
new file mode 100644
index 0000000000000000000000000000000000000000..451cfe3357ca80dad10613a79251ed75da366898
--- /dev/null
+++ b/src/pe/raytracing/Lighting.h
@@ -0,0 +1,78 @@
+//======================================================================================================================
+//
+//  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 Lighting.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <pe/basic.h>
+#include <pe/Types.h>
+#include <core/math/Vector3.h>
+#include <pe/raytracing/Color.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+/*!\brief The Lighting struct defines the properties of a point light in the scene.
+ */
+struct Lighting {
+   Vec3 pointLightOrigin;
+   Color diffuseColor;
+   Color specularColor;
+   Color ambientColor;
+   
+   /*!\brief Instantiation constructor for the Lighting struct.
+    */
+   Lighting () {
+      
+   }
+   
+   /*!\brief Instantiation constructor for the Lighting struct.
+    * \param pointLightOrigin Origin of the point light.
+    * \param diffuseColor Diffuse color (base color of the light).
+    * \param specularColor Specular color (color of light refractions on an objects surface).
+    * \param ambientColor Color of the ambient light in the scene.
+    */
+   Lighting (const Vec3& _pointLightOrigin,
+             const Color& _diffuseColor, const Color& _specularColor, const Color& _ambientColor)
+   : pointLightOrigin(_pointLightOrigin),
+   diffuseColor(_diffuseColor), specularColor(_specularColor), ambientColor(_ambientColor) {
+      
+   }
+   
+   /*!\brief Instantiation constructor for the Lighting struct.
+    * \param config Config handle.
+    *
+    * The config block has to contain a pointLightOrigin parameter (Vec3).
+    * Optional are ambientColor (Vec3), diffuseColor (Vec3), specularColor (Vec3).
+    * Colors are Vec3's with values from 0 to 1.
+    */
+   Lighting (const Config::BlockHandle& config) {
+      WALBERLA_CHECK(config.isValid(), "No valid config passed to raytracer lighting.");
+
+      pointLightOrigin = config.getParameter<Vec3>("pointLightOrigin");
+      diffuseColor = config.getParameter<Color>("diffuseColor", Color(1,1,1));
+      specularColor = config.getParameter<Color>("specularColor", Color(1,1,1));
+      ambientColor = config.getParameter<Color>("ambientColor", Color(0.5,0.5,0.5));
+   }
+};
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/Ray.cpp b/src/pe/raytracing/Ray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c3f0a977684c1ffa0e2171503942572e49460949
--- /dev/null
+++ b/src/pe/raytracing/Ray.cpp
@@ -0,0 +1,41 @@
+//======================================================================================================================
+//
+//  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 Ray.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#include <pe/raytracing/Ray.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+/*!\brief Global output operator for rays.
+ *
+ * \param os Reference to the output stream.
+ * \param ray Reference to a constant ray object.
+ * \return Reference to the output stream.
+ */
+std::ostream& operator<<(std::ostream& os, const Ray& ray) {
+   return os << "<o: " << ray.getOrigin()
+   << ", d: " << ray.getDirection()
+   << ", c: (" << ray.getImageX() << "/" << ray.getImageY() << ")>";
+}
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/Ray.h b/src/pe/raytracing/Ray.h
new file mode 100644
index 0000000000000000000000000000000000000000..052ee87bf4cd11398d9e3229808068600741eb24
--- /dev/null
+++ b/src/pe/raytracing/Ray.h
@@ -0,0 +1,225 @@
+//======================================================================================================================
+//
+//  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 Ray.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/DataTypes.h"
+#include "core/math/Vector3.h"
+#include <pe/rigidbody/RigidBody.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+   
+class Ray {
+public:
+   /*!\name Constructors */
+   //@{
+   /*!\brief Instantiation constructor for the Raytracer class.
+    */
+   Ray () {
+      Ray (Vec3(0,0,0), Vec3(1,0,0));
+   }
+   
+   /*!\brief Instantiation constructor for the Raytracer class.
+    * \param origin Origin of the ray. ()
+    * \param direction Normalized direction of the ray.
+    */
+   Ray (Vec3 origin, Vec3 direction) {
+      setDirection(direction);
+      setOrigin(origin);
+   }
+   //@}
+
+private:
+   /*!\name Member variables */
+   //@{
+   Vec3 origin_; //!< Origin of the ray.
+   Vec3 direction_; //!< The normalized direction of the ray.
+   Vec3 inv_direction_; //!< The inverted direction of the ray.
+   Vector3<int8_t> sign_; /*!< The signs of the inverted direction of the ray.
+                           (Required for Ray-Box intersection code.)*/
+   size_t imageX_; //!< Y value of the pixel coordinate this ray intersects.
+   size_t imageY_; //!< X value of the pixel coordinate this ray intersects.
+   //@}
+
+public:
+   /*!\name Get functions */
+   //@{
+   /*!\brief Returns the origin point of the ray.
+    */
+   inline const Vec3& getOrigin () const {
+      return origin_;
+   }
+   
+   /*!\brief Returns the normalized direction vector of the ray.
+    */
+   inline const Vec3& getDirection () const {
+      return direction_;
+   }
+   
+   /*!\brief Returns the normalized direction vector of the ray for a given axis.
+    */
+   inline real_t getDirection (size_t axis) const {
+      WALBERLA_ASSERT(axis <= 2, "No valid axis index passed.");
+      return direction_[axis];
+   }
+   
+   /*!\brief Returns the x component of the ray direction.
+    */
+   inline real_t xDir () const {
+      return direction_[0];
+   }
+   
+   /*!\brief Returns the y component of the ray direction.
+    */
+   inline real_t yDir () const {
+      return direction_[1];
+   }
+   
+   /*!\brief Returns the z component of the ray direction.
+    */
+   inline real_t zDir () const {
+      return direction_[2];
+   }
+   
+   /*!\brief Returns the inverse of the direction vector of the ray.
+    */
+   inline const Vec3& getInvDirection () const {
+      return inv_direction_;
+   }
+
+   /*!\brief Returns the inverse of the direction vector of the ray for a given axis.
+    */
+   inline real_t getInvDirection (size_t axis) const {
+      WALBERLA_ASSERT(axis <= 2, "No valid axis index passed.");
+      return inv_direction_[axis];
+   }
+   
+   /*!\brief Returns the x component of the inverse ray direction.
+    */
+   inline real_t xInvDir () const {
+      return inv_direction_[0];
+   }
+   
+   /*!\brief Returns the y component of the inverse ray direction.
+    */
+   inline real_t yInvDir () const {
+      return inv_direction_[1];
+   }
+   
+   /*!\brief Returns the z component of the inverse ray direction.
+    */
+   inline real_t zInvDir () const {
+      return inv_direction_[2];
+   }
+   
+   /*!\brief Returns the signs of the inverted direction vector of the ray.
+    *
+    * Returns the signs of the inverted direction vector of the ray as required for the ray-box intersection algorithm.
+    */
+   inline const Vector3<int8_t>& getInvDirectionSigns () const {
+      return sign_;
+   }
+   
+   /*!\brief Returns the X value of the pixel coordinate this ray intersects.
+    *
+    * \return X value of pixel coordinate.
+    */
+   inline size_t getImageX () const {
+      return imageX_;
+   }
+   
+   /*!\brief Returns the Y value of the pixel coordinate this ray intersects.
+    *
+    * \return Y value of pixel coordinate.
+    */
+   inline size_t getImageY () const {
+      return imageY_;
+   }
+   //@}
+
+   /*!\name Set functions */
+   //@{
+   /*!\brief Set the origin point of the ray.
+    * \param origin Origin point
+    */
+   inline void setOrigin (const Vec3& origin) {
+      origin_ = origin;
+   }
+   
+   /*!\brief Set the _normalized_ direction vector of the ray.
+    * \param direction Normalized direction vector
+    */
+   inline void setDirection (const Vec3& direction) {
+      // im kommentar verweis auf normalisierung
+      WALBERLA_CHECK_FLOAT_EQUAL(direction.length(), real_t(1));
+      direction_ = direction;
+      calcInvDirection();
+   }
+   
+   /*!\brief Sets the X and Y values of the image pixel coordinate this ray intersects.
+    * \param x X value of the pixel coordinate
+    * \param y Y value of the pixel coordinate
+    */
+   inline void setImageCoordinate (size_t x, size_t y) {
+      imageX_ = x;
+      imageY_ = y;
+   }
+   
+   /*!\brief Sets the X value of the image pixel coordinate this ray intersects.
+    * \param x X value of the pixel coordinate
+    */
+   inline void setImageX (size_t x) {
+      imageX_ = x;
+   }
+   
+   /*!\brief Sets the Y value of the image pixel coordinate this ray intersects.
+    * \param y Y value of the pixel coordinate
+    */
+   inline void setImageY (size_t y) {
+      imageY_ = y;
+   }
+   //@}
+
+   /*!\name Utility functions */
+   //@{
+   /*!\brief Calculates the inverse of the direction vector and saves its signs.
+    */
+   inline void calcInvDirection () {
+      inv_direction_ = Vec3(1/direction_[0], 1/direction_[1], 1/direction_[2]);
+      sign_[0] = (inv_direction_[0] < 0) ? int8_t(1) : int8_t(0);
+      sign_[1] = (inv_direction_[1] < 0) ? int8_t(1) : int8_t(0);
+      sign_[2] = (inv_direction_[2] < 0) ? int8_t(1) : int8_t(0);
+   }
+   
+   /*!\brief Transforms the ray to the body frame.
+    *
+    * \return Ray transformed to the body frame.
+    */
+   inline Ray transformedToBF(const BodyID body) const {
+      return Ray(body->pointFromWFtoBF(getOrigin()), body->vectorFromWFtoBF(getDirection()));
+   }
+   //@}
+};
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/Raytracer.cpp b/src/pe/raytracing/Raytracer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..59320cc4abcb15f6217b53b0e4c1faa03b371dc4
--- /dev/null
+++ b/src/pe/raytracing/Raytracer.cpp
@@ -0,0 +1,426 @@
+//======================================================================================================================
+//
+//  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 Raytracer.cpp
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#include "Raytracer.h"
+#include "geometry/structured/extern/lodepng.h"
+#include "core/mpi/all.h"
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+   
+void BodyIntersectionInfo_Comparator_MPI_OP( BodyIntersectionInfo *in, BodyIntersectionInfo *inout, int *len, MPI_Datatype *dptr) {
+   WALBERLA_UNUSED(dptr);
+   for (int i = 0; i < *len; ++i) {
+      if (in->bodySystemID != 0 && inout->bodySystemID != 0) {
+         WALBERLA_ASSERT(in->imageX == inout->imageX && in->imageY == inout->imageY, "coordinates of infos do not match: " << in->imageX << "/" << in->imageY << " and " << inout->imageX << "/" << inout->imageY);
+      }
+      
+      if ((in->t < inout->t && in->bodySystemID != 0) || (inout->bodySystemID == 0 && in->bodySystemID != 0)) {
+         // info in "in" is closer than the one in "inout" -> update inout to values of in
+         inout->imageX = in->imageX;
+         inout->imageY = in->imageY;
+         inout->bodySystemID = in->bodySystemID;
+         inout->t = in->t;
+         inout->r = in->r;
+         inout->g = in->g;
+         inout->b = in->b;
+      }
+      
+      in++;
+      inout++;
+   }
+}
+
+/*!\brief Instantiation constructor for the Raytracer class.
+ *
+ * \param forest BlockForest the raytracer operates on.
+ * \param storageID Block data ID for the storage the raytracer operates on.
+ * \param globalBodyStorage Pointer to the global body storage.
+ * \param ccdID Block data ID for HashGrids.
+ * \param pixelsHorizontal Horizontal amount of pixels of the generated image.
+ * \param pixelsVertical Vertical amount of pixels of the generated image.
+ * \param fov_vertical Vertical field-of-view of the camera.
+ * \param cameraPosition Position of the camera in the global world frame.
+ * \param lookAtPoint Point the camera looks at in the global world frame.
+ * \param upVector Vector indicating the upwards direction of the camera.
+ * \param backgroundColor Background color of the scene.
+ * \param bodyToShadingParamsFunc A function mapping a BodyID to ShadingParameters for this body.
+ *                                This can be used to customize the color and shading of bodies.
+ * \param isBodyVisibleFunc A function which returns a boolean indicating if a given body should be visible
+ *                          in the final image.
+ */
+Raytracer::Raytracer(const shared_ptr<BlockStorage> forest, const BlockDataID storageID,
+                     const shared_ptr<BodyStorage> globalBodyStorage,
+                     const BlockDataID ccdID,
+                     uint16_t pixelsHorizontal, uint16_t pixelsVertical,
+                     real_t fov_vertical, uint16_t antiAliasFactor,
+                     const Vec3& cameraPosition, const Vec3& lookAtPoint, const Vec3& upVector,
+                     const Lighting& lighting,
+                     const Color& backgroundColor,
+                     std::function<ShadingParameters (const BodyID)> bodyToShadingParamsFunc,
+                     std::function<bool (const BodyID)> isBodyVisibleFunc)
+   : forest_(forest), storageID_(storageID), globalBodyStorage_(globalBodyStorage), ccdID_(ccdID),
+   pixelsHorizontal_(pixelsHorizontal), pixelsVertical_(pixelsVertical),
+   fov_vertical_(fov_vertical), antiAliasFactor_(antiAliasFactor),
+   cameraPosition_(cameraPosition), lookAtPoint_(lookAtPoint), upVector_(upVector),
+   lighting_(lighting),
+   backgroundColor_(backgroundColor),
+   imageOutputEnabled_(true),
+   localImageOutputEnabled_(false),
+   imageOutputDirectory_("."),
+   filenameTimestepWidth_(5),
+   confinePlanesToDomain_(true),
+   bodyToShadingParamsFunc_(bodyToShadingParamsFunc),
+   isBodyVisibleFunc_(isBodyVisibleFunc),
+   raytracingAlgorithm_(RAYTRACE_HASHGRIDS),
+   reductionMethod_(MPI_REDUCE) {
+   
+   setupView_();
+   setupFilenameRankWidth_();
+   WALBERLA_MPI_SECTION() {
+      setupMPI_();
+   }
+}
+
+/*!\brief Instantiation constructor for the Raytracer class using a config object for view setup.
+ *
+ * \param forest BlockForest the raytracer operates on.
+ * \param storageID Storage ID of the block data storage the raytracer operates on.
+ * \param globalBodyStorage Pointer to the global body storage.
+ * \param ccdID Block data ID for HashGrids.
+ * \param config Config block for the raytracer.
+ * \param bodyToShadingParamsFunc A function mapping a BodyID to ShadingParameters for this body.
+ *                                This can be used to customize the color and shading of bodies.
+ * \param isBodyVisibleFunc A function which returns a boolean indicating if a given body should be visible
+ *                          in the final image.
+ *
+ * The config block has to contain image_x (int), image_y (int) and fov_vertical (real, in degrees).
+ * Additionally a vector of reals for each of cameraPosition, lookAt and the upVector for the view setup are required.
+ * Optional is antiAliasFactor (uint, usually between 1 and 4) for supersampling and backgroundColor (Vec3).
+ * For image output after raytracing, set image_output_directory (string); for local image output additionally set
+ * local_image_output_enabled (bool) to true. outputFilenameTimestepZeroPadding (int) sets the zero padding
+ * for timesteps in output filenames.
+ * For the lighting a config block within the Raytracer config block named Lighting has to be defined,
+ * information about its contents is in the Lighting class.
+ */
+Raytracer::Raytracer(const shared_ptr<BlockStorage> forest, const BlockDataID storageID,
+                     const shared_ptr<BodyStorage> globalBodyStorage,
+                     const BlockDataID ccdID,
+                     const Config::BlockHandle& config,
+                     std::function<ShadingParameters (const BodyID)> bodyToShadingParamsFunc,
+                     std::function<bool (const BodyID)> isBodyVisibleFunc)
+   : forest_(forest), storageID_(storageID), globalBodyStorage_(globalBodyStorage), ccdID_(ccdID),
+   bodyToShadingParamsFunc_(bodyToShadingParamsFunc),
+   isBodyVisibleFunc_(isBodyVisibleFunc),
+   raytracingAlgorithm_(RAYTRACE_HASHGRIDS),
+   reductionMethod_(MPI_REDUCE) {
+   WALBERLA_CHECK(config.isValid(), "No valid config passed to raytracer");
+   
+   pixelsHorizontal_ = config.getParameter<uint16_t>("image_x");
+   pixelsVertical_ = config.getParameter<uint16_t>("image_y");
+   fov_vertical_ = config.getParameter<real_t>("fov_vertical");
+   antiAliasFactor_ = config.getParameter<uint16_t>("antiAliasFactor", 1);
+   
+   setLocalImageOutputEnabled(config.getParameter<bool>("local_image_output_enabled", false));
+      
+   if (config.isDefined("image_output_directory")) {
+      setImageOutputEnabled(true);
+      setImageOutputDirectory(config.getParameter<std::string>("image_output_directory", "."));
+      WALBERLA_LOG_INFO_ON_ROOT("Images will be written to " << getImageOutputDirectory() << ".");
+   } else if (getLocalImageOutputEnabled()) {
+      WALBERLA_ABORT("Cannot enable local image output without image_output_directory parameter being set.");
+   }
+      
+   filenameTimestepWidth_ = config.getParameter<uint8_t>("filenameTimestepWidth", uint8_t(5));
+   confinePlanesToDomain_ = config.getParameter<bool>("confinePlanesToDomain", true);
+   
+   cameraPosition_ = config.getParameter<Vec3>("cameraPosition");
+   lookAtPoint_ = config.getParameter<Vec3>("lookAt");
+   upVector_ = config.getParameter<Vec3>("upVector");
+   lighting_ = Lighting(config.getBlock("Lighting"));
+   backgroundColor_ = config.getParameter<Color>("backgroundColor", Vec3(real_t(0.1), real_t(0.1), real_t(0.1)));
+
+   std::string raytracingAlgorithm = config.getParameter<std::string>("raytracingAlgorithm", "RAYTRACE_HASHGRIDS");
+   if (raytracingAlgorithm == "RAYTRACE_HASHGRIDS") {
+      setRaytracingAlgorithm(RAYTRACE_HASHGRIDS);
+   } else if (raytracingAlgorithm == "RAYTRACE_NAIVE") {
+      setRaytracingAlgorithm(RAYTRACE_NAIVE);
+   } else if (raytracingAlgorithm == "RAYTRACE_COMPARE_BOTH") {
+      setRaytracingAlgorithm(RAYTRACE_COMPARE_BOTH);
+   }
+      
+   std::string reductionMethod = config.getParameter<std::string>("reductionMethod", "MPI_REDUCE");
+   if (reductionMethod == "MPI_REDUCE") {
+      setReductionMethod(MPI_REDUCE);
+   } else if (reductionMethod == "MPI_GATHER") {
+      setReductionMethod(MPI_GATHER);
+   }
+      
+   setupView_();
+   setupFilenameRankWidth_();
+   WALBERLA_MPI_SECTION() {
+      setupMPI_();
+   }
+}
+
+/*!\brief Utility function for setting up the view plane and calculating required variables.
+ */
+void Raytracer::setupView_() {
+   // eye coordinate system setup
+   n_ = (cameraPosition_ - lookAtPoint_).getNormalized();
+   u_ = (upVector_ % n_).getNormalized();
+   v_ = n_ % u_;
+   
+   // viewing plane setup
+   d_ = (cameraPosition_ - lookAtPoint_).length();
+   aspectRatio_ = real_t(pixelsHorizontal_) / real_t(pixelsVertical_);
+   real_t fov_vertical_rad = fov_vertical_ * math::M_PI / real_t(180.0);
+   viewingPlaneHeight_ = real_c(tan(fov_vertical_rad/real_t(2.))) * real_t(2.) * d_;
+   viewingPlaneWidth_ = viewingPlaneHeight_ * aspectRatio_;
+   viewingPlaneOrigin_ = lookAtPoint_ - u_*viewingPlaneWidth_/real_t(2.) - v_*viewingPlaneHeight_/real_t(2.);
+   
+   pixelWidth_ = viewingPlaneWidth_ / real_c(pixelsHorizontal_*antiAliasFactor_);
+   pixelHeight_ = viewingPlaneHeight_ / real_c(pixelsVertical_*antiAliasFactor_);
+}
+
+/*!\brief Utility function for initializing the attribute filenameRankWidth.
+ */
+void Raytracer::setupFilenameRankWidth_() {
+   int numProcesses = mpi::MPIManager::instance()->numProcesses();
+   filenameRankWidth_ = uint8_c(log10(numProcesses)+1);
+}
+
+/*!\brief Utility function for setting up the MPI datatype and operation.
+ */
+void Raytracer::setupMPI_() {
+   MPI_Op_create((MPI_User_function *)BodyIntersectionInfo_Comparator_MPI_OP, true, &bodyIntersectionInfo_reduction_op);
+   
+   const int nblocks = 7;
+   const int blocklengths[nblocks] = {1,1,1,1,1,1,1};
+   MPI_Datatype types[nblocks] = {
+      MPI_UNSIGNED, // for coordinate
+      MPI_UNSIGNED, // for coordinate
+      MPI_UNSIGNED_LONG_LONG, // for id
+      MPI_DOUBLE, // for distance
+      MPI_DOUBLE, // for color
+      MPI_DOUBLE, // for color
+      MPI_DOUBLE // for color
+   };
+   MPI_Aint displacements[nblocks];
+   displacements[0] = offsetof(BodyIntersectionInfo, imageX);
+   displacements[1] = offsetof(BodyIntersectionInfo, imageY);
+   displacements[2] = offsetof(BodyIntersectionInfo, bodySystemID);
+   displacements[3] = offsetof(BodyIntersectionInfo, t);
+   displacements[4] = offsetof(BodyIntersectionInfo, r);
+   displacements[5] = offsetof(BodyIntersectionInfo, g);
+   displacements[6] = offsetof(BodyIntersectionInfo, b);
+   
+   MPI_Datatype tmp_type;
+   MPI_Type_create_struct(nblocks, blocklengths, displacements, types, &tmp_type);
+   
+   MPI_Aint lb, extent;
+   MPI_Type_get_extent( tmp_type, &lb, &extent );
+   MPI_Type_create_resized( tmp_type, lb, extent, &bodyIntersectionInfo_mpi_type );
+   
+   MPI_Type_commit(&bodyIntersectionInfo_mpi_type);
+}
+   
+/*!\brief Generates the filename for output files.
+ * \param base String that precedes the timestap and rank info.
+ * \param timestep Timestep this image is from.
+ * \param isGlobalImage Whether this image is the fully stitched together one.
+ */
+std::string Raytracer::getOutputFilename(const std::string& base, size_t timestep, bool isGlobalImage) const {
+   uint_t maxTimestep = uint_c(pow(10, filenameTimestepWidth_));
+   WALBERLA_CHECK(timestep < maxTimestep, "Raytracer only supports outputting " << (maxTimestep-1) << " timesteps for the configured filename timestep width.");
+   mpi::MPIRank rank = mpi::MPIManager::instance()->rank();
+   std::stringstream fileNameStream;
+   fileNameStream << base << "_";
+   fileNameStream << std::setfill('0') << std::setw(int_c(filenameTimestepWidth_)) << timestep; // add timestep
+   WALBERLA_MPI_SECTION() {
+      // Appending the rank to the filename only makes sense if actually using MPI.
+      fileNameStream << "+";
+      if (isGlobalImage) {
+         fileNameStream << "global";
+      } else {
+         fileNameStream << std::setfill('0') << std::setw(int_c(filenameRankWidth_)) << std::to_string(rank); // add rank
+      }
+   }
+   fileNameStream << ".png"; // add extension
+   return fileNameStream.str();
+}
+
+/*!\brief Writes the image of the current intersection buffer to a file in the image output directory.
+ * \param intersectionsBuffer Buffer with intersection info for each pixel.
+ * \param timestep Timestep this image is from.
+ * \param isGlobalImage Whether this image is the fully stitched together one.
+ */
+void Raytracer::writeImageToFile(const std::vector<BodyIntersectionInfo>& intersectionsBuffer, size_t timestep,
+                                 bool isGlobalImage) const {
+   writeImageToFile(intersectionsBuffer, getOutputFilename("image", timestep, isGlobalImage));
+}
+
+/*!\brief Writes the image of the current intersection buffer to a file in the image output directory.
+ * \param intersectionsBuffer Buffer with intersection info for each pixel.
+ * \param fileName Name of the output file.
+ */
+void Raytracer::writeImageToFile(const std::vector<BodyIntersectionInfo>& intersectionsBuffer,
+                                 const std::string& fileName) const {
+   filesystem::path dir = getImageOutputDirectory();
+   filesystem::path file (fileName);
+   filesystem::path fullPath = dir / file;
+   
+   std::vector<u_char> lodeImageBuffer(pixelsHorizontal_*pixelsVertical_*3);
+   
+   uint32_t l = 0;
+   real_t patchSize = real_c(antiAliasFactor_*antiAliasFactor_);
+   for (int y = pixelsVertical_-1; y >= 0; y--) {
+      for (uint32_t x = 0; x < pixelsHorizontal_; x++) {
+         real_t r_sum = 0, g_sum = 0, b_sum = 0;
+         for (uint32_t ay = uint32_c(y)*antiAliasFactor_; ay < (uint32_c(y+1))*antiAliasFactor_; ay++) {
+            for (uint32_t ax = x*antiAliasFactor_; ax < (x+1)*antiAliasFactor_; ax++) {
+               size_t i = coordinateToArrayIndex(ax, ay);
+               r_sum += real_c(intersectionsBuffer[i].r);
+               g_sum += real_c(intersectionsBuffer[i].g);
+               b_sum += real_c(intersectionsBuffer[i].b);
+            }
+         }
+         u_char r = (u_char)(255 * (r_sum/patchSize));
+         u_char g = (u_char)(255 * (g_sum/patchSize));
+         u_char b = (u_char)(255 * (b_sum/patchSize));
+         
+         lodeImageBuffer[l] = r;
+         lodeImageBuffer[l+1] = g;
+         lodeImageBuffer[l+2] = b;
+         l+=3;
+      }
+   }
+   
+   uint32_t error = lodepng::encode(fullPath.string(), lodeImageBuffer, getPixelsHorizontal(), getPixelsVertical(), LCT_RGB);
+   if(error) {
+      WALBERLA_LOG_WARNING("lodePNG error " << error << " when trying to save image file to " << fullPath.string() << ": " << lodepng_error_text(error));
+   }
+}
+
+
+/*!\brief Conflate the intersectionsBuffer of each process onto the root process using MPI_Reduce.
+ * \param intersectionsBuffer Buffer containing all intersections for entire image (including non-hits).
+ * \param tt Optional TimingTree.
+ *
+ * This function conflates the intersectionsBuffer of each process onto the root process using the MPI_Reduce
+ * routine. It requires sending intersection info structs for the entire image instead of only the ones of the hits.
+ *
+ * \attention This function only works on MPI builds due to the explicit usage of MPI routines.
+ */
+void Raytracer::syncImageUsingMPIReduce(std::vector<BodyIntersectionInfo>& intersectionsBuffer, WcTimingTree* tt) {
+   WALBERLA_NON_MPI_SECTION() {
+      WALBERLA_UNUSED(intersectionsBuffer);
+      WALBERLA_UNUSED(tt);
+      WALBERLA_ABORT("Cannot call MPI reduce on a non-MPI build due to usage of MPI-specific code.");
+   }
+   
+   WALBERLA_MPI_BARRIER();
+   if (tt != NULL) tt->start("Reduction");
+   int rank = mpi::MPIManager::instance()->rank();
+
+   const int recvRank = 0;
+   if( rank == recvRank ) {
+      MPI_Reduce(MPI_IN_PLACE,
+                 &intersectionsBuffer[0], int_c(intersectionsBuffer.size()),
+                 bodyIntersectionInfo_mpi_type, bodyIntersectionInfo_reduction_op,
+                 recvRank, MPI_COMM_WORLD);
+   } else {
+      MPI_Reduce(&intersectionsBuffer[0], 0, int_c(intersectionsBuffer.size()),
+                 bodyIntersectionInfo_mpi_type, bodyIntersectionInfo_reduction_op,
+                 recvRank, MPI_COMM_WORLD);
+   }
+   
+   WALBERLA_MPI_BARRIER();
+   if (tt != NULL) tt->stop("Reduction");
+}
+  
+/*!\brief Conflate the intersectionsBuffer of each process onto the root process using MPI_Gather.
+ * \param intersectionsBuffer Buffer containing intersections.
+ * \param tt Optional TimingTree.
+ *
+ * This function conflates the intersectionsBuffer of each process onto the root process using the MPI_Gather
+ * routine. It only sends information for hits.
+ */
+void Raytracer::syncImageUsingMPIGather(std::vector<BodyIntersectionInfo>& intersections, std::vector<BodyIntersectionInfo>& intersectionsBuffer, WcTimingTree* tt) {
+   WALBERLA_MPI_BARRIER();
+   if (tt != NULL) tt->start("Reduction");
+   
+   mpi::SendBuffer sendBuffer;
+   for (auto& info: intersections) {
+      sendBuffer << info.imageX << info.imageY
+      << info.bodySystemID << info.t
+      << info.r << info.g << info.b;
+   }
+
+   mpi::RecvBuffer recvBuffer;
+   mpi::gathervBuffer(sendBuffer, recvBuffer, 0);
+   
+   WALBERLA_ROOT_SECTION() {
+      BodyIntersectionInfo info;
+      while (!recvBuffer.isEmpty()) {
+         recvBuffer >> info.imageX;
+         recvBuffer >> info.imageY;
+         recvBuffer >> info.bodySystemID;
+         recvBuffer >> info.t;
+         recvBuffer >> info.r;
+         recvBuffer >> info.g;
+         recvBuffer >> info.b;
+         
+         size_t i = coordinateToArrayIndex(info.imageX, info.imageY);
+         
+         if (intersectionsBuffer[i].bodySystemID == 0 || info.t < intersectionsBuffer[i].t) {
+            intersectionsBuffer[i] = info;
+         }
+      }
+   }
+   
+   WALBERLA_MPI_BARRIER();
+   if (tt != NULL) tt->stop("Reduction");
+}
+
+void Raytracer::localOutput(const std::vector<BodyIntersectionInfo>& intersectionsBuffer, size_t timestep, WcTimingTree* tt) {
+   if (getImageOutputEnabled()) {
+      if (getLocalImageOutputEnabled()) {
+         if (tt != NULL) tt->start("Local Output");
+         writeImageToFile(intersectionsBuffer, timestep);
+         if (tt != NULL) tt->stop("Local Output");
+      }
+   }
+}
+
+void Raytracer::output(const std::vector<BodyIntersectionInfo>& intersectionsBuffer, size_t timestep, WcTimingTree* tt) {
+   if (tt != NULL) tt->start("Output");
+   WALBERLA_ROOT_SECTION() {
+      if (getImageOutputEnabled()) {
+         writeImageToFile(intersectionsBuffer, timestep, true);
+      }
+   }
+   if (tt != NULL) tt->stop("Output");
+}
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/Raytracer.h b/src/pe/raytracing/Raytracer.h
new file mode 100644
index 0000000000000000000000000000000000000000..366098150e5f5082439c39e93a157d908f13ecdf
--- /dev/null
+++ b/src/pe/raytracing/Raytracer.h
@@ -0,0 +1,709 @@
+//======================================================================================================================
+//
+//  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 Raytracer.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <pe/basic.h>
+#include <pe/Types.h>
+#include <core/math/Vector3.h>
+#include <core/mpi/all.h>
+#include <core/config/Config.h>
+#include <core/Filesystem.h>
+#include <core/timing/TimingTree.h>
+#include <functional>
+#include "Ray.h"
+#include "Intersects.h"
+#include "Lighting.h"
+#include "ShadingFunctions.h"
+#include "pe/ccd/ICCD.h"
+#include <pe/ccd/HashGrids.h>
+
+#include <stddef.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+/*!\brief Contains information about a ray-body intersection.
+ */
+struct BodyIntersectionInfo {
+   unsigned int imageX;          //!< Viewing plane x pixel coordinate the ray belongs to.   -> MPI_UNSIGNED
+   unsigned int imageY;          //!< Viewing plane y pixel coordinate the ray belongs to.   -> MPI_UNSIGNED
+   walberla::id_t bodySystemID;  //!< System ID of body which was hit.                       -> MPI_UNSIGNED_LONG_LONG
+   double t;                     //!< Distance from camera to intersection point on body.    -> MPI_DOUBLE
+   double r;                     //!< Red value for the pixel.                               -> MPI_DOUBLE
+   double g;                     //!< Green value for the pixel.                             -> MPI_DOUBLE
+   double b;                     //!< Blue value for the pixel.                              -> MPI_DOUBLE
+};
+
+inline bool defaultIsBodyVisible(const BodyID body) {
+   WALBERLA_UNUSED(body);
+   return true;
+}
+
+class Raytracer {
+public:
+   /*!\brief Which method to use when reducing the process-local image to a global one.
+    */
+   enum ReductionMethod {
+      MPI_REDUCE,    //!< Reduce info from all processes onto root (assembling happens during reduction).
+      MPI_GATHER     //!< Gather info from all processes onto root process and assemble global image there.
+   };
+   /*!\brief Which algorithm to use when doing ray-object intersection finding.
+    */
+   enum Algorithm {
+      RAYTRACE_HASHGRIDS,              //!< Use hashgrids to find ray-body intersections.
+      RAYTRACE_NAIVE,                  //!< Use the brute force approach of checking all objects for intersection testing.
+      RAYTRACE_COMPARE_BOTH,           //!< Compare both methods and check for pixel errors.
+      RAYTRACE_COMPARE_BOTH_STRICTLY   //!< Same as RAYTRACE_COMPARE_BOTH but abort if errors found.
+   };
+   
+   /*!\name Constructors */
+   //@{
+   explicit Raytracer(const shared_ptr<BlockStorage> forest, const BlockDataID storageID,
+                      const shared_ptr<BodyStorage> globalBodyStorage,
+                      const BlockDataID ccdID,
+                      uint16_t pixelsHorizontal, uint16_t pixelsVertical,
+                      real_t fov_vertical, uint16_t antiAliasFactor,
+                      const Vec3& cameraPosition, const Vec3& lookAtPoint, const Vec3& upVector,
+                      const Lighting& lighting,
+                      const Color& backgroundColor = Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                      std::function<ShadingParameters (const BodyID)> bodyToShadingParamsFunc = defaultBodyTypeDependentShadingParams,
+                      std::function<bool (const BodyID)> isBodyVisibleFunc = defaultIsBodyVisible);
+
+   explicit Raytracer(const shared_ptr<BlockStorage> forest, const BlockDataID storageID,
+                      const shared_ptr<BodyStorage> globalBodyStorage,
+                      const BlockDataID ccdID,
+                      const Config::BlockHandle& config,
+                      std::function<ShadingParameters (const BodyID)> bodyToShadingParamsFunction = defaultBodyTypeDependentShadingParams,
+                      std::function<bool (const BodyID)> isBodyVisibleFunc = defaultIsBodyVisible);
+   //@}
+
+private:
+   /*!\name Member variables */
+   //@{
+   const shared_ptr<BlockStorage> forest_; //!< The BlockForest the raytracer operates on.
+   const BlockDataID storageID_;    //!< The storage ID of the block data storage the raytracer operates on.
+   const shared_ptr<BodyStorage> globalBodyStorage_; //!< The global body storage the raytracer operates on.
+   const BlockDataID ccdID_;  //!< The ID of the hash grids block data.
+   
+   uint16_t pixelsHorizontal_;  //!< The horizontal amount of pixels of the generated image.
+   uint16_t pixelsVertical_;    //!< The vertical amount of pixels of the generated image.
+   real_t fov_vertical_;      //!< The vertical field-of-view of the camera.
+   uint16_t antiAliasFactor_; /*!< Factor used for oversampling. Should be between 1 (fast, but jagged edges)
+                               * and 4 (16 times slower, very smooth edges).*/
+   Vec3 cameraPosition_;      //!< The position of the camera in the global world frame.
+   Vec3 lookAtPoint_;         /*!< The point the camera looks at in the global world frame,
+                               marks the center of the view plane.*/
+   Vec3 upVector_;            //!< The vector indicating the upwards direction of the camera.
+   Lighting lighting_;        //!< The lighting of the scene.
+   Color backgroundColor_;    //!< Background color of the scene.
+   
+   bool imageOutputEnabled_;  //!< Enable / disable writing images to file.
+   bool localImageOutputEnabled_; //!< Enable / disable writing images of the local process to file.
+   std::string imageOutputDirectory_; //!< Path to the image output directory.
+   
+   uint8_t filenameTimestepWidth_; /*!< Width of the timestep number in output filenames.
+                                   * Use e.g. 5 for ranges from 1 to 99 999: Will result in
+                                   * filenames like image_00001.png up to image_99999.png. */
+   uint8_t filenameRankWidth_;  //!< Width of the mpi rank part in a filename.
+   bool confinePlanesToDomain_; //!< Enable to render only the parts of planes within the simulation domain.
+   std::function<ShadingParameters (const BodyID)> bodyToShadingParamsFunc_; /*!< Function which returns a
+                                                                              * ShadingParameters struct for the
+                                                                              * specified body. */
+   std::function<bool (const BodyID)> isBodyVisibleFunc_; /*!< Function which returns a boolean indicating if
+                                                           * a given body should be visible in the final image. */
+   Algorithm raytracingAlgorithm_;  //!< Algorithm to use while intersection testing.
+   ReductionMethod reductionMethod_; //!< Reduction method used for assembling the image from all processes.
+   //@}
+   
+   /*!\name Member variables for raytracing geometry */
+   //@{
+   Vec3 n_;                   //!< The normal vector of the viewing plane.
+   Vec3 u_;                   //!< The vector spanning the viewing plane in the "right direction".
+   Vec3 v_;                   //!< The vector spanning the viewing plane in the "up direction".
+   real_t d_;                 //!< The the distance from camera to viewing plane.
+   real_t aspectRatio_;       //!< The aspect ratio of the generated image and viewing plane.
+   real_t viewingPlaneHeight_; //!< The viewing plane height in the world frame.
+   real_t viewingPlaneWidth_; //!< The viewing plane width in the world frame.
+   Vec3 viewingPlaneOrigin_;  //!< The origin of the viewing plane.
+   real_t pixelWidth_;        //!< The width of a pixel of the generated image in the viewing plane.
+   real_t pixelHeight_;       //!< The height of a pixel of the generated image in the viewing plane.
+   //@}
+   
+   MPI_Op bodyIntersectionInfo_reduction_op;
+   MPI_Datatype bodyIntersectionInfo_mpi_type;
+   
+public:
+   /*!\name Get functions */
+   //@{
+   inline uint16_t getPixelsHorizontal() const;
+   inline uint16_t getPixelsVertical() const;
+   inline real_t getFOVVertical() const;
+   inline const Vec3& getCameraPosition() const;
+   inline const Vec3& getLookAtPoint() const;
+   inline const Vec3& getUpVector() const;
+   inline const Color& getBackgroundColor() const;
+   inline bool getImageOutputEnabled() const;
+   inline bool getLocalImageOutputEnabled() const;
+   inline const std::string& getImageOutputDirectory() const;
+   inline uint8_t getFilenameTimestepWidth() const;
+   inline bool getConfinePlanesToDomain() const;
+   //@}
+
+   /*!\name Set functions */
+   //@{
+   inline void setBackgroundColor(const Color& color);
+   inline void setImageOutputEnabled(const bool enabled);
+   inline void setLocalImageOutputEnabled(const bool enabled);
+   inline void setImageOutputDirectory(const std::string& path);
+   inline void setFilenameTimestepWidth(uint8_t width);
+   inline void setRaytracingAlgorithm(Algorithm algorithm);
+   inline void setReductionMethod(ReductionMethod reductionMethod);
+   inline void setConfinePlanesToDomain(bool confinePlanesToOrigin);
+   //@}
+   
+   /*!\name Functions */
+   //@{
+   template <typename BodyTypeTuple>
+   void generateImage(const size_t timestep, WcTimingTree* tt = NULL );
+   
+   void setupView_();
+   void setupFilenameRankWidth_();
+   void setupMPI_();
+   
+private:
+   void localOutput(const std::vector<BodyIntersectionInfo>& intersectionsBuffer, size_t timestep,
+                    WcTimingTree* tt = NULL);
+   void output(const std::vector<BodyIntersectionInfo>& intersectionsBuffer, size_t timestep,
+               WcTimingTree* tt = NULL);
+
+   std::string getOutputFilename(const std::string& base, size_t timestep, bool isGlobalImage) const;
+   void writeImageToFile(const std::vector<BodyIntersectionInfo>& intersectionsBuffer,
+                         size_t timestep, bool isGlobalImage = false) const;
+   void writeImageToFile(const std::vector<BodyIntersectionInfo>& intersectionsBuffer,
+                         const std::string& fileName) const;
+   
+   void syncImageUsingMPIReduce(std::vector<BodyIntersectionInfo>& intersectionsBuffer, WcTimingTree* tt = NULL);
+   void syncImageUsingMPIGather(std::vector<BodyIntersectionInfo>& intersections,
+                                std::vector<BodyIntersectionInfo>& intersectionsBuffer, WcTimingTree* tt = NULL);
+   
+   inline bool isPlaneVisible(const PlaneID plane, const Ray& ray) const;
+   inline size_t coordinateToArrayIndex(size_t x, size_t y) const;
+   
+   template <typename BodyTypeTuple>
+   inline void traceRayInGlobalBodyStorage(const Ray& ray, BodyID& body_closest, real_t& t_closest, Vec3& n_closest) const;
+   template <typename BodyTypeTuple>
+   inline void traceRayNaively(const Ray& ray, BodyID& body_closest, real_t& t_closest, Vec3& n_closest) const;
+   template <typename BodyTypeTuple>
+   inline void traceRayInHashGrids(const Ray& ray, BodyID& body_closest, real_t& t_closest, Vec3& n_closest) const;
+
+   inline Color getColor(const BodyID body, const Ray& ray, real_t t, const Vec3& n) const;
+   //@}
+};
+   
+/*!\brief Returns the horizontal amount of pixels of the generated image.
+ *
+ * \return The horizontal amount of pixels of the generated image.
+ */
+inline uint16_t Raytracer::getPixelsHorizontal() const {
+   return pixelsHorizontal_;
+}
+
+/*!\brief Returns the vertical amount of pixels of the generated image.
+ *
+ * \return The vertical amount of pixels of the generated image.
+ */
+inline uint16_t Raytracer::getPixelsVertical() const {
+   return pixelsVertical_;
+}
+
+/*!\brief Returns the vertical field-of-view of the camera.
+ *
+ * \return The vertical field-of-view of the camera.
+ */
+inline real_t Raytracer::getFOVVertical() const {
+   return fov_vertical_;
+}
+
+/*!\brief Returns the position of the camera in the global world frame.
+ *
+ * \return The position of the camera.
+ *
+ * Returns the position of the camera in the global world frame.
+ */
+inline const Vec3& Raytracer::getCameraPosition() const {
+   return cameraPosition_;
+}
+
+/*!\brief Returns the point the camera looks at in the global world frame.
+ *
+ * \return The looked at point.
+ *
+ * Returns the point the camera looks at in the global world frame, which also marks the center of
+ * the view plane.
+ */
+inline const Vec3& Raytracer::getLookAtPoint() const {
+   return lookAtPoint_;
+}
+
+/*!\brief Returns the vector indicating the upwards direction of the camera.
+ *
+ * \return The upwards vector of the camera.
+ *
+ * Returns the vector indicating the upwards direction of the camera.
+ */
+inline const Vec3& Raytracer::getUpVector() const {
+   return upVector_;
+}
+
+/*!\brief Returns the background color of the scene.
+ *
+ * \return Color.
+ *
+ * Returns the background color of the scene.
+ */
+inline const Color& Raytracer::getBackgroundColor() const {
+   return backgroundColor_;
+}
+
+/*!\brief Returns true if image output to a file is enabled.
+ *
+ * \return True if image output enabled, otherwise false.
+ */
+inline bool Raytracer::getImageOutputEnabled() const {
+   return imageOutputEnabled_;
+}
+
+/*!\brief Returns true if local image output to a file is enabled.
+ *
+ * \return True if local image output enabled, otherwise false.
+ */
+inline bool Raytracer::getLocalImageOutputEnabled() const {
+   return localImageOutputEnabled_;
+}
+   
+/*!\brief Returns the directory where the images will be saved in.
+ *
+ * \return Path to the image output directory.
+ */
+inline const std::string& Raytracer::getImageOutputDirectory() const {
+   return imageOutputDirectory_;
+}
+
+/*!\brief Returns width of the timestep number in output filenames.
+ * \return Width of the timestep part in filenames.
+ */
+inline uint8_t Raytracer::getFilenameTimestepWidth() const {
+   return filenameTimestepWidth_;
+}
+
+/*!\brief Returns whether the rendering of planes gets confined in the simulation domain.
+ * \return True if the rendering of planes gets confined in the simulation domain.
+ */
+inline bool Raytracer::getConfinePlanesToDomain() const {
+   return confinePlanesToDomain_;
+}
+
+/*!\brief Set the background color of the scene.
+ *
+ * \param color New background color.
+ */
+inline void Raytracer::setBackgroundColor(const Color& color) {
+   backgroundColor_ = color;
+}
+   
+/*!\brief Enable / disable outputting images in the specified directory.
+ * \param enabled Set to true / false to enable / disable image output.
+ */
+inline void Raytracer::setImageOutputEnabled(const bool enabled) {
+   imageOutputEnabled_ = enabled;
+}
+
+/*!\brief Enable / disable outputting local images in the specified directory.
+ * \param enabled Set to true / false to enable / disable image output.
+ */
+inline void Raytracer::setLocalImageOutputEnabled(const bool enabled) {
+   localImageOutputEnabled_ = enabled;
+}
+   
+/*!\brief Enable / disable outputting images in the specified directory.
+ * \param enabled Set to true / false to enable / disable image output.
+ */
+inline void Raytracer::setImageOutputDirectory(const std::string& path) {
+   filesystem::path dir (path);
+   WALBERLA_CHECK(filesystem::exists(dir) && filesystem::is_directory(dir), "Image output directory " << path << " is invalid.");
+   
+   imageOutputDirectory_ = path;
+}
+
+/*!\brief Set width of timestep number in output filenames.
+ * \param width Width of timestep part in a filename.
+ */
+inline void Raytracer::setFilenameTimestepWidth(uint8_t width) {
+   filenameTimestepWidth_ = width;
+}
+
+/*!\brief Set the algorithm to use while ray tracing.
+ * \param algorithm One of RAYTRACE_HASHGRIDS, RAYTRACE_NAIVE, RAYTRACE_COMPARE_BOTH, RAYTRACE_COMPARE_BOTH_STRICTLY (abort on errors).
+ */
+inline void Raytracer::setRaytracingAlgorithm(Algorithm algorithm) {
+   raytracingAlgorithm_ = algorithm;
+}
+
+/*!\brief Set the algorithm to use while reducing.
+ * \param reductionMethod One of MPI_GATHER or MPI_REDUCE (latter one only works on MPI builds).
+ */
+inline void Raytracer::setReductionMethod(ReductionMethod reductionMethod) {
+   reductionMethod_ = reductionMethod;
+}
+
+/*!\brief Set if the rendering of planes should get confined to the simulation domain.
+ * \param confinePlanesToOrigin True if the rendering of planes should get confined to the simulation domain.
+ */
+inline void Raytracer::setConfinePlanesToDomain(bool confinePlanesToDomain) {
+   confinePlanesToDomain_ = confinePlanesToDomain;
+}
+
+/*!\brief Checks if a plane should get rendered.
+ * \param plane Plane to check for visibility.
+ * \param ray Ray which is intersected with plane.
+ *
+ * Checks if a plane should get rendered by comparing the planes normal and the ray direction.
+ * If the rays direction vectors projection on the planes normal is positive, the plane is considered invisible.
+ */
+inline bool Raytracer::isPlaneVisible(const PlaneID plane, const Ray& ray) const {
+   return plane->getNormal() * ray.getDirection() < 0;
+}
+
+/*!\brief Converts a coordinate to an array index.
+ * \param x X component of the coordinate.
+ * \param y Y component of the coordinate.
+ * \return Array index.
+ */
+inline size_t Raytracer::coordinateToArrayIndex(size_t x, size_t y) const {
+   return y*pixelsHorizontal_*antiAliasFactor_ + x;
+}
+
+/*!\brief Traces a ray in the global body storage and finds the closest ray-body intersection.
+ * \param ray Ray which is shot.
+ * \param body_closest Reference where the closest body will be stored in.
+ * \param t_closest Reference where the distance of the currently closest body is stored in,
+                    will get updated if closer intersection found.
+ * \param n_closest Reference where the intersection normal will be stored in.
+ */
+template <typename BodyTypeTuple>
+inline void Raytracer::traceRayInGlobalBodyStorage(const Ray& ray, BodyID& body_closest, real_t& t_closest, Vec3& n_closest) const {
+   WALBERLA_ROOT_SECTION(){
+      real_t t = std::numeric_limits<real_t>::max();
+      Vec3 n;
+      
+      IntersectsFunctor func(ray, t, n);
+      
+      for(auto bodyIt = globalBodyStorage_->begin(); bodyIt != globalBodyStorage_->end(); ++bodyIt)
+      {
+         if (!isBodyVisibleFunc_(bodyIt.getBodyID()))
+         {
+            continue;
+         }
+         
+         bool isPlane = (bodyIt->getTypeID() == Plane::getStaticTypeID());
+         if (isPlane)
+         {
+            PlaneID plane = (PlaneID)bodyIt.getBodyID();
+            if (!isPlaneVisible(plane, ray))
+            {
+               continue;
+            }
+         }
+         
+         bool intersects = SingleCast<BodyTypeTuple, IntersectsFunctor, bool>::execute(bodyIt.getBodyID(), func);
+         if (intersects && t < t_closest)
+         {
+            if (isPlane && confinePlanesToDomain_)
+            {
+               Vec3 intersectionPoint = ray.getOrigin()+ray.getDirection()*t;
+               if (!forest_->getDomain().contains(intersectionPoint, real_t(1e-8)))
+               {
+                  continue;
+               }
+            }
+            // body was shot by ray and is currently closest to camera
+            t_closest = t;
+            body_closest = bodyIt.getBodyID();
+            n_closest = n;
+         }
+      }
+   }
+}
+
+/*!\brief Traces a ray naively and finds the closest ray-body intersection.
+ * \param ray Ray which is shot.
+ * \param body_closest Reference where the closest body will be stored in.
+ * \param t_closest Reference where the distance of the currently closest body is stored in,
+                    will get updated if closer intersection found.
+ * \param n_closest Reference where the intersection normal will be stored in.
+ */
+template <typename BodyTypeTuple>
+inline void Raytracer::traceRayNaively(const Ray& ray, BodyID& body_closest, real_t& t_closest, Vec3& n_closest) const {
+   real_t t = std::numeric_limits<real_t>::max();
+   Vec3 n;
+   
+   IntersectsFunctor func(ray, t, n);
+   
+   for (auto blockIt = forest_->begin(); blockIt != forest_->end(); ++blockIt) {
+      for (auto bodyIt = LocalBodyIterator::begin(*blockIt, storageID_); bodyIt != LocalBodyIterator::end(); ++bodyIt) {
+         if (!isBodyVisibleFunc_(bodyIt.getBodyID())) {
+            continue;
+         }
+         
+         bool intersects = SingleCast<BodyTypeTuple, IntersectsFunctor, bool>::execute(bodyIt.getBodyID(), func);
+         if (intersects && t < t_closest) {
+            // body was shot by ray and is currently closest to camera
+            t_closest = t;
+            body_closest = bodyIt.getBodyID();
+            n_closest = n;
+         }
+      }
+   }
+}
+
+/*!\brief Traces a ray in the global body storage and finds the closest ray-body intersection.
+ * \param ray Ray which is shot.
+ * \param body_closest Reference where the closest body will be stored in.
+ * \param t_closest Reference where the distance of the currently closest body is stored in,
+                    will get updated if closer intersection found.
+ * \param n_closest Reference where the intersection normal will be stored in.
+ */
+template <typename BodyTypeTuple>
+inline void Raytracer::traceRayInHashGrids(const Ray& ray, BodyID& body_closest, real_t& t_closest, Vec3& n_closest) const {
+   real_t t = std::numeric_limits<real_t>::max();
+   Vec3 n;
+   
+   for (auto blockIt = forest_->begin(); blockIt != forest_->end(); ++blockIt) {
+      const AABB& blockAABB = blockIt->getAABB();
+      const ccd::HashGrids* hashgrids = blockIt->uncheckedFastGetData<ccd::HashGrids>(ccdID_);
+      BodyID body = hashgrids->getClosestBodyIntersectingWithRay<BodyTypeTuple>(ray, blockAABB, t, n,
+                                                                                isBodyVisibleFunc_);
+      if (body != NULL && t < t_closest) {
+         t_closest = t;
+         body_closest = body;
+         n_closest = n;
+      }
+   }
+}
+   
+/*!\brief Does one raytracing step.
+ *
+ * \param timestep The timestep after which the raytracing starts.
+ *
+ * \attention Planes will not get rendered if their normal and the rays direction point in the approximately
+ * same direction. See Raytracer::isPlaneVisible() for further information.
+ */
+template <typename BodyTypeTuple>
+void Raytracer::generateImage(const size_t timestep, WcTimingTree* tt) {
+   if (tt != NULL) tt->start("Raytracing");
+   const real_t realMax = std::numeric_limits<real_t>::max();
+   
+   std::vector<BodyIntersectionInfo> intersections;
+   // contains for each pixel information about an intersection:
+   size_t bufferSize = (pixelsVertical_*antiAliasFactor_)*(pixelsHorizontal_*antiAliasFactor_);
+   std::vector<BodyIntersectionInfo> intersectionsBuffer(bufferSize);
+
+   if (raytracingAlgorithm_ == RAYTRACE_HASHGRIDS || raytracingAlgorithm_ == RAYTRACE_COMPARE_BOTH
+      || raytracingAlgorithm_ == RAYTRACE_COMPARE_BOTH_STRICTLY) {
+      if (tt != NULL) tt->start("HashGrids Update");
+      for (auto blockIt = forest_->begin(); blockIt != forest_->end(); ++blockIt) {
+         ccd::HashGrids* hashgrids = blockIt->getData<ccd::HashGrids>(ccdID_);
+         hashgrids->update();
+      }
+      if (tt != NULL) tt->stop("HashGrids Update");
+   }
+   
+   real_t t, t_closest;
+   Vec3 n;
+   Vec3 n_closest;
+   BodyID body_closest = NULL;
+   Ray ray(cameraPosition_, Vec3(1,0,0));
+   IntersectsFunctor func(ray, t, n);
+   bool isErrorneousPixel = false;
+   uint_t pixelErrors = 0;
+   std::map<BodyID, std::unordered_set<BodyID>> correctToIncorrectBodyIDsMap;
+   
+   if (tt != NULL) tt->start("Intersection Testing");
+   for (size_t x = 0; x < pixelsHorizontal_*antiAliasFactor_; x++) {
+      for (size_t y = 0; y < pixelsVertical_*antiAliasFactor_; y++) {
+         Vec3 pixelLocation = viewingPlaneOrigin_ + u_*(real_c(x)+real_t(0.5))*pixelWidth_ + v_*(real_c(y)+real_t(0.5))*pixelHeight_;
+         Vec3 direction = (pixelLocation - cameraPosition_).getNormalized();
+         ray.setDirection(direction);
+         
+         n.reset();
+         t_closest = realMax;
+         body_closest = NULL;
+         
+         if (raytracingAlgorithm_ == RAYTRACE_HASHGRIDS) {
+            traceRayInHashGrids<BodyTypeTuple>(ray, body_closest, t_closest, n_closest);
+         } else if (raytracingAlgorithm_ == RAYTRACE_NAIVE) {
+            traceRayNaively<BodyTypeTuple>(ray, body_closest, t_closest, n_closest);
+         } else {
+            traceRayInHashGrids<BodyTypeTuple>(ray, body_closest, t_closest, n_closest);
+            BodyID hashgrids_body_closest = body_closest;
+            
+            t_closest = realMax;
+            body_closest = NULL;
+            traceRayNaively<BodyTypeTuple>(ray, body_closest, t_closest, n_closest);
+            
+            if (body_closest != hashgrids_body_closest) {
+               correctToIncorrectBodyIDsMap[body_closest].insert(hashgrids_body_closest);
+               isErrorneousPixel = true;
+               ++pixelErrors;
+            }
+         }
+         
+         traceRayInGlobalBodyStorage<BodyTypeTuple>(ray, body_closest, t_closest, n_closest);
+         
+         BodyIntersectionInfo& intersectionInfo = intersectionsBuffer[coordinateToArrayIndex(x, y)];
+         intersectionInfo.imageX = uint32_t(x);
+         intersectionInfo.imageY = uint32_t(y);
+         
+         if (!realIsIdentical(t_closest, realMax) && body_closest != NULL) {
+            Color color = getColor(body_closest, ray, t_closest, n_closest);
+            if (isErrorneousPixel) {
+               color = Color(1,0,0);
+               isErrorneousPixel = false;
+            }
+            
+            intersectionInfo.bodySystemID = body_closest->getSystemID();
+            intersectionInfo.t = t_closest;
+            intersectionInfo.r = color[0];
+            intersectionInfo.g = color[1];
+            intersectionInfo.b = color[2];
+            
+            intersections.push_back(intersectionInfo);
+         } else {
+            intersectionInfo.bodySystemID = 0;
+            intersectionInfo.t = realMax;
+            intersectionInfo.r = backgroundColor_[0];
+            intersectionInfo.g = backgroundColor_[1];
+            intersectionInfo.b = backgroundColor_[2];
+         }
+      }
+   }
+   if (tt != NULL) tt->stop("Intersection Testing");
+
+   if (raytracingAlgorithm_ == RAYTRACE_COMPARE_BOTH || raytracingAlgorithm_ == RAYTRACE_COMPARE_BOTH_STRICTLY) {
+      if (pixelErrors > 0) {
+         WALBERLA_LOG_WARNING(pixelErrors << " pixel errors found!");
+         
+         std::stringstream ss;
+         for (auto it: correctToIncorrectBodyIDsMap) {
+            const BodyID correctBody = it.first;
+            if (it.first != NULL) {
+               ss << " correct body: " << correctBody->getID() << "(" << correctBody->getHash() << ")";
+            } else {
+               ss << " no body naively found";
+            }
+            ss << ", hashgrids found:";
+            for (auto incorrectBody: it.second) {
+               ss << " ";
+               if (incorrectBody != NULL) {
+                  ss << incorrectBody->getID() << "(" << incorrectBody->getHash();
+               } else {
+                  ss << "NULL";
+               }
+            }
+            ss << std::endl;
+            ss << "  minCorner: " << correctBody->getAABB().minCorner() << ", maxCorner: " << correctBody->getAABB().maxCorner();
+            ss << std::endl;
+         }
+         WALBERLA_LOG_WARNING("Problematic bodies: " << std::endl << ss.str());
+         
+         if (raytracingAlgorithm_ == RAYTRACE_COMPARE_BOTH_STRICTLY) {
+            WALBERLA_ABORT("Pixel errors found, aborting due to strict comparison option.");
+         }
+      } else {
+         WALBERLA_LOG_INFO("No pixel errors found.");
+      }
+   }
+   
+   localOutput(intersectionsBuffer, timestep, tt);
+   
+   // Reduction with different methods only makes sense if actually using MPI.
+   // Besides that, the MPI reduce routine does not compile without MPI.
+   WALBERLA_MPI_SECTION() {
+      switch(reductionMethod_) {
+         case MPI_REDUCE:
+            syncImageUsingMPIReduce(intersectionsBuffer, tt);
+            break;
+         case MPI_GATHER:
+            syncImageUsingMPIGather(intersections, intersectionsBuffer, tt);
+            break;
+      }
+   } else {
+      syncImageUsingMPIGather(intersections, intersectionsBuffer, tt);
+   }
+   
+   output(intersectionsBuffer, timestep, tt);
+   
+   if (tt != NULL) tt->stop("Raytracing");
+}
+
+/*!\brief Computes the color for a certain intersection.
+ *
+ * \param body Intersected body.
+ * \param Ray Ray which intersected the body.
+ * \param t Distance from eye to intersection point.
+ * \param n Intersection normal at the intersection point.
+ *
+ * \return Computed color.
+ */
+inline Color Raytracer::getColor(const BodyID body, const Ray& ray, real_t t, const Vec3& n) const {
+   const ShadingParameters shadingParams = bodyToShadingParamsFunc_(body);
+   
+   const Vec3 intersectionPoint = ray.getOrigin() + ray.getDirection() * t;
+   Vec3 lightDirection = lighting_.pointLightOrigin - intersectionPoint;
+   lightDirection = lightDirection.getNormalized();
+   
+   real_t lambertian = std::max(real_t(0), lightDirection * n);
+   
+   real_t specular = real_t(0);
+   
+   if (lambertian > 0 || realIsEqual(lambertian, real_t(0))) {
+      // Blinn-Phong
+      Vec3 viewDirection = -ray.getDirection();
+      Vec3 halfDirection = (lightDirection + viewDirection).getNormalized();
+      real_t specularAngle = std::max(halfDirection * n, real_t(0));
+      specular = real_c(pow(specularAngle, shadingParams.shininess));
+   }
+   
+   Color color = lighting_.ambientColor.mulComponentWise(shadingParams.ambientColor)
+      + lighting_.diffuseColor.mulComponentWise(shadingParams.diffuseColor)*lambertian
+      + lighting_.specularColor.mulComponentWise(shadingParams.specularColor)*specular;
+   
+   // Capping of color channels to 1.
+   // Capping instead of scaling will make specular highlights stronger.
+   color.clamp();
+
+   return color;
+}
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/ShadingFunctions.h b/src/pe/raytracing/ShadingFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..65675bb21aaee1f85688341e064457c5bdfb6954
--- /dev/null
+++ b/src/pe/raytracing/ShadingFunctions.h
@@ -0,0 +1,176 @@
+//======================================================================================================================
+//
+//  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 Shading.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <pe/basic.h>
+#include <pe/Types.h>
+#include <pe/raytracing/Color.h>
+#include <pe/raytracing/ShadingParameters.h>
+#include <core/mpi/MPIWrapper.h>
+#include <core/mpi/MPIManager.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+inline ShadingParameters defaultBodyTypeDependentShadingParams (const BodyID body);
+inline ShadingParameters processRankDependentShadingParams (const BodyID body);
+inline ShadingParameters defaultShadingParams (const BodyID body);
+inline ShadingParameters blackShadingParams (const BodyID body);
+inline ShadingParameters whiteShadingParams (const BodyID body);
+inline ShadingParameters lightGreyShadingParams (const BodyID body);
+inline ShadingParameters greyShadingParams (const BodyID body);
+inline ShadingParameters darkGreyShadingParams (const BodyID body);
+inline ShadingParameters redShadingParams (const BodyID body);
+inline ShadingParameters greenShadingParams (const BodyID body);
+inline ShadingParameters blueShadingParams (const BodyID body);
+inline ShadingParameters violetShadingParams (const BodyID body);
+inline ShadingParameters yellowShadingParams (const BodyID body);
+
+inline ShadingParameters defaultBodyTypeDependentShadingParams (const BodyID body) {
+   auto bodyTypeID = body->getTypeID();
+   
+   if (bodyTypeID == Plane::getStaticTypeID()) {
+      return lightGreyShadingParams(body).makeMatte();
+   } else if (bodyTypeID == Sphere::getStaticTypeID()) {
+      return redShadingParams(body).makeGlossy();
+   } else if (bodyTypeID == Capsule::getStaticTypeID()) {
+      return blueShadingParams(body).makeGlossy();
+   } else if (bodyTypeID == Box::getStaticTypeID()) {
+      return violetShadingParams(body);
+   } else if (bodyTypeID == Ellipsoid::getStaticTypeID()) {
+      return yellowShadingParams(body).makeGlossy(60);
+   } else {
+      return defaultShadingParams(body);
+   }
+}
+
+inline ShadingParameters processRankDependentShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   int numProcesses = mpi::MPIManager::instance()->numProcesses();
+   int rank = mpi::MPIManager::instance()->rank();
+   
+   real_t hue = real_t(360) * real_t(rank)/real_t(numProcesses);
+   Color color = Color::colorFromHSV(hue, real_t(1), real_t(0.9));
+   
+   return ShadingParameters(color,
+                            color*real_t(0.5),
+                            Color(0,0,0),
+                            real_t(0));
+}
+   
+inline ShadingParameters defaultShadingParams (const BodyID body) {
+   return greyShadingParams(body);
+}
+   
+inline ShadingParameters whiteShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(1), real_t(1), real_t(1)),
+                       Color(real_t(0.9), real_t(0.9), real_t(0.9)),
+                       Color(real_t(0), real_t(0), real_t(0)),
+                       real_t(0));
+   return s;
+}
+   
+inline ShadingParameters blackShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0), real_t(0), real_t(0)),
+                       Color(real_t(0), real_t(0), real_t(0)),
+                       Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                       real_t(0));
+   return s;
+}
+
+inline ShadingParameters lightGreyShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0.82), real_t(0.82), real_t(0.82)),
+                       Color(real_t(0.5), real_t(0.5), real_t(0.5)),
+                       Color(real_t(0), real_t(0), real_t(0)),
+                       real_t(0));
+   return s;
+}
+
+inline ShadingParameters greyShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0.5), real_t(0.5), real_t(0.5)),
+                       Color(real_t(0.4), real_t(0.4), real_t(0.4)),
+                       Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                       real_t(0));
+   return s;
+}
+
+inline ShadingParameters darkGreyShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0.2), real_t(0.2), real_t(0.2)),
+                       Color(real_t(0.06), real_t(0.06), real_t(0.06)),
+                       Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                       real_t(0));
+   return s;
+}
+   
+inline ShadingParameters redShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(1), real_t(0), real_t(0)),
+                       Color(real_t(0.5), real_t(0), real_t(0)),
+                       Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                       real_t(0));
+   return s;
+}
+
+inline ShadingParameters greenShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0), real_t(0.72), real_t(0)),
+                       Color(real_t(0), real_t(0.41), real_t(0)),
+                       Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                       real_t(0));
+   return s;
+}
+
+inline ShadingParameters blueShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0.15), real_t(0.44), real_t(0.91)),
+                       Color(real_t(0), real_t(0), real_t(0.4)),
+                       Color(real_t(0.1), real_t(0.1), real_t(0.1)),
+                       real_t(0));
+   return s;
+}
+   
+inline ShadingParameters yellowShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(1), real_t(0.96), real_t(0)),
+                       Color(real_t(0.5), real_t(0.48), real_t(0)),
+                       Color(real_t(0), real_t(0), real_t(0)),
+                       real_t(0));
+   return s;
+}
+
+inline ShadingParameters violetShadingParams (const BodyID body) {
+   WALBERLA_UNUSED(body);
+   ShadingParameters s(Color(real_t(0.6), real_t(0), real_t(0.9)),
+                       Color(real_t(0.5), real_t(0), real_t(0.8)),
+                       Color(real_t(0), real_t(0), real_t(0)),
+                       real_t(0));
+   return s;
+}
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
diff --git a/src/pe/raytracing/ShadingParameters.h b/src/pe/raytracing/ShadingParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e476527d1bd665c4017780f8858dbd39aff8876
--- /dev/null
+++ b/src/pe/raytracing/ShadingParameters.h
@@ -0,0 +1,88 @@
+//======================================================================================================================
+//
+//  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 Shading.h
+//! \author Lukas Werner
+//
+//======================================================================================================================
+
+#pragma once
+
+#include <pe/basic.h>
+#include <pe/Types.h>
+#include <pe/raytracing/Color.h>
+
+namespace walberla {
+namespace pe {
+namespace raytracing {
+
+struct ShadingParameters {
+   Color diffuseColor;  //!< Primary color of the material.
+   Color ambientColor;  //!< Color the material has even when its not directly lit.
+   Color specularColor; //!< Color the specular highlight has.
+   real_t shininess;    //!< How shiny a material is (approximate range is between 1 and 100).
+   
+   /*!\brief Instantiation constructor for the Shading struct.
+    */
+   ShadingParameters () {
+      
+   }
+   
+   /*!\brief Instantiation constructor for the Shading struct.
+    * \param diffuseColor Primary color of the material.
+    * \param ambientColor Color the material has even when its not directly lit.
+    * \param specularColor Color this material contributes to on its specular highlights.
+    * \param shininess Shininess of the material.
+    */
+   ShadingParameters (const Color& _diffuseColor, const Color& _ambientColor, const Color& _specularColor, real_t _shininess)
+   : diffuseColor(_diffuseColor), ambientColor(_ambientColor), specularColor(_specularColor), shininess(_shininess) {
+      
+   }
+   
+   /*!\brief Instantiation constructor for the Shading struct.
+    * \param config Config handle.
+    *
+    * Config has to contain diffuseColor (Color), ambientColor (Color), specularColor (Color), shininess (real_t).
+    * Colors are Vec3's with values from 0 to 1.
+    */
+   ShadingParameters (const Config::BlockHandle& config) {
+      diffuseColor = config.getParameter<Color>("diffuseColor");
+      ambientColor = config.getParameter<Color>("ambientColor");
+      specularColor = config.getParameter<Color>("specularColor");
+      shininess = config.getParameter<real_t>("shininess");
+   }
+   
+   /*!\brief Makes a rendered object shiny by setting the shininess and adjusting the specularColor.
+    * \param _shininess Shininess
+    */
+   ShadingParameters& makeGlossy(real_t _shininess = 30) {
+      shininess = _shininess;
+      specularColor.set(real_t(1), real_t(1), real_t(1));
+      return *this;
+   }
+   
+   /*!\brief Makes the rendered object matte by setting the shininess attribute to zero and adjusting the specularColor.
+    */
+   ShadingParameters& makeMatte() {
+      shininess = 0;
+      specularColor.set(real_t(0.1), real_t(0.1), real_t(0.1));
+      return *this;
+   }
+};
+
+} //namespace raytracing
+} //namespace pe
+} //namespace walberla
+
diff --git a/tests/pe/CMakeLists.txt b/tests/pe/CMakeLists.txt
index b29ec6940fa922d1ee5e0e798f3afc9be1d4937a..49002bdc955b67fb6b97361a3a7e38f50e592fa4 100644
--- a/tests/pe/CMakeLists.txt
+++ b/tests/pe/CMakeLists.txt
@@ -127,5 +127,8 @@ waLBerla_execute_test( NAME   PE_STATICTYPEIDS )
 waLBerla_compile_test( NAME   PE_UNION FILES Union.cpp DEPENDS core  )
 waLBerla_execute_test( NAME   PE_UNION )
 
+waLBerla_compile_test( NAME   PE_RAYTRACING FILES Raytracing.cpp DEPENDS core  )
+waLBerla_execute_test( NAME   PE_RAYTRACING )
+
 waLBerla_compile_test( NAME   PE_VOLUMEINERTIA FILES VolumeInertia.cpp DEPENDS core  )
 waLBerla_execute_test( NAME   PE_VOLUMEINERTIA CONFIGURATIONS Release RelWithDbgInfo)
diff --git a/tests/pe/Raytracing.cpp b/tests/pe/Raytracing.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d214fa9c9b0c76ee0555b1668bafcc85fc2b3c55
--- /dev/null
+++ b/tests/pe/Raytracing.cpp
@@ -0,0 +1,912 @@
+#include <pe/basic.h>
+#include "pe/utility/BodyCast.h"
+
+#include "pe/Materials.h"
+
+#include "pe/rigidbody/Box.h"
+#include "pe/rigidbody/Capsule.h"
+#include "pe/rigidbody/Sphere.h"
+#include "pe/rigidbody/Plane.h"
+#include "pe/rigidbody/Union.h"
+#include "pe/rigidbody/Ellipsoid.h"
+
+#include "pe/rigidbody/SetBodyTypeIDs.h"
+#include "pe/Types.h"
+
+#include "core/debug/TestSubsystem.h"
+#include "core/DataTypes.h"
+#include "core/math/Vector3.h"
+
+#include <pe/raytracing/Ray.h>
+#include <pe/raytracing/Intersects.h>
+#include <pe/raytracing/Raytracer.h>
+#include <pe/raytracing/Color.h>
+#include <pe/raytracing/ShadingFunctions.h>
+
+#include <pe/ccd/HashGrids.h>
+#include "pe/rigidbody/BodyStorage.h"
+#include <core/timing/TimingTree.h>
+
+#include <pe/utility/GetBody.h>
+
+#include <sstream>
+#include <tuple>
+
+using namespace walberla;
+using namespace walberla::pe;
+using namespace walberla::pe::raytracing;
+
+typedef boost::tuple<Box, Plane, Sphere, Capsule, Ellipsoid> BodyTuple ;
+
+void SphereIntersectsTest()
+{
+   MaterialID iron = Material::find("iron");
+   Sphere sp1(123, 1, Vec3(3,3,3), Vec3(0,0,0), Quat(), 2, iron, false, true, false);
+   real_t t;
+   Vec3 n;
+   
+   // ray through the center
+   Ray ray1(Vec3(3,-5,3), Vec3(0,1,0));
+   WALBERLA_LOG_INFO("RAY -> SPHERE");
+   
+   WALBERLA_CHECK(intersects(&sp1, ray1, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(6));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(-1));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0));
+   
+   // ray tangential
+   Ray ray2(Vec3(3,-5,3), Vec3(0,7.5,real_t(std::sqrt(real_t(15))/real_t(2))).getNormalized());
+   WALBERLA_CHECK(intersects(&sp1, ray2, t, n));
+   
+   // sphere behind ray origin
+   Sphere sp2(123, 1, Vec3(3,-8,3), Vec3(0,0,0), Quat(), 2, iron, false, true, false);
+   WALBERLA_CHECK(!intersects(&sp2, ray1, t, n));
+   
+   // sphere around ray origin
+   Sphere sp3(123, 1, Vec3(3,-5,3), Vec3(0,0,0), Quat(), 2, iron, false, true, false);
+   WALBERLA_CHECK(intersects(&sp3, ray1, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(2));
+}
+
+void PlaneIntersectsTest() {
+   MaterialID iron = Material::find("iron");
+   // plane with center 3,3,3 and parallel to y-z plane
+   Plane pl1(1, 1, Vec3(3, 3, 3), Vec3(1, 0, 0), real_t(1.0), iron);
+   
+   Ray ray1(Vec3(-5,3,3), Vec3(1,0,0));
+   real_t t;
+   Vec3 n;
+   
+   WALBERLA_LOG_INFO("RAY -> PLANE");
+   WALBERLA_CHECK(intersects(&pl1, ray1, t, n), "ray through center did not hit");
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(8), "distance between ray and plane is incorrect");
+   
+   Ray ray2(Vec3(-5,3,3), Vec3(1,0,-1).getNormalized());
+   WALBERLA_CHECK(intersects(&pl1, ray2, t, n), "ray towards random point on plane didn't hit");
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(sqrt(real_t(128))), "distance between ray and plane is incorrect");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(-1), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+   
+   Plane pl1neg(1, 1, Vec3(3, 3, 3), Vec3(-1, 0, 0), real_t(1.0), iron);
+   WALBERLA_CHECK(intersects(&pl1neg, ray2, t, n), "ray towards random point on plane didn't hit");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(-1), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+   
+   Ray ray3(Vec3(-5,3,3), Vec3(-1,0,0).getNormalized());
+   Plane pl5(1, 1, Vec3(-7, 3, 3), Vec3(1, 0, 0), real_t(1.0), iron);
+   WALBERLA_CHECK(intersects(&pl5, ray3, t, n), "ray towards random point on plane didn't hit");
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(2), "distance between ray and plane is incorrect");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(1), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+   
+   // plane with center 3,3,3 and parallel to x-z plane
+   Plane pl2(1, 1, Vec3(3, 3, 3), Vec3(0, 1, 0), real_t(1.0), iron);
+   WALBERLA_CHECK(!intersects(&pl2, ray1, t, n), "ray parallel to plane shouldnt hit");
+   
+   // plane with center -10,3,3 and parallel to y-z plane
+   Plane pl4(1, 1, Vec3(-10, 3, 3), Vec3(1, 0, 0), real_t(1.0), iron);
+   WALBERLA_CHECK(!intersects(&pl4, ray1, t, n), "ray hit plane behind origin");
+   
+   Plane pl6(1, 1, Vec3(3, 3, 0), Vec3(-1, 0, 0), real_t(1.0), iron);
+   Ray ray4(Vec3(0,0,5), Vec3(1, 0, -1).getNormalized());
+   WALBERLA_CHECK(intersects(&pl6, ray4, t, n), "ray didnt hit");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(-1), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+}
+
+void BoxIntersectsTest() {
+   WALBERLA_LOG_INFO("RAY -> BOX");
+   
+   MaterialID iron = Material::find("iron");
+   real_t t;
+   Vec3 n;
+   
+   Box box1(127, 5, Vec3(0, -15, 0), Vec3(0, 0, 0), Quat(), Vec3(10, 10, 10), iron, false, true, false);
+   Ray ray1(Vec3(3,-5,3), Vec3(0,1,0));
+   WALBERLA_CHECK(!intersects(&box1, ray1, t, n));
+   
+   Box box2(128, 5, Vec3(0, -2, 0), Vec3(0, 0, 0), Quat(), Vec3(10, 10, 10), iron, false, true, false);
+   WALBERLA_CHECK(intersects(&box2, ray1, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(t, real_t(8), real_t(1e-7));
+   
+   Box box3(128, 5, Vec3(0, 5, 0), Vec3(0, 0, 0), Quat(), Vec3(10, 10, 10), iron, false, true, false);
+   WALBERLA_CHECK(intersects(&box3, ray1, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(5));
+   
+   Ray ray6(Vec3(-8,5,0), Vec3(1,0,0));
+   WALBERLA_CHECK(intersects(&box3, ray6, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(3));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(-1), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+   
+   Ray ray7(Vec3(8,5,0), Vec3(-1,0,0));
+   WALBERLA_CHECK(intersects(&box3, ray7, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(3));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(1), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+   
+   // ray origin within box
+   Ray ray2(Vec3(-2,0,0), Vec3(1,0,1).getNormalized());
+   WALBERLA_CHECK(intersects(&box3, ray2, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(t, real_t(7.0710), real_t(1e-4));
+   
+   Ray ray3(Vec3(3,-5,3), Vec3(2, real_t(-1.5), real_t(0.5)).getNormalized());
+   Box box4(128, 5, Vec3(0, 8, 0), Vec3(0, 0, 0), Quat(), Vec3(10, 10, 10), iron, false, true, false);
+   WALBERLA_CHECK(!intersects(&box4, ray3, t, n));
+   
+   Ray ray4(Vec3(3,-5,3), Vec3(-2, 3, real_t(0.5)).getNormalized());
+   WALBERLA_CHECK(intersects(&box4, ray4, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(t, real_t(9.7068), real_t(1e-4));
+   
+   Box box5(128, 5, Vec3(4, 0, 0), Vec3(0, 0, 0), Quat(), Vec3(4, 4, 4), iron, false, true, false);
+   box5.rotate(0,0,math::M_PI/4);
+   Ray ray5(Vec3(0,1.5,0), Vec3(1,0,0));
+   WALBERLA_CHECK(intersects(&box5, ray5, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(t, real_t(2.67157), real_t(1e-4));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(n[0], real_t(-0.707107), real_t(1e-5), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(n[1], real_t(0.707107), real_t(1e-5), "incorrect normal calculated");
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0), "incorrect normal calculated");
+}
+
+void AABBIntersectsTest() {
+   WALBERLA_LOG_INFO("RAY -> AABB");
+   
+   Ray ray1(Vec3(-5,5,5), Vec3(1,0,0));
+   real_t t;
+   
+   AABB aabb(0,0,0,
+             10,10,10);
+   
+   WALBERLA_CHECK(intersects(aabb, ray1, t));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(5));
+   
+   WALBERLA_CHECK(intersects(aabb, ray1, t, 1.0));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(4));
+   
+   Ray ray2(Vec3(-5,5,10.5), Vec3(1,0,0)); // ray shooting over aabb, but within padding passed to intersects
+   WALBERLA_CHECK(intersects(aabb, ray1, t, 1.0));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(4));
+}
+
+void CapsuleIntersectsTest() {
+   MaterialID iron = Material::find("iron");
+   real_t t;
+   Vec3 n;
+   
+   Capsule cp1(0, 0, Vec3(2,3,3), Vec3(0,0,0), Quat(), real_t(2), real_t(2), iron, false, true, false);
+   
+   // ray through the center
+   Ray ray1(Vec3(3,-5,3), Vec3(0,1,0));
+   WALBERLA_LOG_INFO("RAY -> CAPSULE");
+   
+   WALBERLA_CHECK(intersects(&cp1, ray1, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(6));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(-1));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0));
+   
+   Ray ray2(Vec3(-5,3,3), Vec3(1,0,0));
+   WALBERLA_CHECK(intersects(&cp1, ray2, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(4));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(-1));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0));
+}
+
+void EllipsoidTest() {
+   WALBERLA_LOG_INFO("RAY -> ELLIPSOID");
+   
+   MaterialID iron = Material::find("iron");
+   real_t t;
+   Vec3 n;
+   
+   Ellipsoid el1(0,0, Vec3(2,3,3), Vec3(0,0,0), Quat(), Vec3(2,3,1), iron, false, true, false);
+   
+   Ray ray1(Vec3(-2,3,3), Vec3(1,0,0).getNormalized());
+   WALBERLA_CHECK(intersects(&el1, ray1, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(2));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(-1));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(0));
+
+   Ray ray2(Vec3(2,3,0), Vec3(0,0,-1).getNormalized());
+   WALBERLA_CHECK(!intersects(&el1, ray2, t, n));
+   
+   Ray ray3(Vec3(2,3,5), Vec3(0,0,-1).getNormalized());
+   WALBERLA_CHECK(intersects(&el1, ray3, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL(t, real_t(1));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[0], real_t(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[1], real_t(0));
+   WALBERLA_CHECK_FLOAT_EQUAL(n[2], real_t(1));
+
+   Ray ray4(Vec3(-2,real_t(2),real_t(2)), Vec3(1,real_t(0),real_t(0.5)).getNormalized());
+   WALBERLA_CHECK(intersects(&el1, ray4, t, n));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(t, real_t(2.36809), real_t(1e-5));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(n[0], real_t(-0.78193), real_t(1e-5));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(n[1], real_t(-0.62324), real_t(1e-5));
+   WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(n[2], real_t(0.012265), real_t(1e-5));
+}
+
+ShadingParameters customBodyToShadingParams(const BodyID body) {
+   if (body->getID() == 10) {
+      return greenShadingParams(body).makeGlossy(30);
+   } else if (body->getID() == 7) {
+      return greenShadingParams(body).makeGlossy(10);
+   } else if (body->getID() == 9) {
+      return darkGreyShadingParams(body).makeGlossy(50);
+   } else if (body->getID() == 3) {
+      return redShadingParams(body).makeGlossy(200);
+   } else {
+      return defaultBodyTypeDependentShadingParams(body);
+   }
+}
+
+void RaytracerTest(Raytracer::Algorithm raytracingAlgorithm = Raytracer::RAYTRACE_HASHGRIDS, walberla::uint8_t antiAliasFactor = 1) {
+   WALBERLA_LOG_INFO("Raytracer");
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,10,10,10), Vector3<uint_t>(1,1,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling( globalBodyStorage, storageID ), "CCD");
+   
+   Lighting lighting(Vec3(0, 5, 8), // 8, 5, 9.5 gut für ebenen, 0,5,8
+                     Color(1, 1, 1), //diffuse
+                     Color(1, 1, 1), //specular
+                     Color(real_t(0.4), real_t(0.4), real_t(0.4))); //ambient
+   Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                       size_t(640), size_t(480),
+                       real_t(49.13), antiAliasFactor,
+                       Vec3(-5,5,5), Vec3(-1,5,5), Vec3(0,0,1), //-5,5,5; -1,5,5
+                       lighting,
+                       Color(real_t(0.2), real_t(0.2), real_t(0.2)),
+                       customBodyToShadingParams);
+   
+   MaterialID iron = Material::find("iron");
+   
+   //PlaneID xNegPlane = createPlane(*globalBodyStorage, 0, Vec3(-1,0,0), Vec3(5,0,0), iron);
+   // xNegPlane obstructs only the top left sphere and intersects some objects
+   
+   //PlaneID xNegPlaneClose = createPlane(*globalBodyStorage, 0, Vec3(-1,0,0), Vec3(1,0,0), iron);
+   
+   // Test Scene v1 - Spheres, (rotated) boxes, confining walls, tilted plane in right bottom back corner
+   createPlane(*globalBodyStorage, 0, Vec3(0,-1,0), Vec3(0,10,0), iron); // left wall
+   createPlane(*globalBodyStorage, 0, Vec3(0,1,0), Vec3(0,0,0), iron); // right wall
+   createPlane(*globalBodyStorage, 0, Vec3(0,0,1), Vec3(0,0,0), iron); // floor
+   createPlane(*globalBodyStorage, 0, Vec3(0,0,-1), Vec3(0,0,10), iron); // ceiling
+   createPlane(*globalBodyStorage, 0, Vec3(-1,0,0), Vec3(10,0,0), iron); // back wall
+   createPlane(*globalBodyStorage, 0, Vec3(1,0,0), Vec3(0,0,0), iron); // front wall, should not get rendered
+   
+   createPlane(*globalBodyStorage, 0, Vec3(-1,1,1), Vec3(8,2,2), iron); // tilted plane in right bottom back corner
+   
+   createSphere(*globalBodyStorage, *forest, storageID, 2, Vec3(6,real_t(9.5),real_t(9.5)), real_t(0.5));
+   createSphere(*globalBodyStorage, *forest, storageID, 3, Vec3(4,real_t(5.5),5), real_t(1));
+   createSphere(*globalBodyStorage, *forest, storageID, 6, Vec3(3,real_t(8.5),5), real_t(1));
+   BoxID box = createBox(*globalBodyStorage, *forest, storageID, 7, Vec3(5,real_t(6.5),5), Vec3(2,4,3));
+   if (box != NULL) box->rotate(0,math::M_PI/4,math::M_PI/4);
+   createBox(*globalBodyStorage, *forest, storageID, 8, Vec3(5,1,8), Vec3(2,2,2));
+   // Test scene v1 end
+   
+   // Test scene v2 additions start
+   createBox(*globalBodyStorage, *forest, storageID, 9, Vec3(9,9,5), Vec3(1,1,10));
+   createCapsule(*globalBodyStorage, *forest, storageID, 10, Vec3(3, 9, 1), real_t(0.5), real_t(7), iron);
+   CapsuleID capsule = createCapsule(*globalBodyStorage, *forest, storageID, 11, Vec3(7, real_t(3.5), real_t(7.5)), real_t(1), real_t(2), iron);
+   if (capsule != NULL) capsule->rotate(0,math::M_PI/3,math::M_PI/4-math::M_PI/8);
+   // Test scene v2 end
+   
+   // Test scene v3 additions start
+   EllipsoidID ellipsoid = createEllipsoid(*globalBodyStorage, *forest, storageID, 12, Vec3(6,2,real_t(2.5)), Vec3(3,2,real_t(1.2)));
+   ellipsoid->rotate(0, math::M_PI/real_t(6), 0);
+   // Test scene v3 end
+   
+   //raytracer.setTBufferOutputDirectory("tbuffer");
+   //raytracer.setTBufferOutputEnabled(true);
+   raytracer.setImageOutputEnabled(true);
+   //raytracer.setLocalImageOutputEnabled(true);
+   
+   raytracer.setRaytracingAlgorithm(raytracingAlgorithm);
+   raytracer.generateImage<BodyTuple>(0);
+}
+
+ShadingParameters customSpheresBodyToShadingParams(const BodyID body) {
+   if (body->getTypeID() == Plane::getStaticTypeID()) {
+      return greyShadingParams(body);
+   }
+   
+   switch (body->getID()) {
+      case 0:
+         return blueShadingParams(body).makeGlossy(1);
+      case 1:
+         return blueShadingParams(body).makeGlossy(10);
+      case 2:
+         return blueShadingParams(body).makeGlossy(30);
+      case 3:
+         return blueShadingParams(body).makeGlossy(80);
+      case 4:
+         return whiteShadingParams(body);
+      case 5:
+         return lightGreyShadingParams(body);
+      case 6:
+         return greyShadingParams(body);
+      case 7:
+         return darkGreyShadingParams(body);
+      case 8:
+         return blackShadingParams(body).makeGlossy(100);
+      case 9:
+         return redShadingParams(body);
+      case 10:
+         return blueShadingParams(body);
+      case 11:
+         return violetShadingParams(body);
+      case 12:
+         return greenShadingParams(body);
+      case 13:
+         return greenShadingParams(body).makeGlossy(30);
+      case 14:
+         return blueShadingParams(body).makeGlossy(1000);
+      default:
+         return lightGreyShadingParams(body);
+   }
+}
+
+void RaytracerSpheresTestScene(Raytracer::Algorithm raytracingAlgorithm = Raytracer::RAYTRACE_HASHGRIDS, walberla::uint8_t antiAliasFactor = 1) {
+   WALBERLA_LOG_INFO("Raytracer Spheres Scene");
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,10,10,10), Vector3<uint_t>(1,1,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling( globalBodyStorage, storageID ), "CCD");
+   
+   Lighting lighting(Vec3(0, 5, 8), // 8, 5, 9.5 gut für ebenen, 0,5,8
+                     Color(1, 1, 1), //diffuse
+                     Color(1, 1, 1), //specular
+                     Color(real_t(0.4), real_t(0.4), real_t(0.4))); //ambient
+   Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                       size_t(640), size_t(480),
+                       real_t(49.13), antiAliasFactor,
+                       Vec3(-5,5,5), Vec3(-1,5,5), Vec3(0,0,1), //-5,5,5; -1,5,5
+                       lighting,
+                       Color(real_t(0.2),real_t(0.2),real_t(0.2)),
+                       customSpheresBodyToShadingParams);
+   
+   MaterialID iron = Material::find("iron");
+   
+   createPlane(*globalBodyStorage, 0, Vec3(0,-1,0), Vec3(0,10,0), iron); // left wall
+   createPlane(*globalBodyStorage, 0, Vec3(0,1,0), Vec3(0,0,0), iron); // right wall
+   createPlane(*globalBodyStorage, 0, Vec3(0,0,1), Vec3(0,0,0), iron); // floor
+   createPlane(*globalBodyStorage, 0, Vec3(0,0,-1), Vec3(0,0,10), iron); // ceiling
+   createPlane(*globalBodyStorage, 0, Vec3(-1,0,0), Vec3(10,0,0), iron); // back wall
+   createPlane(*globalBodyStorage, 0, Vec3(1,0,0), Vec3(0,0,0), iron); // front wall, should not get rendered
+   
+   walberla::id_t id=0;
+   for (int j=0; j<4; j++) {
+      for (int i=0; i<4; i++) {
+         createSphere(*globalBodyStorage, *forest, storageID, id, Vec3(6,real_c(i+1)*real_t(2),real_c(j+1)*real_t(2)), real_t(0.9));
+         id++;
+      }
+   }
+   
+   raytracer.setImageOutputEnabled(true);
+   
+   raytracer.setRaytracingAlgorithm(raytracingAlgorithm);
+   raytracer.generateImage<BodyTuple>(1);
+}
+
+ShadingParameters customHashGridsBodyToShadingParams(const BodyID body) {
+   if (body->getTypeID() == Sphere::getStaticTypeID()) {
+      return yellowShadingParams(body);
+   }
+   
+   return defaultBodyTypeDependentShadingParams(body);
+}
+
+
+void HashGridsTest(Raytracer::Algorithm raytracingAlgorithm, walberla::uint8_t antiAliasFactor,
+                   size_t boxes, size_t capsules, size_t spheres, size_t numberOfViews = 1,
+                   real_t boxLenMin = real_t(0.1), real_t boxLenMax = real_t(0.2), bool boxRotation = false,
+                   real_t capRadiusMin = real_t(0.1), real_t capRadiusMax = real_t(0.2), real_t capLenMin = real_t(0.1), real_t capLenMax = real_t(0.3),
+                   real_t sphereRadiusMin = real_t(0.1), real_t sphereRadiusMax = real_t(0.3)) {
+   WALBERLA_LOG_INFO("Generating " << boxes << " boxes, " << capsules << " capsules and " << spheres << " spheres");
+   
+   using namespace walberla::pe::ccd;
+   WcTimingTree tt;
+   tt.start("Setup");
+   
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,4,4,4), Vector3<uint_t>(2,3,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling(globalBodyStorage, storageID), "CCD");
+   
+   const AABB& forestAABB = forest->getDomain();
+   
+   bool removeUnproblematic = false;
+   std::vector<walberla::id_t> problematicBodyIDs = {165, 5, 31}; //{50, 44, 66, 155, 170, 51};
+   std::vector<walberla::id_t> bodySIDs;
+   
+   // generate bodies for test
+   std::vector<BodyID> bodies;
+   for (size_t i = 0; i < boxes; i++) {
+      real_t len = math::realRandom(boxLenMin, boxLenMax); //0.2 0.5
+      real_t x = math::realRandom(forestAABB.xMin(), forestAABB.xMax());
+      real_t y = math::realRandom(forestAABB.yMin(), forestAABB.yMax());
+      real_t z = math::realRandom(forestAABB.zMin(), forestAABB.zMax());
+      walberla::id_t id = walberla::id_t(i);
+      BoxID box_ = createBox(*globalBodyStorage, *forest, storageID, id, Vec3(x, y, z), Vec3(len, len, len));
+      WALBERLA_CHECK(box_ != NULL);
+      if (boxRotation) {
+         box_->rotate(0, math::realRandom(real_t(0), real_t(1))*math::M_PI, math::realRandom(real_t(0), real_t(1))*math::M_PI);
+      }
+      bodies.push_back(box_);
+      bodySIDs.push_back(box_->getSystemID());
+   }
+   
+   for (size_t i = 0; i < capsules; i++) {
+      real_t len = math::realRandom(capLenMin, capLenMax); // 0.2 0.5
+      real_t radius = math::realRandom(capRadiusMin, capRadiusMax);
+      real_t x = math::realRandom(forestAABB.xMin(), forestAABB.xMax());
+      real_t y = math::realRandom(forestAABB.yMin(), forestAABB.yMax());
+      real_t z = math::realRandom(forestAABB.zMin(), forestAABB.zMax());
+      walberla::id_t id = walberla::id_t(boxes+i);
+      CapsuleID capsule = createCapsule(*globalBodyStorage, *forest, storageID, id, Vec3(x, y, z), radius, len);
+      WALBERLA_CHECK(capsule != NULL);
+      capsule->rotate(0, math::realRandom(real_t(0), real_t(1))*math::M_PI, math::realRandom(real_t(0), real_t(1))*math::M_PI);
+      bodies.push_back(capsule);
+      bodySIDs.push_back(capsule->getSystemID());
+   }
+   
+   for (size_t i = 0; i < spheres; i++) {
+      real_t radius = math::realRandom(sphereRadiusMin, sphereRadiusMax); // 0.2 0.3
+      real_t x = math::realRandom(forestAABB.xMin(), forestAABB.xMax());
+      real_t y = math::realRandom(forestAABB.yMin(), forestAABB.yMax());
+      real_t z = math::realRandom(forestAABB.zMin(), forestAABB.zMax());
+      walberla::id_t id = walberla::id_t(boxes+capsules+i);
+      SphereID sphere = createSphere(*globalBodyStorage, *forest, storageID, id, Vec3(x, y, z), radius);
+      WALBERLA_CHECK(sphere != NULL);
+      bodies.push_back(sphere);
+      bodySIDs.push_back(sphere->getSystemID());
+   }
+   
+   for (auto blockIt = forest->begin(); blockIt != forest->end(); ++blockIt) {
+      ccd::HashGrids* hashgrids = blockIt->getData<ccd::HashGrids>(ccdID);
+      hashgrids->update();
+      for (auto bodyIt = LocalBodyIterator::begin(*blockIt, storageID); bodyIt != LocalBodyIterator::end(); ++bodyIt) {
+         if (removeUnproblematic && std::find(problematicBodyIDs.begin(), problematicBodyIDs.end(), bodyIt->getID()) == problematicBodyIDs.end()) {
+            bodyIt->setPosition(-100, -100, -100);
+         }
+      }
+   }
+   
+   MaterialID iron = Material::find("iron");
+   createPlane(*globalBodyStorage, 0, Vec3(0,-1,0), Vec3(0,forestAABB.yMax(),0), iron); // left wall
+   createPlane(*globalBodyStorage, 0, Vec3(0,1,0), Vec3(0,forestAABB.yMin(),0), iron); // right wall
+   createPlane(*globalBodyStorage, 0, Vec3(0,0,1), Vec3(0,0,forestAABB.zMin()), iron); // floor
+   createPlane(*globalBodyStorage, 0, Vec3(0,0,-1), Vec3(0,0,forestAABB.zMax()), iron); // ceiling
+   createPlane(*globalBodyStorage, 0, Vec3(-1,0,0), Vec3(forestAABB.xMax(),0,0), iron); // back wall
+   createPlane(*globalBodyStorage, 0, Vec3(1,0,0), Vec3(forestAABB.xMin(),0,0), iron); // front wall, should not get rendered
+   
+   
+   std::vector<std::tuple<Vec3, Vec3, Vec3>> viewVectors;
+   
+   // y up, in negative z direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, real_t(2.1), 7),
+                                     Vec3(real_t(2.1), 2, 4),
+                                     Vec3(0,1,0)));
+   // y up, in positive z direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, 2, -3),
+                                     Vec3(2, real_t(2.1), real_t(0.1)),
+                                     Vec3(0,1,0)));
+   // x up, in positive z direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, 2, -3),
+                                     Vec3(2, real_t(2.1), real_t(0.1)),
+                                     Vec3(1,0,0)));
+   // y and x up, in positive z direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, 2, -3),
+                                     Vec3(2, real_t(2.1), real_t(0.1)),
+                                     Vec3(1,1,0)));
+   // y and x up, in negative z direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, 2, 6.5),
+                                     Vec3(real_t(2.1), real_t(2.1), 4),
+                                     Vec3(real_t(0.5),1,0)));
+   // z up, in positive x direction
+   viewVectors.push_back(std::make_tuple(Vec3(-3, 2, real_t(1.9)),
+                                     Vec3(0, real_t(2.1), 2),
+                                     Vec3(0,0,1)));
+   // z up, in negative x direction
+   viewVectors.push_back(std::make_tuple(Vec3(7, 2, real_t(1.9)),
+                                     Vec3(4, real_t(2.1), 2),
+                                     Vec3(0,0,1)));
+   // z and y up, in negative x direction
+   viewVectors.push_back(std::make_tuple(Vec3(7, 2, real_t(1.9)),
+                                     Vec3(4, real_t(2.1), 2),
+                                     Vec3(0,1,1)));
+   // z and x up, in negative y direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, 6, real_t(1.9)),
+                                     Vec3(real_t(2.3), 4, 2),
+                                     Vec3(1,0,1)));
+   // z up, in positive y direction
+   viewVectors.push_back(std::make_tuple(Vec3(2, real_t(-3.6), real_t(1.9)),
+                                     Vec3(real_t(2.3), 0, real_t(2.1)),
+                                     Vec3(0,0,1)));
+   
+   Lighting lighting0(Vec3(forestAABB.xSize()/real_t(2)+1, forestAABB.ySize()/real_t(2),
+                           real_t(2)*forestAABB.zMax()+2), // 8, 5, 9.5 gut für ebenen, 0,5,8
+                      Color(1, 1, 1), //diffuse
+                      Color(1, 1, 1), //specular
+                      Color(real_t(0.4), real_t(0.4), real_t(0.4))); //ambient
+   tt.stop("Setup");
+
+   size_t i = 0;
+   for (auto& vector: viewVectors) {
+      if (i == numberOfViews) {
+         break;
+      }
+      
+      Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                           size_t(640), size_t(480),
+                           real_t(49.13), antiAliasFactor,
+                           std::get<0>(vector),
+                           std::get<1>(vector),
+                           std::get<2>(vector),
+                           lighting0,
+                           Color(real_t(0.2),real_t(0.2),real_t(0.2)),
+                           customHashGridsBodyToShadingParams);
+      raytracer.setImageOutputEnabled(true);
+      raytracer.setFilenameTimestepWidth(12);
+      WALBERLA_LOG_INFO("output #" << i << " to: " << (boxes*100000000 + capsules*10000 + spheres) << " in " << raytracer.getImageOutputDirectory());
+      raytracer.setRaytracingAlgorithm(raytracingAlgorithm);
+      raytracer.generateImage<BodyTuple>(boxes*100000000 + capsules*10000 + spheres, &tt);
+      i++;
+   }
+   
+   auto temp = tt.getReduced();
+   WALBERLA_ROOT_SECTION() {
+      std::cout << temp;
+   }
+}
+
+ShadingParameters customArtifactsBodyToShadingParams(const BodyID body) {
+   if (body->getTypeID() == Box::getStaticTypeID()) {
+      return lightGreyShadingParams(body);
+   }
+   return defaultShadingParams(body);
+}
+
+void raytraceArtifactsForest(Raytracer::Algorithm raytracingAlgorithm, walberla::uint8_t antiAliasFactor,
+                             const shared_ptr<BlockStorage> forest, const BlockDataID storageID,
+                             const shared_ptr<BodyStorage> globalBodyStorage,
+                             const BlockDataID ccdID,
+                             const Vec3& cameraPosition, const Vec3& lookAtPoint, const Vec3& upVector,
+                             size_t numberOfBoxes, walberla::uint8_t timestepWidth) {
+   WcTimingTree tt;
+
+   Lighting lighting(cameraPosition,
+                     Color(1, 1, 1), //diffuse
+                     Color(1, 1, 1), //specular
+                     Color(real_t(0.4), real_t(0.4), real_t(0.4))); //ambient
+   
+   Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                       size_t(640), size_t(480),
+                       real_t(49.13), antiAliasFactor,
+                       cameraPosition,
+                       lookAtPoint,
+                       upVector,
+                       lighting,
+                       Color(real_t(0.2),real_t(0.2),real_t(0.2)),
+                       customArtifactsBodyToShadingParams);
+   raytracer.setImageOutputEnabled(true);
+   raytracer.setFilenameTimestepWidth(timestepWidth);
+   raytracer.setRaytracingAlgorithm(raytracingAlgorithm);
+   WALBERLA_LOG_INFO("output to: " << numberOfBoxes << " in " << raytracer.getImageOutputDirectory());
+   raytracer.generateImage<BodyTuple>(numberOfBoxes, &tt);
+   
+   auto temp = tt.getReduced();
+   WALBERLA_ROOT_SECTION() {
+      std::cout << temp;
+   }
+}
+
+void HashGridsArtifactsTest(Raytracer::Algorithm raytracingAlgorithm, walberla::uint8_t antiAliasFactor,
+                            size_t boxes, real_t boxLenMin = real_t(0.1), real_t boxLenMax = real_t(0.2)) {
+   WALBERLA_LOG_INFO_ON_ROOT("HashGrids Artifacts Test - In negative Z direction");
+   
+   WALBERLA_LOG_INFO(" Generating " << boxes << " boxes");
+   
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,4,4,4), Vector3<uint_t>(1,1,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling(globalBodyStorage, storageID), "CCD");
+   
+   const AABB& forestAABB = forest->getDomain();
+   
+   // generate bodies for test
+   for (size_t i = 0; i < boxes; i++) {
+      real_t len = math::realRandom(boxLenMin, boxLenMax); //0.2 0.5
+      real_t x_min = math::realRandom(forestAABB.xMin()+len/real_t(2), forestAABB.xMax());
+      real_t y_min = math::realRandom(forestAABB.yMin()+len/real_t(2), forestAABB.yMax());
+      real_t z_min = math::realRandom(forestAABB.zMin()+len/real_t(2), forestAABB.zMax());
+      if (i%5 == 0) {
+         x_min = forestAABB.xMax() - math::realRandom(len/real_t(2), len);
+      } else if (i%5 == 1){
+         x_min = forestAABB.xMin() + math::realRandom(real_t(0), len/real_t(2));
+      } else if (i%5 == 2){
+         y_min = forestAABB.yMax() - math::realRandom(len/real_t(2), len);
+      } else if (i%5 == 3){
+         y_min = forestAABB.yMin() + math::realRandom(real_t(0), len/real_t(2));
+      } else if (i%5 == 4){
+         z_min = forestAABB.zMin() + math::realRandom(real_t(0), len/real_t(2));
+      }
+      walberla::id_t id = walberla::id_t(i);
+      BoxID box_ = createBox(*globalBodyStorage, *forest, storageID, id, Vec3(x_min, y_min, z_min), Vec3(len, len, len));
+      WALBERLA_CHECK(box_ != NULL);
+   }
+   
+   raytraceArtifactsForest(raytracingAlgorithm, antiAliasFactor,
+                           forest, storageID, globalBodyStorage, ccdID,
+                           Vec3(2, 2, 7), Vec3(2, 2, 4), Vec3(0,1,0),
+                           boxes, 3);
+}
+
+void HashGridsFromNegativeArtifactsTest(Raytracer::Algorithm raytracingAlgorithm, walberla::uint8_t antiAliasFactor,
+                                        size_t boxes, real_t boxLenMin = real_t(0.1), real_t boxLenMax = real_t(0.2)) {
+   WALBERLA_LOG_INFO_ON_ROOT("HashGrids Artifacts Test - In positive Z direction");
+   
+   WALBERLA_LOG_INFO_ON_ROOT(" Generating " << boxes << " boxes");
+   
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,4,4,4), Vector3<uint_t>(1,1,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling(globalBodyStorage, storageID), "CCD");
+   
+   const AABB& forestAABB = forest->getDomain();
+
+   // generate bodies for test
+   std::vector<BodyID> bodies;
+   for (size_t i = 0; i < boxes; i++) {
+      real_t len = math::realRandom(boxLenMin, boxLenMax); //0.2 0.5
+      real_t x_min = math::realRandom(forestAABB.xMin()+len/real_t(2), forestAABB.xMax());
+      real_t y_min = math::realRandom(forestAABB.yMin()+len/real_t(2), forestAABB.yMax());
+      real_t z_min = math::realRandom(forestAABB.zMin()+len/real_t(2), forestAABB.zMax());
+      
+      if (i%5 == 0) {
+         x_min = forestAABB.xMax() - math::realRandom(len/real_t(2), len);
+      } else if (i%5 == 1){
+         x_min = forestAABB.xMin() + math::realRandom(real_t(0), len/real_t(2));
+      } else if (i%5 == 2){
+         y_min = forestAABB.yMax() - math::realRandom(len/real_t(2), len);
+      } else if (i%5 == 3){
+         y_min = forestAABB.yMin() + math::realRandom(real_t(0), len/real_t(2));
+      } else if (i%5 == 4){
+         z_min = forestAABB.zMax() - math::realRandom(len/real_t(2), len);
+      }
+      
+      //real_t z_min = len+0.1;
+      walberla::id_t id = walberla::id_t(i);
+      BoxID box_ = createBox(*globalBodyStorage, *forest, storageID, id, Vec3(x_min, y_min, z_min), Vec3(len, len, len));
+      WALBERLA_CHECK(box_ != NULL);
+   }
+   
+   raytraceArtifactsForest(raytracingAlgorithm, antiAliasFactor,
+                           forest, storageID, globalBodyStorage, ccdID,
+                           Vec3(2, 2, -3), Vec3(2, 2, 0), Vec3(0,1,0),
+                           boxes, 4);
+}
+
+void HashGridsFromNegativeXArtifactsTest(Raytracer::Algorithm raytracingAlgorithm, walberla::uint8_t antiAliasFactor,
+                                         size_t boxes, real_t boxLenMin = real_t(0.1), real_t boxLenMax = real_t(0.2)) {
+   WALBERLA_LOG_INFO_ON_ROOT("HashGrids Artifacts Test - In positive X direction");
+   WALBERLA_LOG_INFO_ON_ROOT(" Generating " << boxes << " boxes");
+   
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,4,4,4), Vector3<uint_t>(1,1,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling(globalBodyStorage, storageID), "CCD");
+   
+   const AABB& forestAABB = forest->getDomain();
+   
+   // generate bodies for test
+   for (size_t i = 0; i < boxes; i++) {
+      real_t len = math::realRandom(boxLenMin, boxLenMax); //0.2 0.5
+      real_t x_min = math::realRandom(forestAABB.xMin()+len/real_t(2), forestAABB.xMax());
+      real_t y_min = math::realRandom(forestAABB.yMin()+len/real_t(2), forestAABB.yMax());
+      real_t z_min = math::realRandom(forestAABB.zMin()+len/real_t(2), forestAABB.zMax());
+      
+      if (i%5 == 0) {
+         z_min = forestAABB.zMax() - math::realRandom(len/real_t(2), len);
+      } else if (i%5 == 1){
+         z_min = forestAABB.zMin() + math::realRandom(real_t(0), len/real_t(2));
+      } else if (i%5 == 2){
+         y_min = forestAABB.yMax() - math::realRandom(len/real_t(2), len);
+      } else if (i%5 == 3){
+         y_min = forestAABB.yMin() + math::realRandom(real_t(0), len/real_t(2));
+      } else if (i%5 == 4){
+         x_min = forestAABB.xMax() - math::realRandom(len/real_t(2), len);
+      }
+      
+      //real_t z_min = len+0.1;
+      walberla::id_t id = walberla::id_t(i);
+      BoxID box_ = createBox(*globalBodyStorage, *forest, storageID, id, Vec3(x_min, y_min, z_min), Vec3(len, len, len));
+      WALBERLA_CHECK(box_ != NULL);
+   }
+   
+   raytraceArtifactsForest(raytracingAlgorithm, antiAliasFactor,
+                           forest, storageID, globalBodyStorage, ccdID,
+                           Vec3(-3, 2, 2), Vec3(0, 2, 2), Vec3(0,0,1),
+                           boxes, 6);
+}
+
+
+Vec3 minCornerToGpos(const Vec3& minCorner, real_t lengths) {
+   return minCorner + Vec3(lengths/2, lengths/2, lengths/2);
+}
+
+void HashGridsTestScene(Raytracer::Algorithm raytracingAlgorithm = Raytracer::RAYTRACE_HASHGRIDS, walberla::uint8_t antiAliasFactor = 1) {
+   WALBERLA_LOG_INFO_ON_ROOT("HashGrids Test Scene");
+   
+   shared_ptr<BodyStorage> globalBodyStorage = make_shared<BodyStorage>();
+   shared_ptr<BlockForest> forest = createBlockForest(AABB(0,0,0,8,8,8), Vector3<uint_t>(1,1,1), Vector3<bool>(false, false, false));
+   auto storageID = forest->addBlockData(createStorageDataHandling<BodyTuple>(), "Storage");
+   auto ccdID = forest->addBlockData(ccd::createHashGridsDataHandling(globalBodyStorage, storageID), "CCD");
+   
+   const AABB& forestAABB = forest->getDomain();
+   
+   std::vector<BodyID> bodies;
+   
+   // create bodies
+   size_t id = 0;
+   real_t len = real_t(0.6);
+   
+   real_t x_min = 0, y_min = 0;
+   len = real_t(1.2);
+   real_t gap = real_t(0.4);
+   
+   // cubes on z = 0 plane
+   for (int i = 0; ; ++i) {
+      x_min = forestAABB.xMin() + real_c(i)*(gap+len);
+      if (x_min > forestAABB.max(0)) {
+         break;
+      }
+      for (int j = 0; ; ++j) {
+         y_min = forestAABB.yMin() + real_c(j)*(gap+len);
+         if (y_min > forestAABB.max(1)) {
+            break;
+         }
+         
+         bodies.push_back(createBox(*globalBodyStorage, *forest, storageID, ++id, minCornerToGpos(Vec3(x_min, y_min, 0), len), Vec3(len, len, len)));
+      }
+   }
+   
+   //cubes on z = max plane
+   for (int i = 0; ; ++i) {
+      x_min = forestAABB.xMin() + real_c(i)*(gap+len);
+      if (x_min > forestAABB.max(0)) {
+         break;
+      }
+      for (int j = 0; ; ++j) {
+         y_min = forestAABB.yMin() + real_c(j)*(gap+len);
+         if (y_min > forestAABB.max(1)) {
+            break;
+         }
+         
+         bodies.push_back(createBox(*globalBodyStorage, *forest, storageID, ++id, minCornerToGpos(Vec3(x_min, y_min, forestAABB.zMax()-len), len), Vec3(len, len, len)));
+      }
+   }
+   
+   std::vector<std::tuple<Vec3, Vec3, Vec3>> viewVectors;
+   
+   // in negative x direction -> cubes to the right
+   viewVectors.push_back(std::make_tuple(Vec3(15,4,4),
+                                         Vec3(8,4,4),
+                                         Vec3(0,1,0)));
+   // in negative x direction and negative z direction, up vector in y direction -> cubes from the right tilted
+   viewVectors.push_back(std::make_tuple(Vec3(12,4,8),
+                                         Vec3(6,4,2),
+                                         Vec3(0,1,0)));
+   // in negative x direction and negative z direction, up vector in negative y direction
+   viewVectors.push_back(std::make_tuple(Vec3(12,4,8),
+                                         Vec3(6,4,2),
+                                         Vec3(0,-1,0)));
+   // in positive x direction
+   viewVectors.push_back(std::make_tuple(Vec3(-7,4,4),
+                                         Vec3(0,4,4),
+                                         Vec3(0,1,0)));
+   // in negative x direction
+   viewVectors.push_back(std::make_tuple(Vec3(4,4,15),
+                                         Vec3(4,4,8),
+                                         Vec3(0,1,0)));
+   
+   WcTimingTree tt;
+   
+   Lighting lighting(Vec3(1,2,15),
+                     Color(1, 1, 1), //diffuse
+                     Color(1, 1, 1), //specular
+                     Color(real_t(0.4), real_t(0.4), real_t(0.4))); //ambient
+   
+   int i = 0;
+   for (auto& vector: viewVectors) {
+      Raytracer raytracer(forest, storageID, globalBodyStorage, ccdID,
+                          size_t(640), size_t(480),
+                          real_t(49.13), antiAliasFactor,
+                          std::get<0>(vector),
+                          std::get<1>(vector),
+                          std::get<2>(vector),
+                          lighting,
+                          Color(real_t(0.2),real_t(0.2),real_t(0.2)));
+      
+      raytracer.setRaytracingAlgorithm(raytracingAlgorithm);
+      raytracer.setImageOutputEnabled(true);
+      raytracer.setFilenameTimestepWidth(1);
+      WALBERLA_LOG_INFO("output to: " << i << " in " << raytracer.getImageOutputDirectory());
+      raytracer.setRaytracingAlgorithm(raytracingAlgorithm);
+      raytracer.generateImage<BodyTuple>(size_t(i), &tt);
+      
+      auto temp = tt.getReduced();
+      WALBERLA_ROOT_SECTION() {
+         std::cout << temp;
+      }
+      
+      i++;
+   }
+}
+
+int main( int argc, char** argv )
+{
+   walberla::debug::enterTestMode();
+   walberla::MPIManager::instance()->initializeMPI( &argc, &argv );
+   SetBodyTypeIDs<BodyTuple>::execute();
+   math::seedRandomGenerator( static_cast<unsigned int>(1337 * mpi::MPIManager::instance()->worldRank()) );
+   
+   SphereIntersectsTest();
+   PlaneIntersectsTest();
+   BoxIntersectsTest();
+   AABBIntersectsTest();
+   CapsuleIntersectsTest();
+   EllipsoidTest();
+
+   const Raytracer::Algorithm algorithm = Raytracer::RAYTRACE_COMPARE_BOTH_STRICTLY;
+   const walberla::uint8_t antiAliasFactor = 1;
+   RaytracerTest(algorithm, antiAliasFactor);
+   RaytracerSpheresTestScene(algorithm, antiAliasFactor);
+   HashGridsTestScene(algorithm, antiAliasFactor);
+   
+   if (argc >= 2 && strcmp(argv[1], "--longrun") == 0) {
+      HashGridsTest(algorithm, antiAliasFactor,
+                    50, 30, 130,
+                    10);
+      HashGridsTest(algorithm, antiAliasFactor,
+                    60, 60, 3,
+                    1,
+                    real_t(0.1), real_t(0.3), true,
+                    real_t(0.1), real_t(0.2), real_t(0.1), real_t(0.2),
+                    real_t(0.5), real_t(0.6));
+      HashGridsArtifactsTest(algorithm, antiAliasFactor, 750, real_t(0.2), real_t(0.3));
+      HashGridsFromNegativeArtifactsTest(algorithm, antiAliasFactor, 750, real_t(0.2), real_t(0.3));
+      HashGridsFromNegativeXArtifactsTest(algorithm, antiAliasFactor, 750, real_t(0.2), real_t(0.3));
+   }
+   
+   return EXIT_SUCCESS;
+}
+