]> git.lizzy.rs Git - minetest.git/commitdiff
Add depth sorting for node faces (#11696)
authorx2048 <codeforsmile@gmail.com>
Sat, 2 Apr 2022 08:42:27 +0000 (10:42 +0200)
committerGitHub <noreply@github.com>
Sat, 2 Apr 2022 08:42:27 +0000 (10:42 +0200)
Use BSP tree to order transparent triangles
https://en.wikipedia.org/wiki/Binary_space_partitioning

builtin/settingtypes.txt
src/client/clientmap.cpp
src/client/clientmap.h
src/client/content_mapblock.cpp
src/client/mapblock_mesh.cpp
src/client/mapblock_mesh.h
src/client/tile.h
src/defaultsettings.cpp

index 3dc165bd16aecae74a2105398bd8207028541de1..b4230735b8355aa31b44ab467d6d44b2c54f48df 100644 (file)
@@ -858,6 +858,10 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force
 #    A restart is required after changing this.
 show_entity_selectionbox (Show entity selection boxes) bool false
 
+#    Distance in nodes at which transparency depth sorting is enabled
+#    Use this to limit the performance impact of transparency depth sorting
+transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128
+
 [*Menus]
 
 #    Use a cloud animation for the main menu background.
index 1a024e464b75698bf9999649112ce945ff2042f4..f070a58bb5bdf2295361b9084608273504776b99 100644 (file)
@@ -97,9 +97,32 @@ ClientMap::ClientMap(
        m_cache_trilinear_filter  = g_settings->getBool("trilinear_filter");
        m_cache_bilinear_filter   = g_settings->getBool("bilinear_filter");
        m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
+       m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
 
 }
 
+void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
+{
+       v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+       v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
+
+       m_camera_position = pos;
+       m_camera_direction = dir;
+       m_camera_fov = fov;
+       m_camera_offset = offset;
+
+       v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+       v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
+
+       // reorder the blocks when camera crosses block boundary
+       if (previous_block != current_block)
+               m_needs_update_drawlist = true;
+
+       // reorder transparent meshes when camera crosses node boundary
+       if (previous_node != current_node)
+               m_needs_update_transparent_meshes = true;
+}
+
 MapSector * ClientMap::emergeSector(v2s16 p2d)
 {
        // Check that it doesn't exist already
@@ -323,22 +346,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
        u32 mesh_animate_count = 0;
        //u32 mesh_animate_count_far = 0;
 
+       /*
+               Update transparent meshes
+       */
+       if (is_transparent_pass)
+               updateTransparentMeshBuffers();
+
        /*
                Draw the selected MapBlocks
        */
 
        MeshBufListList grouped_buffers;
-
-       struct DrawDescriptor {
-               v3s16 m_pos;
-               scene::IMeshBuffer *m_buffer;
-               bool m_reuse_material;
-
-               DrawDescriptor(const v3s16 &pos, scene::IMeshBuffer *buffer, bool reuse_material) :
-                       m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material)
-               {}
-       };
-
        std::vector<DrawDescriptor> draw_order;
        video::SMaterial previous_material;
 
@@ -375,7 +393,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                /*
                        Get the meshbuffers of the block
                */
-               {
+               if (is_transparent_pass) {
+                       // In transparent pass, the mesh will give us
+                       // the partial buffers in the correct order
+                       for (auto &buffer : block->mesh->getTransparentBuffers())
+                               draw_order.emplace_back(block_pos, &buffer);
+               }
+               else {
+                       // otherwise, group buffers across meshes
+                       // using MeshBufListList
                        MapBlockMesh *mapBlockMesh = block->mesh;
                        assert(mapBlockMesh);
 
@@ -389,35 +415,14 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
                                        video::SMaterial& material = buf->getMaterial();
                                        video::IMaterialRenderer* rnd =
-                                               driver->getMaterialRenderer(material.MaterialType);
+                                                       driver->getMaterialRenderer(material.MaterialType);
                                        bool transparent = (rnd && rnd->isTransparent());
-                                       if (transparent == is_transparent_pass) {
+                                       if (!transparent) {
                                                if (buf->getVertexCount() == 0)
                                                        errorstream << "Block [" << analyze_block(block)
-                                                               << "] contains an empty meshbuf" << std::endl;
-
-                                               material.setFlag(video::EMF_TRILINEAR_FILTER,
-                                                       m_cache_trilinear_filter);
-                                               material.setFlag(video::EMF_BILINEAR_FILTER,
-                                                       m_cache_bilinear_filter);
-                                               material.setFlag(video::EMF_ANISOTROPIC_FILTER,
-                                                       m_cache_anistropic_filter);
-                                               material.setFlag(video::EMF_WIREFRAME,
-                                                       m_control.show_wireframe);
-
-                                               if (is_transparent_pass) {
-                                                       // Same comparison as in MeshBufListList
-                                                       bool new_material = material.getTexture(0) != previous_material.getTexture(0) ||
-                                                                       material != previous_material;
-
-                                                       draw_order.emplace_back(block_pos, buf, !new_material);
-
-                                                       if (new_material)
-                                                               previous_material = material;
-                                               }
-                                               else {
-                                                       grouped_buffers.add(buf, block_pos, layer);
-                                               }
+                                                                       << "] contains an empty meshbuf" << std::endl;
+
+                                               grouped_buffers.add(buf, block_pos, layer);
                                        }
                                }
                        }
@@ -442,8 +447,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
        // Render all mesh buffers in order
        drawcall_count += draw_order.size();
+
        for (auto &descriptor : draw_order) {
-               scene::IMeshBuffer *buf = descriptor.m_buffer;
+               scene::IMeshBuffer *buf;
+               
+               if (descriptor.m_use_partial_buffer) {
+                       descriptor.m_partial_buffer->beforeDraw();
+                       buf = descriptor.m_partial_buffer->getBuffer();
+               }
+               else {
+                       buf = descriptor.m_buffer;
+               }
 
                // Check and abort if the machine is swapping a lot
                if (draw.getTimerTime() > 2000) {
@@ -454,6 +468,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
                if (!descriptor.m_reuse_material) {
                        auto &material = buf->getMaterial();
+
+                       // Apply filter settings
+                       material.setFlag(video::EMF_TRILINEAR_FILTER,
+                               m_cache_trilinear_filter);
+                       material.setFlag(video::EMF_BILINEAR_FILTER,
+                               m_cache_bilinear_filter);
+                       material.setFlag(video::EMF_ANISOTROPIC_FILTER,
+                               m_cache_anistropic_filter);
+                       material.setFlag(video::EMF_WIREFRAME,
+                               m_control.show_wireframe);
+
                        // pass the shadow map texture to the buffer texture
                        ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
                        if (shadow && shadow->is_active()) {
@@ -475,7 +500,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
                driver->setTransform(video::ETS_WORLD, m);
                driver->drawMeshBuffer(buf);
-               vertex_count += buf->getVertexCount();
+               vertex_count += buf->getIndexCount();
        }
 
        g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
@@ -698,7 +723,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
        u32 drawcall_count = 0;
        u32 vertex_count = 0;
 
-       MeshBufListList drawbufs;
+       MeshBufListList grouped_buffers;
+       std::vector<DrawDescriptor> draw_order;
+
 
        int count = 0;
        int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
@@ -727,7 +754,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
                /*
                        Get the meshbuffers of the block
                */
-               {
+               if (is_transparent_pass) {
+                       // In transparent pass, the mesh will give us
+                       // the partial buffers in the correct order
+                       for (auto &buffer : block->mesh->getTransparentBuffers())
+                               draw_order.emplace_back(block_pos, &buffer);
+               }
+               else {
+                       // otherwise, group buffers across meshes
+                       // using MeshBufListList
                        MapBlockMesh *mapBlockMesh = block->mesh;
                        assert(mapBlockMesh);
 
@@ -742,49 +777,74 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
                                        video::SMaterial &mat = buf->getMaterial();
                                        auto rnd = driver->getMaterialRenderer(mat.MaterialType);
                                        bool transparent = rnd && rnd->isTransparent();
-                                       if (transparent == is_transparent_pass)
-                                               drawbufs.add(buf, block_pos, layer);
+                                       if (!transparent)
+                                               grouped_buffers.add(buf, block_pos, layer);
                                }
                        }
                }
        }
 
+       u32 buffer_count = 0;
+       for (auto &lists : grouped_buffers.lists)
+               for (MeshBufList &list : lists)
+                       buffer_count += list.bufs.size();
+       
+       draw_order.reserve(draw_order.size() + buffer_count);
+       
+       // Capture draw order for all solid meshes
+       for (auto &lists : grouped_buffers.lists) {
+               for (MeshBufList &list : lists) {
+                       // iterate in reverse to draw closest blocks first
+                       for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
+                               draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+               }
+       }
+
        TimeTaker draw("Drawing shadow mesh buffers");
 
        core::matrix4 m; // Model matrix
        v3f offset = intToFloat(m_camera_offset, BS);
+       u32 material_swaps = 0;
 
-       // Render all layers in order
-       for (auto &lists : drawbufs.lists) {
-               for (MeshBufList &list : lists) {
-                       // Check and abort if the machine is swapping a lot
-                       if (draw.getTimerTime() > 1000) {
-                               infostream << "ClientMap::renderMapShadows(): Rendering "
-                                               "took >1s, returning." << std::endl;
-                               break;
-                       }
-                       for (auto &pair : list.bufs) {
-                               scene::IMeshBuffer *buf = pair.second;
-
-                               // override some material properties
-                               video::SMaterial local_material = buf->getMaterial();
-                               local_material.MaterialType = material.MaterialType;
-                               local_material.BackfaceCulling = material.BackfaceCulling;
-                               local_material.FrontfaceCulling = material.FrontfaceCulling;
-                               local_material.BlendOperation = material.BlendOperation;
-                               local_material.Lighting = false;
-                               driver->setMaterial(local_material);
-
-                               v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS);
-                               m.setTranslation(block_wpos - offset);
-
-                               driver->setTransform(video::ETS_WORLD, m);
-                               driver->drawMeshBuffer(buf);
-                               vertex_count += buf->getVertexCount();
-                       }
+       // Render all mesh buffers in order
+       drawcall_count += draw_order.size();
+
+       for (auto &descriptor : draw_order) {
+               scene::IMeshBuffer *buf;
+               
+               if (descriptor.m_use_partial_buffer) {
+                       descriptor.m_partial_buffer->beforeDraw();
+                       buf = descriptor.m_partial_buffer->getBuffer();
+               }
+               else {
+                       buf = descriptor.m_buffer;
+               }
 
-                       drawcall_count += list.bufs.size();
+               // Check and abort if the machine is swapping a lot
+               if (draw.getTimerTime() > 1000) {
+                       infostream << "ClientMap::renderMapShadows(): Rendering "
+                                       "took >1s, returning." << std::endl;
+                       break;
                }
+
+               if (!descriptor.m_reuse_material) {
+                       // override some material properties
+                       video::SMaterial local_material = buf->getMaterial();
+                       local_material.MaterialType = material.MaterialType;
+                       local_material.BackfaceCulling = material.BackfaceCulling;
+                       local_material.FrontfaceCulling = material.FrontfaceCulling;
+                       local_material.BlendOperation = material.BlendOperation;
+                       local_material.Lighting = false;
+                       driver->setMaterial(local_material);
+                       ++material_swaps;
+               }
+
+               v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+               m.setTranslation(block_wpos - offset);
+
+               driver->setTransform(video::ETS_WORLD, m);
+               driver->drawMeshBuffer(buf);
+               vertex_count += buf->getIndexCount();
        }
 
        // restore the driver material state 
@@ -796,6 +856,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
        g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
        g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
        g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+       g_profiler->avg(prefix + "material swaps [#]", material_swaps);
 }
 
 /*
@@ -891,3 +952,40 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
        g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
        g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
 }
+
+void ClientMap::updateTransparentMeshBuffers()
+{
+       ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
+       u32 sorted_blocks = 0;
+       u32 unsorted_blocks = 0;
+       f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
+
+
+       // Update the order of transparent mesh buffers in each mesh
+       for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
+               MapBlock* block = it->second;
+               if (!block->mesh)
+                       continue;
+               
+               if (m_needs_update_transparent_meshes || 
+                               block->mesh->getTransparentBuffers().size() == 0) {
+
+                       v3s16 block_pos = block->getPos();
+                       v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
+                       f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
+                       if (distance <= sorting_distance_sq) {
+                               block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
+                               ++sorted_blocks;
+                       }
+                       else {
+                               block->mesh->consolidateTransparentBuffers();
+                               ++unsorted_blocks;
+                       }
+               }
+       }
+
+       g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
+       g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
+       m_needs_update_transparent_meshes = false;
+}
+
index b4dc423950c697e628c837060e2e03abf4e5d35b..d8554313d1cddf6fb56361c344a00165713a5287 100644 (file)
@@ -56,6 +56,7 @@ struct MeshBufListList
 
 class Client;
 class ITextureSource;
+class PartialMeshBuffer;
 
 /*
        ClientMap
@@ -85,21 +86,7 @@ class ClientMap : public Map, public scene::ISceneNode
                ISceneNode::drop();
        }
 
-       void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset)
-       {
-               v3s16 previous_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE);
-
-               m_camera_position = pos;
-               m_camera_direction = dir;
-               m_camera_fov = fov;
-               m_camera_offset = offset;
-
-               v3s16 current_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE);
-
-               // reorder the blocks when camera crosses block boundary
-               if (previous_block != current_block)
-                       m_needs_update_drawlist = true;
-       }
+       void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset);
 
        /*
                Forcefully get a sector from somewhere
@@ -150,6 +137,10 @@ class ClientMap : public Map, public scene::ISceneNode
        f32 getCameraFov() const { return m_camera_fov; }
 
 private:
+
+       // update the vertex order in transparent mesh buffers
+       void updateTransparentMeshBuffers();
+
        // Orders blocks by distance to the camera
        class MapBlockComparer
        {
@@ -167,6 +158,26 @@ class ClientMap : public Map, public scene::ISceneNode
                v3s16 m_camera_block;
        };
 
+
+       // reference to a mesh buffer used when rendering the map.
+       struct DrawDescriptor {
+               v3s16 m_pos;
+               union {
+                       scene::IMeshBuffer *m_buffer;
+                       const PartialMeshBuffer *m_partial_buffer;
+               };
+               bool m_reuse_material:1;
+               bool m_use_partial_buffer:1;
+
+               DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) :
+                       m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false)
+               {}
+
+               DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
+                       m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
+               {}
+       };
+
        Client *m_client;
        RenderingEngine *m_rendering_engine;
 
@@ -179,6 +190,7 @@ class ClientMap : public Map, public scene::ISceneNode
        v3f m_camera_direction = v3f(0,0,1);
        f32 m_camera_fov = M_PI;
        v3s16 m_camera_offset;
+       bool m_needs_update_transparent_meshes = true;
 
        std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist;
        std::map<v3s16, MapBlock*> m_drawlist_shadow;
@@ -190,4 +202,5 @@ class ClientMap : public Map, public scene::ISceneNode
        bool m_cache_bilinear_filter;
        bool m_cache_anistropic_filter;
        bool m_added_to_shadow_renderer{false};
+       u16 m_cache_transparency_sorting_distance;
 };
index bb2d6398f1015c855a19d07dc3f495fbcaeaafa2..947793ed04914dbf4fd89c613ea7bceaa5430530 100644 (file)
@@ -381,12 +381,12 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
                box.MinEdge *= f->visual_scale;
                box.MaxEdge *= f->visual_scale;
        }
-       box.MinEdge += origin;
-       box.MaxEdge += origin;
        if (!txc) {
                generateCuboidTextureCoords(box, texture_coord_buf);
                txc = texture_coord_buf;
        }
+       box.MinEdge += origin;
+       box.MaxEdge += origin;
        if (!tiles) {
                tiles = &tile;
                tile_count = 1;
@@ -1377,6 +1377,59 @@ void MapblockMeshGenerator::drawNodeboxNode()
 
        std::vector<aabb3f> boxes;
        n.getNodeBoxes(nodedef, &boxes, neighbors_set);
+
+       bool isTransparent = false;
+
+       for (const TileSpec &tile : tiles) {
+               if (tile.layers[0].isTransparent()) {
+                       isTransparent = true;
+                       break;
+               }
+       }
+
+       if (isTransparent) {
+               std::vector<float> sections;
+               // Preallocate 8 default splits + Min&Max for each nodebox
+               sections.reserve(8 + 2 * boxes.size());
+
+               for (int axis = 0; axis < 3; axis++) {
+                       // identify sections
+
+                       if (axis == 0) {
+                               // Default split at node bounds, up to 3 nodes in each direction
+                               for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
+                                       sections.push_back(s);
+                       }
+                       else {
+                               // Avoid readding the same 8 default splits for Y and Z
+                               sections.resize(8);
+                       }
+
+                       // Add edges of existing node boxes, rounded to 1E-3
+                       for (size_t i = 0; i < boxes.size(); i++) {
+                               sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
+                               sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
+                       }
+
+                       // split the boxes at recorded sections
+                       // limit splits to avoid runaway crash if inner loop adds infinite splits
+                       // due to e.g. precision problems.
+                       // 100 is just an arbitrary, reasonably high number.
+                       for (size_t i = 0; i < boxes.size() && i < 100; i++) {
+                               aabb3f *box = &boxes[i];
+                               for (float section : sections) {
+                                       if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
+                                               aabb3f copy(*box);
+                                               copy.MinEdge[axis] = section;
+                                               box->MaxEdge[axis] = section;
+                                               boxes.push_back(copy);
+                                               box = &boxes[i]; // find new address of the box in case of reallocation
+                                       }
+                               }
+                       }
+               }
+       }
+
        for (auto &box : boxes)
                drawAutoLightedCuboid(box, nullptr, tiles, 6);
 }
index 03522eca9326db57e6a91f5c3f512b3ec2a5eb6c..8c7d661863f496eb9bf8be38e51afaa35fd9abdf 100644 (file)
@@ -30,6 +30,7 @@ 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
@@ -1003,6 +1004,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
 */
@@ -1173,8 +1341,31 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
 
                        scene::SMeshBuffer *buf = new scene::SMeshBuffer();
                        buf->Material = material;
-                       buf->append(&p.vertices[0], p.vertices.size(),
-                               &p.indices[0], p.indices.size());
+                       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());
+                               break;
+                       }
                        mesh->addMeshBuffer(buf);
                        buf->drop();
                }
@@ -1187,6 +1378,7 @@ 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 =
@@ -1298,6 +1490,67 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
        return true;
 }
 
+void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
+{
+       // 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;
+               }
+               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);
+       }
+}
+
 video::SColor encode_light(u16 light, u8 emissive_light)
 {
        // Get components
index 3b17c4af9827c4539afb0530cdd0186a7b8433ab..cfc39ade229d0161a6254bd5b0bcfb6fe772550a 100644 (file)
@@ -71,6 +71,91 @@ struct MeshMakeData
        void setSmoothLighting(bool smooth_lighting);
 };
 
+// represents a triangle as indexes into the vertex buffer in SMeshBuffer
+class MeshTriangle
+{
+public:
+       scene::SMeshBuffer *buffer;
+       u16 p1, p2, p3;
+       v3f centroid;
+       float areaSQ;
+
+       void updateAttributes()
+       {
+               v3f v1 = buffer->getPosition(p1);
+               v3f v2 = buffer->getPosition(p2);
+               v3f v3 = buffer->getPosition(p3);
+
+               centroid = (v1 + v2 + v3) / 3;
+               areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4;
+       }
+
+       v3f getNormal() const {
+               v3f v1 = buffer->getPosition(p1);
+               v3f v2 = buffer->getPosition(p2);
+               v3f v3 = buffer->getPosition(p3);
+
+               return (v2-v1).crossProduct(v3-v1);
+       }
+};
+
+/**
+ * Implements a binary space partitioning tree 
+ * See also: https://en.wikipedia.org/wiki/Binary_space_partitioning
+ */
+class MapBlockBspTree
+{
+public:
+       MapBlockBspTree() {}
+
+       void buildTree(const std::vector<MeshTriangle> *triangles);
+
+       void traverse(v3f viewpoint, std::vector<s32> &output) const
+       {
+               traverse(root, viewpoint, output);
+       }
+
+private:
+       // Tree node definition;
+       struct TreeNode
+       {
+               v3f normal;
+               v3f origin;
+               std::vector<s32> triangle_refs;
+               s32 front_ref;
+               s32 back_ref;
+
+               TreeNode() = default;
+               TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) :
+                               normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref)
+               {}
+       };
+
+
+       s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth);
+       void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const;
+
+       const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally
+       std::vector<TreeNode> nodes; // list of nodes
+       s32 root = -1; // index of the root node
+};
+
+class PartialMeshBuffer
+{
+public:
+       PartialMeshBuffer(scene::SMeshBuffer *buffer, const std::vector<u16> &vertex_indexes) :
+                       m_buffer(buffer), m_vertex_indexes(vertex_indexes)
+       {}
+
+       scene::IMeshBuffer *getBuffer() const { return m_buffer; }
+       const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
+
+       void beforeDraw() const;
+private:
+       scene::SMeshBuffer *m_buffer;
+       std::vector<u16> m_vertex_indexes;
+};
+
 /*
        Holds a mesh for a mapblock.
 
@@ -125,6 +210,15 @@ class MapBlockMesh
                        m_animation_force_timer--;
        }
 
+       /// update transparent buffers to render towards the camera
+       void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
+       void consolidateTransparentBuffers();
+
+       /// get the list of transparent buffers
+       const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
+       {
+               return this->m_transparent_buffers;
+       }
 private:
        scene::IMesh *m_mesh[MAX_TILE_LAYERS];
        MinimapMapblock *m_minimap_mapblock;
@@ -158,6 +252,13 @@ class MapBlockMesh
        // of sunlit vertices
        // Keys are pairs of (mesh index, buffer index in the mesh)
        std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
+
+       // list of all semitransparent triangles in the mapblock
+       std::vector<MeshTriangle> m_transparent_triangles;
+       // Binary Space Partitioning tree for the block
+       MapBlockBspTree m_bsp_tree;
+       // Ordered list of references to parts of transparent buffers to draw
+       std::vector<PartialMeshBuffer> m_transparent_buffers;
 };
 
 /*!
index fe96cef584287572165d14197e699d9a3a6a412d..88ff91f8edeea44b9b41bd10b03005c7f17174f4 100644 (file)
@@ -260,6 +260,18 @@ struct TileLayer
                        && (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL);
        }
 
+       bool isTransparent() const
+       {
+               switch (material_type) {
+               case TILE_MATERIAL_BASIC:
+               case TILE_MATERIAL_ALPHA:
+               case TILE_MATERIAL_LIQUID_TRANSPARENT:
+               case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
+                       return true;
+               }
+               return false;
+       }
+
        // Ordered for size, please do not reorder
 
        video::ITexture *texture = nullptr;
@@ -308,7 +320,8 @@ struct TileSpec
                for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
                        if (layers[layer] != other.layers[layer])
                                return false;
-                       if (!layers[layer].isTileable())
+                       // Only non-transparent tiles can be merged into fast faces
+                       if (layers[layer].isTransparent() || !layers[layer].isTileable())
                                return false;
                }
                return rotation == 0
index 2e9a191994c578a6ba53a5cc29a7147e192ae16e..b86287157b2ff2ddca34a321e994bc0e16524349 100644 (file)
@@ -244,6 +244,7 @@ void set_default_settings()
        settings->setDefault("enable_particles", "true");
        settings->setDefault("arm_inertia", "true");
        settings->setDefault("show_nametag_backgrounds", "true");
+       settings->setDefault("transparency_sorting_distance", "16");
 
        settings->setDefault("enable_minimap", "true");
        settings->setDefault("minimap_shape_round", "true");