]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/client/mapblock_mesh.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / client / mapblock_mesh.cpp
index fbd7e2ab7ce8b577db2dccaa7c305d1d4232282f..f0d43ec7e2f12dac890382faa75ba1d2a61ac035 100644 (file)
@@ -30,16 +30,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "client/meshgen/collector.h"
 #include "client/renderingengine.h"
 #include <array>
+#include <algorithm>
 
 /*
        MeshMakeData
 */
 
-MeshMakeData::MeshMakeData(Client *client, bool use_shaders,
-               bool use_tangent_vertices):
+MeshMakeData::MeshMakeData(Client *client, bool use_shaders):
        m_client(client),
-       m_use_shaders(use_shaders),
-       m_use_tangent_vertices(use_tangent_vertices)
+       m_use_shaders(use_shaders)
 {}
 
 void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
@@ -81,33 +80,6 @@ void MeshMakeData::fill(MapBlock *block)
        }
 }
 
-void MeshMakeData::fillSingleNode(MapNode *node)
-{
-       m_blockpos = v3s16(0,0,0);
-
-       v3s16 blockpos_nodes = v3s16(0,0,0);
-       VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE,
-                       blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1));
-       s32 volume = area.getVolume();
-       s32 our_node_index = area.index(1,1,1);
-
-       // Allocate this block + neighbors
-       m_vmanip.clear();
-       m_vmanip.addArea(area);
-
-       // Fill in data
-       MapNode *data = new MapNode[volume];
-       for(s32 i = 0; i < volume; i++)
-       {
-               if (i == our_node_index)
-                       data[i] = *node;
-               else
-                       data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0);
-       }
-       m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent());
-       delete[] data;
-}
-
 void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos)
 {
        if (crack_level >= 0)
@@ -422,31 +394,40 @@ static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
        u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7;
        idx = (idx - 1) * 4;
 
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#if __GNUC__ > 7
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
        memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16));
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
 }
 
 static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
 {
-       if (dir.X > 0 || dir.Y > 0 || dir.Z < 0)
+       if (dir.X > 0 || dir.Y != 0 || dir.Z < 0)
                base -= scale;
        if (dir == v3s16(0,0,1)) {
-               *u = -base.X - 1;
-               *v = -base.Y - 1;
+               *u = -base.X;
+               *v = -base.Y;
        } else if (dir == v3s16(0,0,-1)) {
                *u = base.X + 1;
-               *v = -base.Y - 2;
+               *v = -base.Y - 1;
        } else if (dir == v3s16(1,0,0)) {
                *u = base.Z + 1;
-               *v = -base.Y - 2;
-       } else if (dir == v3s16(-1,0,0)) {
-               *u = -base.Z - 1;
                *v = -base.Y - 1;
+       } else if (dir == v3s16(-1,0,0)) {
+               *u = -base.Z;
+               *v = -base.Y;
        } else if (dir == v3s16(0,1,0)) {
                *u = base.X + 1;
-               *v = -base.Z - 2;
+               *v = -base.Z - 1;
        } else if (dir == v3s16(0,-1,0)) {
-               *u = base.X;
-               *v = base.Z;
+               *u = base.X + 1;
+               *v = base.Z + 1;
        }
 }
 
@@ -818,20 +799,19 @@ static void getTileInfo(
                u16 *lights,
                u8 &waving,
                TileSpec &tile,
+               // lol more Input
                bool xray,
-               std::set<content_t> xraySet
-       )
+               std::set<content_t> xraySet)
 {
        VoxelManipulator &vmanip = data->m_vmanip;
        const NodeDefManager *ndef = data->m_client->ndef();
        v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
-       
+
        const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
 
        content_t c0 = n0.getContent();
        if (xray && xraySet.find(c0) != xraySet.end())
                c0 = CONTENT_AIR;
-
        // Don't even try to get n1 if n0 is already CONTENT_IGNORE
        if (c0 == CONTENT_IGNORE) {
                makes_face = false;
@@ -915,6 +895,9 @@ static void updateFastFaceRow(
                g_settings->getBool("enable_shaders") &&
                g_settings->getBool("enable_waving_water");
 
+       static thread_local const bool force_not_tiling =
+                       g_settings->getBool("enable_dynamic_shadows");
+
        v3s16 p = startpos;
 
        u16 continuous_tiles_count = 1;
@@ -955,7 +938,8 @@ static void updateFastFaceRow(
                                        xray,
                                        xraySet);
 
-                       if (next_makes_face == makes_face
+                       if (!force_not_tiling
+                                       && next_makes_face == makes_face
                                        && next_p_corrected == p_corrected + translate_dir
                                        && next_face_dir_corrected == face_dir_corrected
                                        && memcmp(next_lights, lights, sizeof(lights)) == 0
@@ -1062,6 +1046,173 @@ static void applyTileColor(PreMeshBuffer &pmb)
        }
 }
 
+/*
+       MapBlockBspTree
+*/
+
+void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles)
+{
+       this->triangles = triangles;
+
+       nodes.clear();
+
+       // assert that triangle index can fit into s32
+       assert(triangles->size() <= 0x7FFFFFFFL);
+       std::vector<s32> indexes;
+       indexes.reserve(triangles->size());
+       for (u32 i = 0; i < triangles->size(); i++)
+               indexes.push_back(i);
+
+       root = buildTree(v3f(1, 0, 0), v3f(85, 85, 85), 40, indexes, 0);
+}
+
+/**
+ * @brief Find a candidate plane to split a set of triangles in two
+ * 
+ * The candidate plane is represented by one of the triangles from the set.
+ * 
+ * @param list Vector of indexes of the triangles in the set
+ * @param triangles Vector of all triangles in the BSP tree
+ * @return Address of the triangle that represents the proposed split plane
+ */
+static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
+{
+       // find the center of the cluster.
+       v3f center(0, 0, 0);
+       size_t n = list.size();
+       for (s32 i : list) {
+               center += triangles[i].centroid / n;
+       }
+
+       // find the triangle with the largest area and closest to the center
+       const MeshTriangle *candidate_triangle = &triangles[list[0]];
+       const MeshTriangle *ith_triangle;
+       for (s32 i : list) {
+               ith_triangle = &triangles[i];
+               if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
+                               (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
+                               ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
+                       candidate_triangle = ith_triangle;
+               }
+       }
+       return candidate_triangle;
+}
+
+s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
+{
+       // if the list is empty, don't bother
+       if (list.empty())
+               return -1;
+
+       // if there is only one triangle, or the delta is insanely small, this is a leaf node
+       if (list.size() == 1 || delta < 0.01) {
+               nodes.emplace_back(normal, origin, list, -1, -1);
+               return nodes.size() - 1;
+       }
+
+       std::vector<s32> front_list;
+       std::vector<s32> back_list;
+       std::vector<s32> node_list;
+
+       // split the list
+       for (s32 i : list) {
+               const MeshTriangle &triangle = (*triangles)[i];
+               float factor = normal.dotProduct(triangle.centroid - origin);
+               if (factor == 0)
+                       node_list.push_back(i);
+               else if (factor > 0)
+                       front_list.push_back(i);
+               else
+                       back_list.push_back(i);
+       }
+
+       // define the new split-plane
+       v3f candidate_normal(normal.Z, normal.X, normal.Y);
+       float candidate_delta = delta;
+       if (depth % 3 == 2)
+               candidate_delta /= 2;
+
+       s32 front_index = -1;
+       s32 back_index = -1;
+
+       if (!front_list.empty()) {
+               v3f next_normal = candidate_normal;
+               v3f next_origin = origin + delta * normal;
+               float next_delta = candidate_delta;
+               if (next_delta < 10) {
+                       const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
+                       next_normal = candidate->getNormal();
+                       next_origin = candidate->centroid;
+               }
+               front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
+
+               // if there are no other triangles, don't create a new node
+               if (back_list.empty() && node_list.empty())
+                       return front_index;
+       }
+
+       if (!back_list.empty()) {
+               v3f next_normal = candidate_normal;
+               v3f next_origin = origin - delta * normal;
+               float next_delta = candidate_delta;
+               if (next_delta < 10) {
+                       const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
+                       next_normal = candidate->getNormal();
+                       next_origin = candidate->centroid;
+               }
+
+               back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
+
+               // if there are no other triangles, don't create a new node
+               if (front_list.empty() && node_list.empty())
+                       return back_index;
+       }
+
+       nodes.emplace_back(normal, origin, node_list, front_index, back_index);
+
+       return nodes.size() - 1;
+}
+
+void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
+{
+       if (node < 0) return; // recursion break;
+
+       const TreeNode &n = nodes[node];
+       float factor = n.normal.dotProduct(viewpoint - n.origin);
+
+       if (factor > 0)
+               traverse(n.back_ref, viewpoint, output);
+       else
+               traverse(n.front_ref, viewpoint, output);
+
+       if (factor != 0)
+               for (s32 i : n.triangle_refs)
+                       output.push_back(i);
+
+       if (factor > 0)
+               traverse(n.front_ref, viewpoint, output);
+       else
+               traverse(n.back_ref, viewpoint, output);
+}
+
+
+
+/*
+       PartialMeshBuffer
+*/
+
+void PartialMeshBuffer::beforeDraw() const
+{
+       // Patch the indexes in the mesh buffer before draw
+
+       m_buffer->Indices.clear();
+       if (!m_vertex_indexes.empty()) {
+               for (auto index : m_vertex_indexes)
+                       m_buffer->Indices.push_back(index);
+       }
+       m_buffer->setDirty(scene::EBT_INDEX);
+}
+
 /*
        MapBlockMesh
 */
@@ -1077,10 +1228,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
        for (auto &m : m_mesh)
                m = new scene::SMesh();
        m_enable_shaders = data->m_use_shaders;
-       m_use_tangent_vertices = data->m_use_tangent_vertices;
        m_enable_vbo = g_settings->getBool("enable_vbo");
 
-       if (g_settings->getBool("enable_minimap")) {
+       if (data->m_client->getMinimap()) {
                m_minimap_mapblock = new MinimapMapblock;
                m_minimap_mapblock->getMinimapNodes(
                        &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);
@@ -1096,17 +1246,19 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
                X-Ray
        */
        bool xray = g_settings->getBool("xray");
-       std::set<content_t> xraySet;
+       std::set<content_t> xraySet, nodeESPSet;
        if (xray)
                xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef());
-       
+
+       nodeESPSet = splitToContentT(g_settings->get("node_esp_nodes"), data->m_client->ndef());
+
        /*
                We are including the faces of the trailing edges of the block.
                This means that when something changes, the caller must
                also update the meshes of the blocks at the leading edges.
 
                NOTE: This is the slowest part of this method.
-       */      
+       */
        {
                // 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
                //TimeTaker timer2("updateAllFastFaceRows()");
@@ -1114,6 +1266,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
        }
        // End of slow part
 
+       /*
+               NodeESP
+       */
+       {
+               v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
+               for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
+                       for (s16 y = 0; y < MAP_BLOCKSIZE; y++) {
+                               for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
+                                       v3s16 pos = v3s16(x, y, z) + blockpos_nodes;
+                                       const MapNode &node = data->m_vmanip.getNodeRefUnsafeCheckFlags(pos);
+                                       if (nodeESPSet.find(node.getContent()) != nodeESPSet.end())
+                                               esp_nodes.insert(pos);
+                               }
+                       }
+               }
+       }
+
        /*
                Convert FastFaces to MeshCollector
        */
@@ -1143,14 +1312,17 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
        */
 
        {
-               MapblockMeshGenerator generator(data, &collector);
-               generator.generate();
+               MapblockMeshGenerator(data, &collector,
+                       data->m_client->getSceneManager()->getMeshManipulator()).generate();
        }
 
        /*
                Convert MeshCollector to SMesh
        */
 
+       const bool desync_animations = g_settings->getBool(
+               "desynchronize_mapblock_texture_animation");
+
        for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
                for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
                {
@@ -1180,18 +1352,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
                        // - Texture animation
                        if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
                                // Add to MapBlockMesh in order to animate these tiles
-                               m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer;
-                               m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
-                               if (g_settings->getBool(
-                                               "desynchronize_mapblock_texture_animation")) {
+                               auto &info = m_animation_info[{layer, i}];
+                               info.tile = p.layer;
+                               info.frame = 0;
+                               if (desync_animations) {
                                        // Get starting position from noise
-                                       m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] =
+                                       info.frame_offset =
                                                        100000 * (2.0 + noise3d(
                                                        data->m_blockpos.X, data->m_blockpos.Y,
                                                        data->m_blockpos.Z, 0));
                                } else {
                                        // Play all synchronized
-                                       m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0;
+                                       info.frame_offset = 0;
                                }
                                // Replace tile texture with the first animation frame
                                p.layer.texture = (*p.layer.frames)[0].texture;
@@ -1202,19 +1374,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
                                // Dummy sunlight to handle non-sunlit areas
                                video::SColorf sunlight;
                                get_sunlight_color(&sunlight, 0);
-                               u32 vertex_count = p.vertices.size();
+
+                               std::map<u32, video::SColor> colors;
+                               const u32 vertex_count = p.vertices.size();
                                for (u32 j = 0; j < vertex_count; j++) {
                                        video::SColor *vc = &p.vertices[j].Color;
                                        video::SColor copy = *vc;
                                        if (vc->getAlpha() == 0) // No sunlight - no need to animate
                                                final_color_blend(vc, copy, sunlight); // Finalize color
                                        else // Record color to animate
-                                               m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy;
+                                               colors[j] = copy;
 
                                        // The sunlight ratio has been stored,
                                        // delete alpha (for the final rendering).
                                        vc->setAlpha(255);
                                }
+                               if (!colors.empty())
+                                       m_daynight_diffs[{layer, i}] = std::move(colors);
                        }
 
                        // Create material
@@ -1238,51 +1414,38 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
 
                        scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer];
 
-                       // Create meshbuffer, add to mesh
-                       if (m_use_tangent_vertices) {
-                               scene::SMeshBufferTangents *buf =
-                                               new scene::SMeshBufferTangents();
-                               buf->Material = material;
-                               buf->Vertices.reallocate(p.vertices.size());
-                               buf->Indices.reallocate(p.indices.size());
-                               for (const video::S3DVertex &v: p.vertices)
-                                       buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords));
-                               for (u16 i: p.indices)
-                                       buf->Indices.push_back(i);
-                               buf->recalculateBoundingBox();
-                               mesh->addMeshBuffer(buf);
-                               buf->drop();
-                       } else {
-                               scene::SMeshBuffer *buf = new scene::SMeshBuffer();
-                               buf->Material = material;
+                       scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+                       buf->Material = material;
+                       switch (p.layer.material_type) {
+                       // list of transparent materials taken from tile.h
+                       case TILE_MATERIAL_ALPHA:
+                       case TILE_MATERIAL_LIQUID_TRANSPARENT:
+                       case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
+                               {
+                                       buf->append(&p.vertices[0], p.vertices.size(),
+                                               &p.indices[0], 0);
+
+                                       MeshTriangle t;
+                                       t.buffer = buf;
+                                       for (u32 i = 0; i < p.indices.size(); i += 3) {
+                                               t.p1 = p.indices[i];
+                                               t.p2 = p.indices[i + 1];
+                                               t.p3 = p.indices[i + 2];
+                                               t.updateAttributes();
+                                               m_transparent_triangles.push_back(t);
+                                       }
+                               }
+                               break;
+                       default:
                                buf->append(&p.vertices[0], p.vertices.size(),
                                        &p.indices[0], p.indices.size());
-                               mesh->addMeshBuffer(buf);
-                               buf->drop();
+                               break;
                        }
-               }
-
-               /*
-                       Do some stuff to the mesh
-               */
-               m_camera_offset = camera_offset;
-               translateMesh(m_mesh[layer],
-                       intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));
-
-               if (m_use_tangent_vertices) {
-                       scene::IMeshManipulator* meshmanip =
-                               RenderingEngine::get_scene_manager()->getMeshManipulator();
-                       meshmanip->recalculateTangents(m_mesh[layer], true, false, false);
+                       mesh->addMeshBuffer(buf);
+                       buf->drop();
                }
 
                if (m_mesh[layer]) {
-#if 0
-                       // Usually 1-700 faces and 1-7 materials
-                       std::cout << "Updated MapBlock has " << fastfaces_new.size()
-                                       << " faces and uses " << m_mesh[layer]->getMeshBufferCount()
-                                       << " materials (meshbuffers)" << std::endl;
-#endif
-
                        // Use VBO for mesh (this just would set this for ever buffer)
                        if (m_enable_vbo)
                                m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC);
@@ -1290,24 +1453,27 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
        }
 
        //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+       m_bsp_tree.buildTree(&m_transparent_triangles);
 
        // Check if animation is required for this mesh
        m_has_animation =
                !m_crack_materials.empty() ||
                !m_daynight_diffs.empty() ||
-               !m_animation_tiles.empty();
+               !m_animation_info.empty();
 }
 
 MapBlockMesh::~MapBlockMesh()
 {
        for (scene::IMesh *m : m_mesh) {
-               if (m_enable_vbo && m)
+#if IRRLICHT_VERSION_MT_REVISION < 5
+               if (m_enable_vbo) {
                        for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
                                scene::IMeshBuffer *buf = m->getMeshBuffer(i);
                                RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
                        }
+               }
+#endif
                m->drop();
-               m = NULL;
        }
        delete m_minimap_mapblock;
 }
@@ -1327,25 +1493,22 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
                for (auto &crack_material : m_crack_materials) {
                        scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
                                getMeshBuffer(crack_material.first.second);
-                       std::string basename = crack_material.second;
 
                        // Create new texture name from original
-                       std::ostringstream os;
-                       os << basename << crack;
+                       std::string s = crack_material.second + itos(crack);
                        u32 new_texture_id = 0;
                        video::ITexture *new_texture =
-                                       m_tsrc->getTextureForMesh(os.str(), &new_texture_id);
+                                       m_tsrc->getTextureForMesh(s, &new_texture_id);
                        buf->getMaterial().setTexture(0, new_texture);
 
-                       // If the current material is also animated,
-                       // update animation info
-                       auto anim_iter = m_animation_tiles.find(crack_material.first);
-                       if (anim_iter != m_animation_tiles.end()) {
-                               TileLayer &tile = anim_iter->second;
+                       // If the current material is also animated, update animation info
+                       auto anim_it = m_animation_info.find(crack_material.first);
+                       if (anim_it != m_animation_info.end()) {
+                               TileLayer &tile = anim_it->second.tile;
                                tile.texture = new_texture;
                                tile.texture_id = new_texture_id;
                                // force animation update
-                               m_animation_frames[crack_material.first] = -1;
+                               anim_it->second.frame = -1;
                        }
                }
 
@@ -1353,28 +1516,25 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
        }
 
        // Texture animation
-       for (auto &animation_tile : m_animation_tiles) {
-               const TileLayer &tile = animation_tile.second;
+       for (auto &it : m_animation_info) {
+               const TileLayer &tile = it.second.tile;
                // Figure out current frame
-               int frameoffset = m_animation_frame_offsets[animation_tile.first];
-               int frame = (int)(time * 1000 / tile.animation_frame_length_ms
-                               + frameoffset) % tile.animation_frame_count;
+               int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
+                               + it.second.frame_offset) % tile.animation_frame_count;
                // If frame doesn't change, skip
-               if (frame == m_animation_frames[animation_tile.first])
+               if (frameno == it.second.frame)
                        continue;
 
-               m_animation_frames[animation_tile.first] = frame;
+               it.second.frame = frameno;
 
-               scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]->
-                       getMeshBuffer(animation_tile.first.second);
+               scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
 
-               const FrameSpec &animation_frame = (*tile.frames)[frame];
-               buf->getMaterial().setTexture(0, animation_frame.texture);
+               const FrameSpec &frame = (*tile.frames)[frameno];
+               buf->getMaterial().setTexture(0, frame.texture);
                if (m_enable_shaders) {
-                       if (animation_frame.normal_texture)
-                               buf->getMaterial().setTexture(1,
-                                       animation_frame.normal_texture);
-                       buf->getMaterial().setTexture(2, animation_frame.flags_texture);
+                       if (frame.normal_texture)
+                               buf->getMaterial().setTexture(1, frame.normal_texture);
+                       buf->getMaterial().setTexture(2, frame.flags_texture);
                }
        }
 
@@ -1401,16 +1561,64 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
        return true;
 }
 
-void MapBlockMesh::updateCameraOffset(v3s16 camera_offset)
+void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
 {
-       if (camera_offset != m_camera_offset) {
-               for (scene::IMesh *layer : m_mesh) {
-                       translateMesh(layer,
-                               intToFloat(m_camera_offset - camera_offset, BS));
-                       if (m_enable_vbo)
-                               layer->setDirty();
+       // nothing to do if the entire block is opaque
+       if (m_transparent_triangles.empty())
+               return;
+
+       v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
+       v3f rel_camera_pos = camera_pos - block_posf;
+
+       std::vector<s32> triangle_refs;
+       m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
+
+       // arrange index sequences into partial buffers
+       m_transparent_buffers.clear();
+
+       scene::SMeshBuffer *current_buffer = nullptr;
+       std::vector<u16> current_strain;
+       for (auto i : triangle_refs) {
+               const auto &t = m_transparent_triangles[i];
+               if (current_buffer != t.buffer) {
+                       if (current_buffer) {
+                               m_transparent_buffers.emplace_back(current_buffer, current_strain);
+                               current_strain.clear();
+                       }
+                       current_buffer = t.buffer;
+               }
+               current_strain.push_back(t.p1);
+               current_strain.push_back(t.p2);
+               current_strain.push_back(t.p3);
+       }
+
+       if (!current_strain.empty())
+               m_transparent_buffers.emplace_back(current_buffer, current_strain);
+}
+
+void MapBlockMesh::consolidateTransparentBuffers()
+{
+       m_transparent_buffers.clear();
+
+       scene::SMeshBuffer *current_buffer = nullptr;
+       std::vector<u16> current_strain;
+
+       // use the fact that m_transparent_triangles is already arranged by buffer
+       for (const auto &t : m_transparent_triangles) {
+               if (current_buffer != t.buffer) {
+                       if (current_buffer != nullptr) {
+                               this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
+                               current_strain.clear();
+                       }
+                       current_buffer = t.buffer;
                }
-               m_camera_offset = camera_offset;
+               current_strain.push_back(t.p1);
+               current_strain.push_back(t.p2);
+               current_strain.push_back(t.p3);
+       }
+
+       if (!current_strain.empty()) {
+               this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
        }
 }