Commit 8e7adf05 authored by Dominik Schuster's avatar Dominik Schuster
Browse files

Merge branch 'master' into fluidizedbed_showcase

parents e17be593 df7d374c
......@@ -4,6 +4,9 @@ ui_*
qrc_*
*~
# macOS
**/.DS_Store
# Backup files of kate/kwrite
# Generated files
......
......@@ -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();
......
......@@ -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 >;
}
}
......@@ -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();
......
......@@ -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.
*/
}
......
......@@ -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 }
......
......@@ -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
......
......@@ -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;