]> git.lizzy.rs Git - minetest.git/blobdiff - src/content_mapblock.cpp
Fix node-nodebox lighting difference in direct sunlight (#7061)
[minetest.git] / src / content_mapblock.cpp
index 6673e2bd9f9ca96309889fbd67091d8355b9cba1..348748c911882d4690e42cbe04bfc0cc6b16b788 100644 (file)
@@ -26,10 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "client/tile.h"
 #include "mesh.h"
 #include <IMeshManipulator.h>
+#include "client/renderingengine.h"
 #include "client.h"
-#include "log.h"
 #include "noise.h"
-#include "util/cpp11.h"
 
 // Distance of light extrapolation (for oversized nodes)
 // After this distance, it gives up and considers light level constant
@@ -43,7 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 // Corresponding offsets are listed in g_27dirs
 #define FRAMED_NEIGHBOR_COUNT 18
 
-static constexpr v3s16 light_dirs[8] = {
+static const v3s16 light_dirs[8] = {
        v3s16(-1, -1, -1),
        v3s16(-1, -1,  1),
        v3s16(-1,  1, -1),
@@ -65,8 +64,7 @@ MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector
        collector = output;
 
        nodedef   = data->m_client->ndef();
-       smgr      = data->m_client->getSceneManager();
-       meshmanip = smgr->getMeshManipulator();
+       meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator();
 
        enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
                !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
@@ -74,33 +72,57 @@ MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector
        blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
 }
 
-void MapblockMeshGenerator::useTile(int index, bool disable_backface_culling)
+void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
 {
-       tile = getNodeTileN(n, p, index, data);
+       if (special)
+               getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
+       else
+               getTile(index, &tile);
        if (!data->m_smooth_lighting)
                color = encode_light(light, f->light_source);
-       for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
-               tile.layers[layer].material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
-               if (disable_backface_culling)
-                       tile.layers[layer].material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
+
+       for (auto &layer : tile.layers) {
+               layer.material_flags |= set_flags;
+               layer.material_flags &= ~reset_flags;
        }
 }
 
-void MapblockMeshGenerator::useDefaultTile(bool set_color)
+// Returns a tile, ready for use, non-rotated.
+void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
 {
-       tile = getNodeTile(n, p, v3s16(0, 0, 0), data);
-       if (set_color && !data->m_smooth_lighting)
-               color = encode_light(light, f->light_source);
+       getNodeTileN(n, p, index, data, *tile);
 }
 
-TileSpec MapblockMeshGenerator::getTile(const v3s16& direction)
+// Returns a tile, ready for use, rotated according to the node facedir.
+void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
 {
-       return getNodeTile(n, p, direction, data);
+       getNodeTile(n, p, direction, data, *tile);
 }
 
-void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal)
+// Returns a special tile, ready for use, non-rotated.
+void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
 {
-       static const v2f tcoords[4] = {v2f(0, 0), v2f(1, 0), v2f(1, 1), v2f(0, 1)};
+       *tile = f->special_tiles[index];
+       TileLayer *top_layer = nullptr;
+
+       for (auto &layernum : tile->layers) {
+               TileLayer *layer = &layernum;
+               if (layer->texture_id == 0)
+                       continue;
+               top_layer = layer;
+               if (!layer->has_color)
+                       n.getColor(*f, &layer->color);
+       }
+
+       if (apply_crack)
+               top_layer->material_flags |= MATERIAL_FLAG_CRACK;
+}
+
+void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
+       float vertical_tiling)
+{
+       const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
+               v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
        video::S3DVertex vertices[4];
        bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
        v3f normal2(normal.X, normal.Y, normal.Z);
@@ -129,7 +151,7 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal)
 //              the faces in the list is up-down-right-left-back-front
 //              (compatible with ContentFeatures).
 void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
-       TileSpec *tiles, int tilecount, const u16 *lights, const f32 *txc)
+       TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
 {
        assert(tilecount >= 1 && tilecount <= 6); // pre-condition
 
@@ -241,10 +263,12 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
 
        if (data->m_smooth_lighting) {
                for (int j = 0; j < 24; ++j) {
-                       vertices[j].Color = encode_light(lights[light_indices[j]],
+                       video::S3DVertex &vertex = vertices[j];
+                       vertex.Color = encode_light(
+                               lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
                                f->light_source);
                        if (!f->light_source)
-                               applyFacesShading(vertices[j].Color, vertices[j].Normal);
+                               applyFacesShading(vertex.Color, vertex.Normal);
                }
        }
 
@@ -258,32 +282,45 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
 // Gets the base lighting values for a node
 void MapblockMeshGenerator::getSmoothLightFrame()
 {
+       for (int k = 0; k < 8; ++k)
+               frame.sunlight[k] = false;
        for (int k = 0; k < 8; ++k) {
-               u16 light = getSmoothLight(blockpos_nodes + p, light_dirs[k], data);
-               frame.lightsA[k] = light & 0xff;
-               frame.lightsB[k] = light >> 8;
+               LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
+               frame.lightsDay[k] = light.lightDay;
+               frame.lightsNight[k] = light.lightNight;
+               // If there is direct sunlight and no ambient occlusion at some corner,
+               // mark the vertical edge (top and bottom corners) containing it.
+               if (light.lightDay == 255) {
+                       frame.sunlight[k] = true;
+                       frame.sunlight[k ^ 2] = true;
+               }
        }
 }
 
 // Calculates vertex light level
 //  vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
-u16 MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
+LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
 {
+       // Light levels at (logical) node corners are known. Here,
+       // trilinear interpolation is used to calculate light level
+       // at a given point in the node.
        f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
        f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
        f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
-       f32 lightA = 0.0;
-       f32 lightB = 0.0;
+       f32 lightDay = 0.0; // daylight
+       f32 lightNight = 0.0;
+       f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
        for (int k = 0; k < 8; ++k) {
                f32 dx = (k & 4) ? x : 1 - x;
                f32 dy = (k & 2) ? y : 1 - y;
                f32 dz = (k & 1) ? z : 1 - z;
-               lightA += dx * dy * dz * frame.lightsA[k];
-               lightB += dx * dy * dz * frame.lightsB[k];
+               // Use direct sunlight (255), if any; use daylight otherwise.
+               f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
+               lightDay += dx * dy * dz * frame.lightsDay[k];
+               lightNight += dx * dy * dz * frame.lightsNight[k];
+               lightBoosted += dx * dy * dz * light_boosted;
        }
-       return
-               core::clamp(core::round32(lightA), 0, 255) |
-               core::clamp(core::round32(lightB), 0, 255) << 8;
+       return LightInfo{lightDay, lightNight, lightBoosted};
 }
 
 // Calculates vertex color to be used in mapblock mesh
@@ -291,14 +328,15 @@ u16 MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
 //  tile_color - node's tile color
 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
 {
-       u16 light = blendLight(vertex_pos);
-       return encode_light(light, f->light_source);
+       LightInfo light = blendLight(vertex_pos);
+       return encode_light(light.getPair(), f->light_source);
 }
 
 video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
        const v3f &vertex_normal)
 {
-       video::SColor color = blendLightColor(vertex_pos);
+       LightInfo light = blendLight(vertex_pos);
+       video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
        if (!f->light_source)
                applyFacesShading(color, vertex_normal);
        return color;
@@ -345,7 +383,7 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
                tile_count = 1;
        }
        if (data->m_smooth_lighting) {
-               u16 lights[8];
+               LightInfo lights[8];
                for (int j = 0; j < 8; ++j) {
                        v3f d;
                        d.X = (j & 4) ? dx2 : dx1;
@@ -355,36 +393,26 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
                }
                drawCuboid(box, tiles, tile_count, lights, txc);
        } else {
-               drawCuboid(box, tiles, tile_count, NULL, txc);
-       }
-}
-
-/*!
- * Returns the i-th special tile for a map node.
- */
-static TileSpec getSpecialTile(const ContentFeatures &f,
-       const MapNode &n, u8 i)
-{
-       TileSpec copy = f.special_tiles[i];
-       for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) {
-               TileLayer *layer = &copy.layers[layernum];
-               if (layer->texture_id == 0)
-                       continue;
-               if (!layer->has_color)
-                       n.getColor(f, &(layer->color));
+               drawCuboid(box, tiles, tile_count, nullptr, txc);
        }
-       return copy;
 }
 
-void MapblockMeshGenerator::prepareLiquidNodeDrawing(bool flowing)
+void MapblockMeshGenerator::prepareLiquidNodeDrawing()
 {
-       tile_liquid_top = getSpecialTile(*f, n, 0);
-       tile_liquid = getSpecialTile(*f, n, flowing ? 1 : 0);
+       getSpecialTile(0, &tile_liquid_top);
+       getSpecialTile(1, &tile_liquid);
 
        MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
+       MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
        c_flowing = nodedef->getId(f->liquid_alternative_flowing);
        c_source = nodedef->getId(f->liquid_alternative_source);
        top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
+       draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
+       if (draw_liquid_bottom) {
+               const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
+               if (f2.solidness > 1)
+                       draw_liquid_bottom = false;
+       }
 
        if (data->m_smooth_lighting)
                return; // don't need to pre-compute anything in this case
@@ -392,27 +420,23 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing(bool flowing)
        if (f->light_source != 0) {
                // If this liquid emits light and doesn't contain light, draw
                // it at what it emits, for an increased effect
-               light = decode_light(f->light_source);
-               light = light | (light << 8);
+               u8 e = decode_light(f->light_source);
+               light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
        } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
                // Otherwise, use the light of the node on top if possible
-               light = getInteriorLight(ntop, 0, nodedef);
+               light = LightPair(getInteriorLight(ntop, 0, nodedef));
        }
 
        color_liquid_top = encode_light(light, f->light_source);
        color = encode_light(light, f->light_source);
 }
 
-void MapblockMeshGenerator::getLiquidNeighborhood(bool flowing)
+void MapblockMeshGenerator::getLiquidNeighborhood()
 {
        u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
 
        for (int w = -1; w <= 1; w++)
        for (int u = -1; u <= 1; u++) {
-               // Skip getting unneeded data
-               if (!flowing && u && w)
-                       continue;
-
                NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
                v3s16 p2 = p + v3s16(u, 0, w);
                MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
@@ -447,13 +471,6 @@ void MapblockMeshGenerator::getLiquidNeighborhood(bool flowing)
        }
 }
 
-void MapblockMeshGenerator::resetCornerLevels()
-{
-       for (int k = 0; k < 2; k++)
-       for (int i = 0; i < 2; i++)
-               corner_levels[k][i] = 0.5 * BS;
-}
-
 void MapblockMeshGenerator::calculateCornerLevels()
 {
        for (int k = 0; k < 2; k++)
@@ -494,7 +511,7 @@ f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
        return 0;
 }
 
-void MapblockMeshGenerator::drawLiquidSides(bool flowing)
+void MapblockMeshGenerator::drawLiquidSides()
 {
        struct LiquidFaceDesc {
                v3s16 dir; // XZ
@@ -515,25 +532,20 @@ void MapblockMeshGenerator::drawLiquidSides(bool flowing)
                {1, 0},
                {0, 0}
        };
-       for (int i = 0; i < 4; i++) {
-               const LiquidFaceDesc &face = base_faces[i];
+
+       for (const auto &face : base_faces) {
                const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
 
                // No face between nodes of the same liquid, unless there is node
                // at the top to which it should be connected. Again, unless the face
                // there would be inside the liquid
                if (neighbor.is_same_liquid) {
-                       if (!flowing)
-                               continue;
                        if (!top_is_same_liquid)
                                continue;
                        if (neighbor.top_is_same_liquid)
                                continue;
                }
 
-               if (!flowing && (neighbor.content == CONTENT_IGNORE))
-                       continue;
-
                const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
                // Don't draw face if neighbor is blocking the view
                if (neighbor_features.solidness == 2)
@@ -559,7 +571,7 @@ void MapblockMeshGenerator::drawLiquidSides(bool flowing)
        }
 }
 
-void MapblockMeshGenerator::drawLiquidTop(bool flowing)
+void MapblockMeshGenerator::drawLiquidTop()
 {
        // To get backface culling right, the vertices need to go
        // clockwise around the front of the face. And we happened to
@@ -582,50 +594,65 @@ void MapblockMeshGenerator::drawLiquidTop(bool flowing)
                vertices[i].Pos += origin;
        }
 
-       if (flowing) {
-               // Default downwards-flowing texture animation goes from
-               // -Z towards +Z, thus the direction is +Z.
-               // Rotate texture to make animation go in flow direction
-               // Positive if liquid moves towards +Z
-               f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
-                        (corner_levels[1][0] + corner_levels[1][1]);
-               // Positive if liquid moves towards +X
-               f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
-                        (corner_levels[0][1] + corner_levels[1][1]);
-               f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
-               v2f tcoord_center(0.5, 0.5);
-               v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
-               tcoord_translate.rotateBy(tcoord_angle);
-               tcoord_translate.X -= floor(tcoord_translate.X);
-               tcoord_translate.Y -= floor(tcoord_translate.Y);
-
-               for (int i = 0; i < 4; i++) {
-                       vertices[i].TCoords.rotateBy(tcoord_angle, tcoord_center);
-                       vertices[i].TCoords += tcoord_translate;
-               }
+       // Default downwards-flowing texture animation goes from
+       // -Z towards +Z, thus the direction is +Z.
+       // Rotate texture to make animation go in flow direction
+       // Positive if liquid moves towards +Z
+       f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
+                (corner_levels[1][0] + corner_levels[1][1]);
+       // Positive if liquid moves towards +X
+       f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
+                (corner_levels[0][1] + corner_levels[1][1]);
+       f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
+       v2f tcoord_center(0.5, 0.5);
+       v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
+       tcoord_translate.rotateBy(tcoord_angle);
+       tcoord_translate.X -= floor(tcoord_translate.X);
+       tcoord_translate.Y -= floor(tcoord_translate.Y);
+
+       for (video::S3DVertex &vertex : vertices) {
+               vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
+               vertex.TCoords += tcoord_translate;
+       }
+
+       std::swap(vertices[0].TCoords, vertices[2].TCoords);
+
+       collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
+}
 
-               std::swap(vertices[0].TCoords, vertices[2].TCoords);
+void MapblockMeshGenerator::drawLiquidBottom()
+{
+       video::S3DVertex vertices[4] = {
+               video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
+               video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
+               video::S3DVertex( BS / 2, -BS / 2,  BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
+               video::S3DVertex(-BS / 2, -BS / 2,  BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
+       };
+
+       for (int i = 0; i < 4; i++) {
+               if (data->m_smooth_lighting)
+                       vertices[i].Color = blendLightColor(vertices[i].Pos);
+               vertices[i].Pos += origin;
        }
 
        collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
 }
 
-void MapblockMeshGenerator::drawLiquidNode(bool flowing)
+void MapblockMeshGenerator::drawLiquidNode()
 {
-       prepareLiquidNodeDrawing(flowing);
-       getLiquidNeighborhood(flowing);
-       if (flowing)
-               calculateCornerLevels();
-       else
-               resetCornerLevels();
-       drawLiquidSides(flowing);
+       prepareLiquidNodeDrawing();
+       getLiquidNeighborhood();
+       calculateCornerLevels();
+       drawLiquidSides();
        if (!top_is_same_liquid)
-               drawLiquidTop(flowing);
+               drawLiquidTop();
+       if (draw_liquid_bottom)
+               drawLiquidBottom();
 }
 
 void MapblockMeshGenerator::drawGlasslikeNode()
 {
-       useDefaultTile();
+       useTile(0, 0, 0);
 
        for (int face = 0; face < 6; face++) {
                // Check this neighbor
@@ -642,14 +669,21 @@ void MapblockMeshGenerator::drawGlasslikeNode()
                        v3f( BS / 2, -BS / 2, -BS / 2),
                        v3f(-BS / 2, -BS / 2, -BS / 2),
                };
-               for (int i = 0; i < 4; i++) {
+
+               for (v3f &vertex : vertices) {
                        switch (face) {
-                               case D6D_ZP: vertices[i].rotateXZBy(180); break;
-                               case D6D_YP: vertices[i].rotateYZBy( 90); break;
-                               case D6D_XP: vertices[i].rotateXZBy( 90); break;
-                               case D6D_ZN: vertices[i].rotateXZBy(  0); break;
-                               case D6D_YN: vertices[i].rotateYZBy(-90); break;
-                               case D6D_XN: vertices[i].rotateXZBy(-90); break;
+                               case D6D_ZP:
+                                       vertex.rotateXZBy(180); break;
+                               case D6D_YP:
+                                       vertex.rotateYZBy( 90); break;
+                               case D6D_XP:
+                                       vertex.rotateXZBy( 90); break;
+                               case D6D_ZN:
+                                       vertex.rotateXZBy(  0); break;
+                               case D6D_YN:
+                                       vertex.rotateYZBy(-90); break;
+                               case D6D_XN:
+                                       vertex.rotateXZBy(-90); break;
                        }
                }
                drawQuad(vertices, dir);
@@ -660,7 +694,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
 {
        TileSpec tiles[6];
        for (int face = 0; face < 6; face++)
-               tiles[face] = getTile(g_6dirs[face]);
+               getTile(g_6dirs[face], &tiles[face]);
 
        TileSpec glass_tiles[6];
        if (tiles[1].layers[0].texture &&
@@ -673,8 +707,8 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
                glass_tiles[4] = tiles[3];
                glass_tiles[5] = tiles[4];
        } else {
-               for (int face = 0; face < 6; face++)
-                       glass_tiles[face] = tiles[4];
+               for (auto &glass_tile : glass_tiles)
+                       glass_tile = tiles[4];
        }
 
        u8 param2 = n.getParam2();
@@ -734,7 +768,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
                        v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
                        MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
                        content_t n2c = n2.getContent();
-                       if (n2c == current || n2c == CONTENT_IGNORE)
+                       if (n2c == current)
                                nb[i] = 1;
                }
        }
@@ -773,7 +807,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
                // Internal liquid level has param2 range 0 .. 63,
                // convert it to -0.5 .. 0.5
                float vlev = (param2 / 63.0) * 2.0 - 1.0;
-               tile = getSpecialTile(*f, n, 0);
+               getSpecialTile(0, &tile);
                drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
                                             -(nb[4] ? g : b),
                                             -(nb[3] ? g : b),
@@ -786,7 +820,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
 void MapblockMeshGenerator::drawAllfacesNode()
 {
        static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
-       useDefaultTile(false);
+       useTile(0, 0, 0);
        drawAutoLightedCuboid(box);
 }
 
@@ -799,7 +833,7 @@ void MapblockMeshGenerator::drawTorchlikeNode()
                case DWM_YN: tileindex = 0; break; // floor
                default:     tileindex = 2; // side (or invalid—should we care?)
        }
-       useTile(tileindex, true);
+       useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
 
        float size = BS / 2 * f->visual_scale;
        v3f vertices[4] = {
@@ -808,14 +842,21 @@ void MapblockMeshGenerator::drawTorchlikeNode()
                v3f( size, -size, 0),
                v3f(-size, -size, 0),
        };
-       for (int i = 0; i < 4; i++) {
+
+       for (v3f &vertex : vertices) {
                switch (wall) {
-                       case DWM_YP: vertices[i].rotateXZBy(-45); break;
-                       case DWM_YN: vertices[i].rotateXZBy( 45); break;
-                       case DWM_XP: vertices[i].rotateXZBy(  0); break;
-                       case DWM_XN: vertices[i].rotateXZBy(180); break;
-                       case DWM_ZP: vertices[i].rotateXZBy( 90); break;
-                       case DWM_ZN: vertices[i].rotateXZBy(-90); break;
+                       case DWM_YP:
+                               vertex.rotateXZBy(-45); break;
+                       case DWM_YN:
+                               vertex.rotateXZBy( 45); break;
+                       case DWM_XP:
+                               vertex.rotateXZBy(  0); break;
+                       case DWM_XN:
+                               vertex.rotateXZBy(180); break;
+                       case DWM_ZP:
+                               vertex.rotateXZBy( 90); break;
+                       case DWM_ZN:
+                               vertex.rotateXZBy(-90); break;
                }
        }
        drawQuad(vertices);
@@ -824,7 +865,7 @@ void MapblockMeshGenerator::drawTorchlikeNode()
 void MapblockMeshGenerator::drawSignlikeNode()
 {
        u8 wall = n.getWallMounted(nodedef);
-       useTile(0, true);
+       useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
        static const float offset = BS / 16;
        float size = BS / 2 * f->visual_scale;
        // Wall at X+ of node
@@ -834,14 +875,21 @@ void MapblockMeshGenerator::drawSignlikeNode()
                v3f(BS / 2 - offset, -size, -size),
                v3f(BS / 2 - offset, -size,  size),
        };
-       for (int i = 0; i < 4; i++) {
+
+       for (v3f &vertex : vertices) {
                switch (wall) {
-                       case DWM_YP: vertices[i].rotateXYBy( 90); break;
-                       case DWM_YN: vertices[i].rotateXYBy(-90); break;
-                       case DWM_XP: vertices[i].rotateXZBy(  0); break;
-                       case DWM_XN: vertices[i].rotateXZBy(180); break;
-                       case DWM_ZP: vertices[i].rotateXZBy( 90); break;
-                       case DWM_ZN: vertices[i].rotateXZBy(-90); break;
+                       case DWM_YP:
+                               vertex.rotateXYBy( 90); break;
+                       case DWM_YN:
+                               vertex.rotateXYBy(-90); break;
+                       case DWM_XP:
+                               vertex.rotateXZBy(  0); break;
+                       case DWM_XN:
+                               vertex.rotateXZBy(180); break;
+                       case DWM_ZP:
+                               vertex.rotateXZBy( 90); break;
+                       case DWM_ZN:
+                               vertex.rotateXZBy(-90); break;
                }
        }
        drawQuad(vertices);
@@ -851,34 +899,35 @@ void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
        bool offset_top_only)
 {
        v3f vertices[4] = {
-               v3f(-scale, -BS / 2 + scale * 2, 0),
-               v3f( scale, -BS / 2 + scale * 2, 0),
+               v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
+               v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
                v3f( scale, -BS / 2, 0),
                v3f(-scale, -BS / 2, 0),
        };
        if (random_offset_Y) {
                PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
-               offset.Y = BS * ((yrng.next() % 16 / 16.0) * 0.125);
+               offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
        }
        int offset_count = offset_top_only ? 2 : 4;
        for (int i = 0; i < offset_count; i++)
                vertices[i].Z += quad_offset;
-       for (int i = 0; i < 4; i++) {
-               vertices[i].rotateXZBy(rotation + rotate_degree);
-               vertices[i] += offset;
+
+       for (v3f &vertex : vertices) {
+               vertex.rotateXZBy(rotation + rotate_degree);
+               vertex += offset;
        }
-       drawQuad(vertices);
+       drawQuad(vertices, v3s16(0, 0, 0), plant_height);
 }
 
-void MapblockMeshGenerator::drawPlantlikeNode()
+void MapblockMeshGenerator::drawPlantlike()
 {
-       useTile(0, false);
        draw_style = PLANT_STYLE_CROSS;
        scale = BS / 2 * f->visual_scale;
        offset = v3f(0, 0, 0);
        rotate_degree = 0;
        random_offset_Y = false;
        face_num = 0;
+       plant_height = 1.0;
 
        switch (f->param_type_2) {
        case CPT2_MESHOPTIONS:
@@ -898,6 +947,10 @@ void MapblockMeshGenerator::drawPlantlikeNode()
                rotate_degree = n.param2 * 2;
                break;
 
+       case CPT2_LEVELED:
+               plant_height = n.param2 / 16.0;
+               break;
+
        default:
                break;
        }
@@ -935,6 +988,27 @@ void MapblockMeshGenerator::drawPlantlikeNode()
        }
 }
 
+void MapblockMeshGenerator::drawPlantlikeNode()
+{
+       useTile();
+       drawPlantlike();
+}
+
+void MapblockMeshGenerator::drawPlantlikeRootedNode()
+{
+       useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
+       origin += v3f(0.0, BS, 0.0);
+       p.Y++;
+       if (data->m_smooth_lighting) {
+               getSmoothLightFrame();
+       } else {
+               MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
+               light = LightPair(getInteriorLight(ntop, 1, nodedef));
+       }
+       drawPlantlike();
+       p.Y--;
+}
+
 void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
        float offset_h, float offset_v)
 {
@@ -944,18 +1018,19 @@ void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle
                v3f( scale, -BS / 2, 0),
                v3f(-scale, -BS / 2, 0),
        };
-       for (int i = 0; i < 4; i++) {
-               vertices[i].rotateYZBy(opening_angle);
-               vertices[i].Z += offset_h;
-               vertices[i].rotateXZBy(rotation);
-               vertices[i].Y += offset_v;
+
+       for (v3f &vertex : vertices) {
+               vertex.rotateYZBy(opening_angle);
+               vertex.Z += offset_h;
+               vertex.rotateXZBy(rotation);
+               vertex.Y += offset_v;
        }
        drawQuad(vertices);
 }
 
 void MapblockMeshGenerator::drawFirelikeNode()
 {
-       useTile(0, false);
+       useTile();
        scale = BS / 2 * f->visual_scale;
 
        // Check for adjacent nodes
@@ -1002,10 +1077,11 @@ void MapblockMeshGenerator::drawFirelikeNode()
 
 void MapblockMeshGenerator::drawFencelikeNode()
 {
-       useDefaultTile(false);
+       useTile(0, 0, 0);
        TileSpec tile_nocrack = tile;
-       for (int layer = 0; layer < MAX_TILE_LAYERS; layer++)
-               tile_nocrack.layers[layer].material_flags &= ~MATERIAL_FLAG_CRACK;
+
+       for (auto &layer : tile_nocrack.layers)
+               layer.material_flags &= ~MATERIAL_FLAG_CRACK;
 
        // Put wood the right way around in the posts
        TileSpec tile_rot = tile;
@@ -1152,7 +1228,7 @@ void MapblockMeshGenerator::drawRaillikeNode()
                angle = rail_kinds[code].angle;
        }
 
-       useTile(tile_index, true);
+       useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
 
        static const float offset = BS / 64;
        static const float size   = BS / 2;
@@ -1164,8 +1240,8 @@ void MapblockMeshGenerator::drawRaillikeNode()
                v3f(-size, -size + offset, -size),
        };
        if (angle)
-               for (int i = 0; i < 4; i++)
-                       vertices[i].rotateXZBy(angle);
+               for (v3f &vertex : vertices)
+                       vertex.rotateXZBy(angle);
        drawQuad(vertices);
 }
 
@@ -1193,7 +1269,7 @@ void MapblockMeshGenerator::drawNodeboxNode()
        TileSpec tiles[6];
        for (int face = 0; face < 6; face++) {
                // Handles facedir rotation for textures
-               tiles[face] = getTile(tile_dirs[face]);
+               getTile(tile_dirs[face], &tiles[face]);
        }
 
        // locate possible neighboring nodes to connect to
@@ -1210,8 +1286,8 @@ void MapblockMeshGenerator::drawNodeboxNode()
 
        std::vector<aabb3f> boxes;
        n.getNodeBoxes(nodedef, &boxes, neighbors_set);
-       for (std::vector<aabb3f>::iterator i = boxes.begin(); i != boxes.end(); ++i)
-               drawAutoLightedCuboid(*i, NULL, tiles, 6);
+       for (const auto &box : boxes)
+               drawAutoLightedCuboid(box, nullptr, tiles, 6);
 }
 
 void MapblockMeshGenerator::drawMeshNode()
@@ -1228,10 +1304,8 @@ void MapblockMeshGenerator::drawMeshNode()
                // Convert wallmounted to 6dfacedir.
                // When cache enabled, it is already converted.
                facedir = n.getWallMounted(nodedef);
-               if (!enable_mesh_cache) {
-                       static const u8 wm_to_6d[6] = {20, 0, 16 + 1, 12 + 3, 8, 4 + 2};
-                       facedir = wm_to_6d[facedir];
-               }
+               if (!enable_mesh_cache)
+                       facedir = wallmounted_to_facedir[facedir];
        }
 
        if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {
@@ -1250,7 +1324,7 @@ void MapblockMeshGenerator::drawMeshNode()
 
        int mesh_buffer_count = mesh->getMeshBufferCount();
        for (int j = 0; j < mesh_buffer_count; j++) {
-               useTile(j, false);
+               useTile(j);
                scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
                video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
                int vertex_count = buf->getVertexCount();
@@ -1286,19 +1360,29 @@ void MapblockMeshGenerator::errorUnknownDrawtype()
 
 void MapblockMeshGenerator::drawNode()
 {
+       // skip some drawtypes early
+       switch (f->drawtype) {
+               case NDT_NORMAL:   // Drawn by MapBlockMesh
+               case NDT_AIRLIKE:  // Not drawn at all
+               case NDT_LIQUID:   // Drawn by MapBlockMesh
+                       return;
+               default:
+                       break;
+       }
+       origin = intToFloat(p, BS);
        if (data->m_smooth_lighting)
                getSmoothLightFrame();
        else
-               light = getInteriorLight(n, 1, nodedef);
+               light = LightPair(getInteriorLight(n, 1, nodedef));
        switch (f->drawtype) {
-               case NDT_LIQUID:            drawLiquidNode(false); break;
-               case NDT_FLOWINGLIQUID:     drawLiquidNode(true); break;
+               case NDT_FLOWINGLIQUID:     drawLiquidNode(); break;
                case NDT_GLASSLIKE:         drawGlasslikeNode(); break;
                case NDT_GLASSLIKE_FRAMED:  drawGlasslikeFramedNode(); break;
                case NDT_ALLFACES:          drawAllfacesNode(); break;
                case NDT_TORCHLIKE:         drawTorchlikeNode(); break;
                case NDT_SIGNLIKE:          drawSignlikeNode(); break;
                case NDT_PLANTLIKE:         drawPlantlikeNode(); break;
+               case NDT_PLANTLIKE_ROOTED:  drawPlantlikeRootedNode(); break;
                case NDT_FIRELIKE:          drawFirelikeNode(); break;
                case NDT_FENCELIKE:         drawFencelikeNode(); break;
                case NDT_RAILLIKE:          drawRaillikeNode(); break;
@@ -1319,12 +1403,14 @@ void MapblockMeshGenerator::generate()
        for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
                n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
                f = &nodedef->get(n);
-               // Solid nodes are drawn by MapBlockMesh
-               if (f->solidness != 0)
-                       continue;
-               if (f->drawtype == NDT_AIRLIKE)
-                       continue;
-               origin = intToFloat(p, BS);
                drawNode();
        }
 }
+
+void MapblockMeshGenerator::renderSingle(content_t node)
+{
+       p = {0, 0, 0};
+       n = MapNode(node, 0xff, 0x00);
+       f = &nodedef->get(n);
+       drawNode();
+}