]> git.lizzy.rs Git - minetest.git/commitdiff
Generalize mesh chunking, and make it configurable. (#13179)
authorlhofhansl <larsh@apache.org>
Wed, 8 Feb 2023 21:42:12 +0000 (13:42 -0800)
committerGitHub <noreply@github.com>
Wed, 8 Feb 2023 21:42:12 +0000 (13:42 -0800)
* Generalize mesh chunking. Set 3x3x3 chunks.

* Make mesh chunk size configurable... Default to 1 (off).

* Extract all mesh grid maths into a dedicated class

---------

Co-authored-by: x2048 <codeforsmile@gmail.com>
builtin/settingtypes.txt
src/client/client.cpp
src/client/client.h
src/client/clientmap.cpp
src/client/mapblock_mesh.cpp
src/client/mapblock_mesh.h
src/client/mesh_generator_thread.cpp
src/defaultsettings.cpp
src/util/directiontables.cpp
src/util/directiontables.h
src/util/numeric.h

index ec8a8ce471ba0803171670bd7a878baea45741ea..14cfdbc0b63e0840c344a9a56596c27d47aef44d 100644 (file)
@@ -1713,6 +1713,13 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc
 #    Warning: This option is EXPERIMENTAL!
 autoscale_mode (Autoscaling mode) enum disable disable,enable,force
 
+#    Side length of a cube of map blocks that the client will consider together
+#    when generating meshes.
+#    Larger values increase the utilization of the GPU by reducing the number of
+#    draw calls, benefiting especially high-end GPUs.
+#    Systems with a low-end GPU (or no GPU) would benefit from smaller values.
+client_mesh_chunk (Client Mesh Chunksize) int 1 1 16
+
 [**Font]
 
 font_bold (Font bold by default) bool false
index 66dee613ff312d621bf5a627ce5d9b36ffc7834b..8fbd56ac8368bc044c2e9e87ec8ec4d0b223fe8f 100644 (file)
@@ -143,6 +143,7 @@ Client::Client(
        }
 
        m_cache_save_interval = g_settings->getU16("server_map_save_interval");
+       m_mesh_grid = { g_settings->getU16("client_mesh_chunk") };
 }
 
 void Client::migrateModStorage()
@@ -564,7 +565,7 @@ void Client::step(float dtime)
                        MapBlock *block = sector->getBlockNoCreateNoEx(r.p.Y);
 
                        // The block in question is not visible (perhaps it is culled at the server),
-                       // create a blank block just to hold the 2x2x2 mesh.
+                       // create a blank block just to hold the chunk's mesh.
                        // If the block becomes visible later it will replace the blank block.
                        if (!block && r.mesh)
                                block = sector->createBlankBlock(r.p.Y);
@@ -607,10 +608,10 @@ void Client::step(float dtime)
                                v3s16 ofs;
 
                                // See also mapblock_mesh.cpp for the code that creates the array of minimap blocks.
-                               for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++)
-                               for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++)
-                               for (ofs.X = 0; ofs.X <= 1; ofs.X++) {
-                                       size_t i = ofs.Z * 4 + ofs.Y * 2 + ofs.X;
+                               for (ofs.Z = 0; ofs.Z < m_mesh_grid.cell_size; ofs.Z++)
+                               for (ofs.Y = 0; ofs.Y < m_mesh_grid.cell_size; ofs.Y++)
+                               for (ofs.X = 0; ofs.X < m_mesh_grid.cell_size; ofs.X++) {
+                                       size_t i = m_mesh_grid.getOffsetIndex(ofs);
                                        if (i < minimap_mapblocks.size() && minimap_mapblocks[i])
                                                m_minimap->addBlock(r.p + ofs, minimap_mapblocks[i]);
                                }
index c05e6d32560466af7323bf03516d096ac021c021..3c7fc0db2de95703bc1ffddc5d69aaea1fd2fc83 100644 (file)
@@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "network/peerhandler.h"
 #include "gameparams.h"
 #include <fstream>
+#include "util/numeric.h"
 
 #define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
 
@@ -437,6 +438,11 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
        {
                return m_env.getLocalPlayer()->formspec_prepend;
        }
+       inline MeshGrid getMeshGrid()
+       {
+               return m_mesh_grid;
+       }
+
 private:
        void loadMods();
 
@@ -602,4 +608,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
        u32 m_csm_restriction_noderange = 8;
 
        std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
+
+       // The number of blocks the client will combine for mesh generation.
+       MeshGrid m_mesh_grid;
 };
index 61f3f24f8ef34596b6739fbf2d22ea3df927af12..af77e5ab2afb9b223577325e10d8be2eae366d82 100644 (file)
@@ -304,6 +304,7 @@ void ClientMap::updateDrawList()
        blocks_seen.getChunk(camera_block).getBits(camera_block) = 0x07; // mark all sides as visible
 
        std::set<v3s16> shortlist;
+       MeshGrid mesh_grid = m_client->getMeshGrid();
 
        // Recursively walk the space and pick mapblocks for drawing
        while (blocks_to_consider.size() > 0) {
@@ -330,7 +331,6 @@ void ClientMap::updateDrawList()
 
                MapBlockMesh *mesh = block ? block->mesh : nullptr;
 
-
                // Calculate the coordinates for range and frutum culling
                v3f mesh_sphere_center;
                f32 mesh_sphere_radius;
@@ -376,13 +376,21 @@ void ClientMap::updateDrawList()
                        continue;
                }
 
-               // Block meshes are stored in blocks where all coordinates are even (lowest bit set to 0)
-               // Add them to the de-dup set.
-               shortlist.emplace(block_coord.X & ~1, block_coord.Y & ~1, block_coord.Z & ~1);
-               // All other blocks we can grab and add to the keeplist right away.
-               if (block) {
-                       m_keeplist.push_back(block);
+               if (mesh_grid.cell_size > 1) {
+                       // Block meshes are stored in the corner block of a chunk
+                       // (where all coordinate are divisible by the chunk size)
+                       // Add them to the de-dup set.
+                       shortlist.emplace(mesh_grid.getMeshPos(block_coord.X), mesh_grid.getMeshPos(block_coord.Y), mesh_grid.getMeshPos(block_coord.Z));
+                       // All other blocks we can grab and add to the keeplist right away.
+                       if (block) {
+                               m_keeplist.push_back(block);
+                               block->refGrab();
+                       }
+               }
+               else if (mesh) {
+                       // without mesh chunking we can add the block to the drawlist
                        block->refGrab();
+                       m_drawlist.emplace(block_coord, block);
                }
 
                // Decide which sides to traverse next or to block away
@@ -485,7 +493,7 @@ void ClientMap::updateDrawList()
 
        g_profiler->avg("MapBlocks shortlist [#]", shortlist.size());
 
-       assert(m_drawlist.empty());
+       assert(m_drawlist.empty() || shortlist.empty());
        for (auto pos : shortlist) {
                MapBlock * block = getBlockNoCreateNoEx(pos);
                if (block) {
@@ -612,6 +620,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
        auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
 
+       const MeshGrid mesh_grid = m_client->getMeshGrid();
        for (auto &i : m_drawlist) {
                v3s16 block_pos = i.first;
                MapBlock *block = i.second;
@@ -740,7 +749,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                        material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
                }
 
-               v3f block_wpos = intToFloat(descriptor.m_pos / 8 * 8 * MAP_BLOCKSIZE, BS);
+               v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
                m.setTranslation(block_wpos - offset);
 
                driver->setTransform(video::ETS_WORLD, m);
@@ -978,6 +987,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
                return;
        }
 
+       const MeshGrid mesh_grid = m_client->getMeshGrid();
        for (const auto &i : m_drawlist_shadow) {
                // only process specific part of the list & break early
                ++count;
@@ -1066,7 +1076,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
                        ++material_swaps;
                }
 
-               v3f block_wpos = intToFloat(descriptor.m_pos / 8 * 8 * MAP_BLOCKSIZE, BS);
+               v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
                m.setTranslation(block_wpos - offset);
 
                driver->setTransform(video::ETS_WORLD, m);
index c0931a570f840725160102c9df5f82256a4c378a..e6ccb764f4dcace2a23ab6d7d2b5e71ce3a291a4 100644 (file)
@@ -38,7 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 MeshMakeData::MeshMakeData(Client *client, bool use_shaders):
        m_client(client),
-       m_use_shaders(use_shaders)
+       m_use_shaders(use_shaders),
+       m_mesh_grid(client->getMeshGrid()),
+       side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size)
 {}
 
 void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
@@ -53,12 +55,11 @@ void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
        m_vmanip.addArea(voxel_area);
 }
 
-void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data)
+void MeshMakeData::fillBlockData(const v3s16 &bp, MapNode *data)
 {
        v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
        VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
 
-       v3s16 bp = m_blockpos + block_offset;
        v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
        m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
 }
@@ -1179,18 +1180,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
 
        v3s16 bp = data->m_blockpos;
        // Only generate minimap mapblocks at even coordinates.
-       if (((bp.X | bp.Y | bp.Z) & 1) == 0 && data->m_client->getMinimap()) {
-               m_minimap_mapblocks.resize(8, nullptr);
+       if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) {
+               m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr);
                v3s16 ofs;
 
                // See also client.cpp for the code that reads the array of minimap blocks.
-               for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++)
-               for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++)
-               for (ofs.X = 0; ofs.X <= 1; ofs.X++) {
+               for (ofs.Z = 0; ofs.Z < data->m_mesh_grid.cell_size; ofs.Z++)
+               for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++)
+               for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) {
                        v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
                        if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
                                MinimapMapblock *block = new MinimapMapblock;
-                               m_minimap_mapblocks[ofs.Z * 4 + ofs.Y * 2 + ofs.X] = block;
+                               m_minimap_mapblocks[data->m_mesh_grid.getOffsetIndex(ofs)] = block;
                                block->getMinimapNodes(&data->m_vmanip, p);
                        }
                }
@@ -1221,7 +1222,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
                Convert FastFaces to MeshCollector
        */
 
-       v3f offset = intToFloat((data->m_blockpos - data->m_blockpos / 8 * 8) * MAP_BLOCKSIZE, BS);
+       v3f offset = intToFloat((data->m_blockpos - data->m_mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS);
        MeshCollector collector(m_bounding_sphere_center, offset);
 
        {
@@ -1584,10 +1585,11 @@ std::unordered_map<v3s16, u8> get_solid_sides(MeshMakeData *data)
 {
        std::unordered_map<v3s16, u8> results;
        v3s16 ofs;
+       const u16 mesh_chunk = data->side_length / MAP_BLOCKSIZE;
 
-       for (ofs.X = 0; ofs.X < 2; ofs.X++)
-       for (ofs.Y = 0; ofs.Y < 2; ofs.Y++)
-       for (ofs.Z = 0; ofs.Z < 2; ofs.Z++) {
+       for (ofs.X = 0; ofs.X < mesh_chunk; ofs.X++)
+       for (ofs.Y = 0; ofs.Y < mesh_chunk; ofs.Y++)
+       for (ofs.Z = 0; ofs.Z < mesh_chunk; ofs.Z++) {
                v3s16 blockpos = data->m_blockpos + ofs;
                v3s16 blockpos_nodes = blockpos * MAP_BLOCKSIZE;
                const NodeDefManager *ndef = data->m_client->ndef();
index 82e9a0f22cf15b92e9432dda5900c43696d3014f..6d5f0dae3a9626b60f0c3ba44207c9e059db26d9 100644 (file)
@@ -43,6 +43,7 @@ struct MeshMakeData
        v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
        v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
        bool m_smooth_lighting = false;
+       MeshGrid m_mesh_grid;
        u16 side_length = MAP_BLOCKSIZE;
 
        Client *m_client;
@@ -54,7 +55,7 @@ struct MeshMakeData
                Copy block data manually (to allow optimizations by the caller)
        */
        void fillBlockDataBegin(const v3s16 &blockpos);
-       void fillBlockData(const v3s16 &block_offset, MapNode *data);
+       void fillBlockData(const v3s16 &bp, MapNode *data);
 
        /*
                Set the (node) position of a crack
index 6d1fe2bda1a588b74f8e995f561b3f905b99608b..95ae25074a6ae062cf6370ac70a7a19e2d30431a 100644 (file)
@@ -77,9 +77,11 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
 
        MutexAutoLock lock(m_mutex);
 
-       // Mesh is placed at even positions at all coordinates
-       // (every 8-th block) and will cover 8 blocks
-       v3s16 mesh_position(p.X & ~1, p.Y & ~1, p.Z & ~1);
+       MeshGrid mesh_grid = m_client->getMeshGrid();
+
+       // Mesh is placed at the corner block of a chunk
+       // (where all coordinate are divisible by the chunk size)
+       v3s16 mesh_position(mesh_grid.getMeshPos(p));
        /*
                Mark the block as urgent if requested
        */
@@ -99,14 +101,19 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
                        q->crack_level = m_client->getCrackLevel();
                        q->crack_pos = m_client->getCrackPos();
                        q->urgent |= urgent;
-                       for (std::size_t i = 0; i < q->map_blocks.size(); i++) {
+                       v3s16 pos;
+                       int i = 0;
+                       for (pos.X = q->p.X - 1; pos.X <= q->p.X + mesh_grid.cell_size; pos.X++)
+                       for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++)
+                       for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) {
                                if (!q->map_blocks[i]) {
-                                       MapBlock *block = map->getBlockNoCreateNoEx(q->p + g_64dirs[i]);
+                                       MapBlock *block = map->getBlockNoCreateNoEx(pos);
                                        if (block) {
                                                block->refGrab();
                                                q->map_blocks[i] = block;
                                        }
                                }
+                               i++;
                        }
                        return true;
                }
@@ -116,9 +123,12 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
                Make a list of blocks necessary for mesh generation and lock the blocks in memory.
        */
        std::vector<MapBlock *> map_blocks;
-       map_blocks.reserve(4*4*4);
-       for (v3s16 dp : g_64dirs) {
-               MapBlock *block = map->getBlockNoCreateNoEx(mesh_position + dp);
+       map_blocks.reserve((mesh_grid.cell_size+2)*(mesh_grid.cell_size+2)*(mesh_grid.cell_size+2));
+       v3s16 pos;
+       for (pos.X = mesh_position.X - 1; pos.X <= mesh_position.X + mesh_grid.cell_size; pos.X++)
+       for (pos.Z = mesh_position.Z - 1; pos.Z <= mesh_position.Z + mesh_grid.cell_size; pos.Z++)
+       for (pos.Y = mesh_position.Y - 1; pos.Y <= mesh_position.Y + mesh_grid.cell_size; pos.Y++) {
+               MapBlock *block = map->getBlockNoCreateNoEx(pos);
                map_blocks.push_back(block);
                if (block)
                        block->refGrab();
@@ -182,13 +192,16 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
 {
        MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders);
        q->data = data;
-       data->side_length = 2 * MAP_BLOCKSIZE;
 
        data->fillBlockDataBegin(q->p);
 
-       for (std::size_t i = 0; i < 64; i++) {
-               MapBlock *block = q->map_blocks[i];
-               data->fillBlockData(g_64dirs[i], block ? block->getData() : block_placeholder.data);
+       v3s16 pos;
+       int i = 0;
+       for (pos.X = q->p.X - 1; pos.X <= q->p.X + data->m_mesh_grid.cell_size; pos.X++)
+       for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + data->m_mesh_grid.cell_size; pos.Z++)
+       for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + data->m_mesh_grid.cell_size; pos.Y++) {
+               MapBlock *block = q->map_blocks[i++];
+               data->fillBlockData(pos, block ? block->getData() : block_placeholder.data);
        }
 
        data->setCrack(q->crack_level, q->crack_pos);
index 5ef9ba5e90619de468d007e459e31ec166cad209..0296967a5f56f0e169ca17e11d95a1a08b1d4af5 100644 (file)
@@ -185,6 +185,7 @@ void set_default_settings()
        settings->setDefault("fps_max", "60");
        settings->setDefault("fps_max_unfocused", "20");
        settings->setDefault("viewing_range", "190");
+       settings->setDefault("client_mesh_chunk", "1");
 #if ENABLE_GLES
        settings->setDefault("near_plane", "0.1");
 #endif
index 86ea6a45f74024284122ecd3bcb6e1c516a70e15..297058c9c8092beb1a8743e664438b0c7cbbd2ff 100644 (file)
@@ -110,78 +110,6 @@ const v3s16 g_27dirs[27] =
        v3s16(0,0,0),
 };
 
-const v3s16 g_64dirs[64] =
-{
-       // +right, +top, +back
-       v3s16( -1, -1, -1),
-       v3s16( -1,  0, -1),
-       v3s16( -1,  1, -1),
-       v3s16( -1,  2, -1),
-       v3s16( -1, -1,  0),
-       v3s16( -1,  0,  0),
-       v3s16( -1,  1,  0),
-       v3s16( -1,  2,  0),
-       v3s16( -1, -1,  1),
-       v3s16( -1,  0,  1),
-       v3s16( -1,  1,  1),
-       v3s16( -1,  2,  1),
-       v3s16( -1, -1,  2),
-       v3s16( -1,  0,  2),
-       v3s16( -1,  1,  2),
-       v3s16( -1,  2,  2),
-
-       v3s16(  0, -1, -1),
-       v3s16(  0,  0, -1),
-       v3s16(  0,  1, -1),
-       v3s16(  0,  2, -1),
-       v3s16(  0, -1,  0),
-       v3s16(  0,  0,  0),
-       v3s16(  0,  1,  0),
-       v3s16(  0,  2,  0),
-       v3s16(  0, -1,  1),
-       v3s16(  0,  0,  1),
-       v3s16(  0,  1,  1),
-       v3s16(  0,  2,  1),
-       v3s16(  0, -1,  2),
-       v3s16(  0,  0,  2),
-       v3s16(  0,  1,  2),
-       v3s16(  0,  2,  2),
-
-       v3s16(  1, -1, -1),
-       v3s16(  1,  0, -1),
-       v3s16(  1,  1, -1),
-       v3s16(  1,  2, -1),
-       v3s16(  1, -1,  0),
-       v3s16(  1,  0,  0),
-       v3s16(  1,  1,  0),
-       v3s16(  1,  2,  0),
-       v3s16(  1, -1,  1),
-       v3s16(  1,  0,  1),
-       v3s16(  1,  1,  1),
-       v3s16(  1,  2,  1),
-       v3s16(  1, -1,  2),
-       v3s16(  1,  0,  2),
-       v3s16(  1,  1,  2),
-       v3s16(  1,  2,  2),
-
-       v3s16(  2, -1, -1),
-       v3s16(  2,  0, -1),
-       v3s16(  2,  1, -1),
-       v3s16(  2,  2, -1),
-       v3s16(  2, -1,  0),
-       v3s16(  2,  0,  0),
-       v3s16(  2,  1,  0),
-       v3s16(  2,  2,  0),
-       v3s16(  2, -1,  1),
-       v3s16(  2,  0,  1),
-       v3s16(  2,  1,  1),
-       v3s16(  2,  2,  1),
-       v3s16(  2, -1,  2),
-       v3s16(  2,  0,  2),
-       v3s16(  2,  1,  2),
-       v3s16(  2,  2,  2),
-};
-
 const u8 wallmounted_to_facedir[6] = {
        20,
        0,
index 7cdcebaa4417ec7e6712d4388a6402b96338ed8b..3883a6e37ce65dca27398bf3904fcc13751f7ef8 100644 (file)
@@ -31,9 +31,6 @@ extern const v3s16 g_26dirs[26];
 // 26th is (0,0,0)
 extern const v3s16 g_27dirs[27];
 
-// all positions around an octablock in sector-first order
-extern const v3s16 g_64dirs[64];
-
 extern const u8 wallmounted_to_facedir[6];
 
 extern const v3s16 wallmounted_dirs[8];
index 265046a63243c4769260e585d18df57ef627c2af..f0d4fe48214b3a50160732b01e01ef3ebbcfa99c 100644 (file)
@@ -145,6 +145,37 @@ inline v3s16 componentwise_max(const v3s16 &a, const v3s16 &b)
        return v3s16(MYMAX(a.X, b.X), MYMAX(a.Y, b.Y), MYMAX(a.Z, b.Z));
 }
 
+/// @brief Describes a grid with given step, oirginating at (0,0,0)
+struct MeshGrid {
+       u16 cell_size;
+
+       u32 getCellVolume() const { return cell_size * cell_size * cell_size; }
+
+       /// @brief returns closest step of the grid smaller than p
+       s16 getMeshPos(s16 p) const
+       {
+               return ((p - (p < 0) * (cell_size - 1)) / cell_size * cell_size);
+       }
+
+       /// @brief Returns coordinates of the origin of the grid cell containing p
+       v3s16 getMeshPos(v3s16 p) const
+       {
+               return v3s16(getMeshPos(p.X), getMeshPos(p.Y), getMeshPos(p.Z));
+       }
+       
+       /// @brief Returns true if p is an origin of a cell in the grid.
+       bool isMeshPos(v3s16 &p) const
+       {
+               return ((p.X + p.Y + p.Z) % cell_size) == 0;
+       }
+
+       /// @brief Returns index of the given offset in a grid cell
+       /// All offset coordinates must be smaller than the size of the cell
+       u16 getOffsetIndex(v3s16 offset) const
+       {
+               return (offset.Z * cell_size + offset.Y) * cell_size + offset.X;
+       }
+};
 
 /** Returns \p f wrapped to the range [-360, 360]
  *