]> git.lizzy.rs Git - minetest.git/blobdiff - src/content_mapblock.cpp
Optimize headers (part 2) (#6272)
[minetest.git] / src / content_mapblock.cpp
index e224f0b0603e803d943e64a1f1c6ca121bf96eea..87a1624b5e5879b2f1e2d43a534b9b035b6dfe22 100644 (file)
@@ -1,6 +1,6 @@
 /*
-Minetest-c55
-Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
@@ -18,1063 +18,1345 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "content_mapblock.h"
-
-#include "main.h" // For g_settings
-#include "mapblock_mesh.h" // For MapBlock_LightColor() and MeshCollector
-#include "settings.h"
-#include "nodedef.h"
-#include "tile.h"
-#include "gamedef.h"
 #include "util/numeric.h"
 #include "util/directiontables.h"
+#include "mapblock_mesh.h"
+#include "settings.h"
+#include "nodedef.h"
+#include "client/tile.h"
+#include "mesh.h"
+#include <IMeshManipulator.h>
+#include "client/renderingengine.h"
+#include "client.h"
+#include "noise.h"
+
+// Distance of light extrapolation (for oversized nodes)
+// After this distance, it gives up and considers light level constant
+#define SMOOTH_LIGHTING_OVERSIZE 1.0
+
+// Node edge count (for glasslike-framed)
+#define FRAMED_EDGE_COUNT 12
+
+// Node neighbor count, including edge-connected, but not vertex-connected
+// (for glasslike-framed)
+// Corresponding offsets are listed in g_27dirs
+#define FRAMED_NEIGHBOR_COUNT 18
+
+static const v3s16 light_dirs[8] = {
+       v3s16(-1, -1, -1),
+       v3s16(-1, -1,  1),
+       v3s16(-1,  1, -1),
+       v3s16(-1,  1,  1),
+       v3s16( 1, -1, -1),
+       v3s16( 1, -1,  1),
+       v3s16( 1,  1, -1),
+       v3s16( 1,  1,  1),
+};
+
+// Standard index set to make a quad on 4 vertices
+static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
+
+const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
+
+MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output)
+{
+       data      = input;
+       collector = output;
+
+       nodedef   = data->m_client->ndef();
+       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
+
+       blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
+}
+
+void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
+{
+       if (special)
+               getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
+       else
+               getNodeTileN(n, p, index, data, tile);
+       if (!data->m_smooth_lighting)
+               color = encode_light(light, f->light_source);
+
+       for (auto &layer : tile.layers) {
+               layer.material_flags |= set_flags;
+               layer.material_flags &= ~reset_flags;
+       }
+}
+
+void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
+{
+       getNodeTile(n, p, direction, data, *tile);
+}
+
+/*!
+ * Returns the i-th special tile for a map node.
+ */
+void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
+{
+       *tile = f->special_tiles[index];
+       TileLayer *top_layer = NULL;
+
+       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);
+       for (int j = 0; j < 4; j++) {
+               vertices[j].Pos = coords[j] + origin;
+               vertices[j].Normal = normal2;
+               if (data->m_smooth_lighting)
+                       vertices[j].Color = blendLightColor(coords[j]);
+               else
+                       vertices[j].Color = color;
+               if (shade_face)
+                       applyFacesShading(vertices[j].Color, normal2);
+               vertices[j].TCoords = tcoords[j];
+       }
+       collector->append(tile, vertices, 4, quad_indices, 6);
+}
 
 // Create a cuboid.
-//  collector - the MeshCollector for the resulting polygons
-//  box       - the position and size of the box
 //  tiles     - the tiles (materials) to use (for all 6 faces)
 //  tilecount - number of entries in tiles, 1<=tilecount<=6
-//  c         - vertex colour - used for all
+//  lights    - vertex light levels. The order is the same as in light_dirs.
+//              NULL may be passed if smooth lighting is disabled.
 //  txc       - texture coordinates - this is a list of texture coordinates
 //              for the opposite corners of each face - therefore, there
-//              should be (2+2)*6=24 values in the list. Alternatively, pass
-//              NULL to use the entire texture for each face. The order of
+//              should be (2+2)*6=24 values in the list. The order of
 //              the faces in the list is up-down-right-left-back-front
-//              (compatible with ContentFeatures). If you specified 0,0,1,1
-//              for each face, that would be the same as passing NULL.
-void makeCuboid(MeshCollector *collector, const aabb3f &box,
-       const TileSpec *tiles, int tilecount,
-       video::SColor &c, const f32* txc)
+//              (compatible with ContentFeatures).
+void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
+       TileSpec *tiles, int tilecount, const u16 *lights, const f32 *txc)
 {
-       assert(tilecount >= 1 && tilecount <= 6);
+       assert(tilecount >= 1 && tilecount <= 6); // pre-condition
 
        v3f min = box.MinEdge;
        v3f max = box.MaxEdge;
 
-       if(txc == NULL)
-       {
-               static const f32 txc_default[24] = {
-                       0,0,1,1,
-                       0,0,1,1,
-                       0,0,1,1,
-                       0,0,1,1,
-                       0,0,1,1,
-                       0,0,1,1
-               };
-               txc = txc_default;
+       video::SColor colors[6];
+       if (!data->m_smooth_lighting) {
+               for (int face = 0; face != 6; ++face) {
+                       colors[face] = encode_light(light, f->light_source);
+               }
+               if (!f->light_source) {
+                       applyFacesShading(colors[0], v3f(0, 1, 0));
+                       applyFacesShading(colors[1], v3f(0, -1, 0));
+                       applyFacesShading(colors[2], v3f(1, 0, 0));
+                       applyFacesShading(colors[3], v3f(-1, 0, 0));
+                       applyFacesShading(colors[4], v3f(0, 0, 1));
+                       applyFacesShading(colors[5], v3f(0, 0, -1));
+               }
        }
 
-       video::S3DVertex vertices[24] =
-       {
-               // up
-               video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]),
-               video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]),
-               video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]),
-               video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]),
-               // down
-               video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]),
-               video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]),
-               video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]),
-               video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]),
+       video::S3DVertex vertices[24] = {
+               // top
+               video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
+               video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
+               video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
+               video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
+               // bottom
+               video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
+               video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
+               video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
+               video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
                // right
-               video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]),
-               video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]),
-               video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]),
-               video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]),
+               video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
+               video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
+               video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
+               video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
                // left
-               video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]),
-               video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]),
-               video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]),
-               video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]),
+               video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
+               video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
+               video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
+               video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
                // back
-               video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]),
-               video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]),
-               video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]),
-               video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]),
+               video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
+               video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
+               video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
+               video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
                // front
-               video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]),
-               video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]),
-               video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]),
-               video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]),
+               video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
+               video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
+               video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
+               video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
        };
 
-       for(s32 j=0; j<24; j++)
-       {
-               int tileindex = MYMIN(j/4, tilecount-1);
-               vertices[j].TCoords *= tiles[tileindex].texture.size;
-               vertices[j].TCoords += tiles[tileindex].texture.pos;
+       static const u8 light_indices[24] = {
+               3, 7, 6, 2,
+               0, 4, 5, 1,
+               6, 7, 5, 4,
+               3, 2, 0, 1,
+               7, 3, 1, 5,
+               2, 6, 4, 0
+       };
+
+       for (int face = 0; face < 6; face++) {
+               int tileindex = MYMIN(face, tilecount - 1);
+               const TileSpec &tile = tiles[tileindex];
+               for (int j = 0; j < 4; j++) {
+                       video::S3DVertex &vertex = vertices[face * 4 + j];
+                       v2f &tcoords = vertex.TCoords;
+                       switch (tile.rotation) {
+                       case 0:
+                               break;
+                       case 1: // R90
+                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
+                               break;
+                       case 2: // R180
+                               tcoords.rotateBy(180, irr::core::vector2df(0, 0));
+                               break;
+                       case 3: // R270
+                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
+                               break;
+                       case 4: // FXR90
+                               tcoords.X = 1.0 - tcoords.X;
+                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
+                               break;
+                       case 5: // FXR270
+                               tcoords.X = 1.0 - tcoords.X;
+                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
+                               break;
+                       case 6: // FYR90
+                               tcoords.Y = 1.0 - tcoords.Y;
+                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
+                               break;
+                       case 7: // FYR270
+                               tcoords.Y = 1.0 - tcoords.Y;
+                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
+                               break;
+                       case 8: // FX
+                               tcoords.X = 1.0 - tcoords.X;
+                               break;
+                       case 9: // FY
+                               tcoords.Y = 1.0 - tcoords.Y;
+                               break;
+                       default:
+                               break;
+                       }
+               }
        }
 
-       u16 indices[] = {0,1,2,2,3,0};
+       if (data->m_smooth_lighting) {
+               for (int j = 0; j < 24; ++j) {
+                       vertices[j].Color = encode_light(lights[light_indices[j]],
+                               f->light_source);
+                       if (!f->light_source)
+                               applyFacesShading(vertices[j].Color, vertices[j].Normal);
+               }
+       }
 
        // Add to mesh collector
-       for(s32 j=0; j<24; j+=4)
-       {
-               int tileindex = MYMIN(j/4, tilecount-1);
-               collector->append(tiles[tileindex],
-                               vertices+j, 4, indices, 6);
+       for (int k = 0; k < 6; ++k) {
+               int tileindex = MYMIN(k, tilecount - 1);
+               collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
+       }
+}
+
+// Gets the base lighting values for a node
+void MapblockMeshGenerator::getSmoothLightFrame()
+{
+       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;
+       }
+}
+
+// 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)
+{
+       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;
+       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];
+       }
+       return
+               core::clamp(core::round32(lightA), 0, 255) |
+               core::clamp(core::round32(lightB), 0, 255) << 8;
+}
+
+// Calculates vertex color to be used in mapblock mesh
+//  vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
+//  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);
+}
+
+video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
+       const v3f &vertex_normal)
+{
+       video::SColor color = blendLightColor(vertex_pos);
+       if (!f->light_source)
+               applyFacesShading(color, vertex_normal);
+       return color;
+}
+
+void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
+{
+       f32 tx1 = (box.MinEdge.X / BS) + 0.5;
+       f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
+       f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
+       f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
+       f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
+       f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
+       f32 txc[24] = {
+                   tx1, 1 - tz2,     tx2, 1 - tz1, // up
+                   tx1,     tz1,     tx2,     tz2, // down
+                   tz1, 1 - ty2,     tz2, 1 - ty1, // right
+               1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
+               1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
+                   tx1, 1 - ty2,     tx2, 1 - ty1, // front
+       };
+       for (int i = 0; i != 24; ++i)
+               coords[i] = txc[i];
+}
+
+void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
+       TileSpec *tiles, int tile_count)
+{
+       f32 texture_coord_buf[24];
+       f32 dx1 = box.MinEdge.X;
+       f32 dy1 = box.MinEdge.Y;
+       f32 dz1 = box.MinEdge.Z;
+       f32 dx2 = box.MaxEdge.X;
+       f32 dy2 = box.MaxEdge.Y;
+       f32 dz2 = box.MaxEdge.Z;
+       box.MinEdge += origin;
+       box.MaxEdge += origin;
+       if (!txc) {
+               generateCuboidTextureCoords(box, texture_coord_buf);
+               txc = texture_coord_buf;
+       }
+       if (!tiles) {
+               tiles = &tile;
+               tile_count = 1;
+       }
+       if (data->m_smooth_lighting) {
+               u16 lights[8];
+               for (int j = 0; j < 8; ++j) {
+                       v3f d;
+                       d.X = (j & 4) ? dx2 : dx1;
+                       d.Y = (j & 2) ? dy2 : dy1;
+                       d.Z = (j & 1) ? dz2 : dz1;
+                       lights[j] = blendLight(d);
+               }
+               drawCuboid(box, tiles, tile_count, lights, txc);
+       } else {
+               drawCuboid(box, tiles, tile_count, NULL, txc);
        }
 }
 
-void mapblock_mesh_generate_special(MeshMakeData *data,
-               MeshCollector &collector)
+void MapblockMeshGenerator::prepareLiquidNodeDrawing()
 {
-       INodeDefManager *nodedef = data->m_gamedef->ndef();
-
-       // 0ms
-       //TimeTaker timer("mapblock_mesh_generate_special()");
-
-       /*
-               Some settings
-       */
-       bool new_style_water = g_settings->getBool("new_style_water");
-       
-       float node_liquid_level = 1.0;
-       if(new_style_water)
-               node_liquid_level = 0.85;
-       
-       v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE;
-
-       for(s16 z=0; z<MAP_BLOCKSIZE; z++)
-       for(s16 y=0; y<MAP_BLOCKSIZE; y++)
-       for(s16 x=0; x<MAP_BLOCKSIZE; x++)
-       {
-               v3s16 p(x,y,z);
-
-               MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes+p);
-               const ContentFeatures &f = nodedef->get(n);
-
-               // Only solidness=0 stuff is drawn here
-               if(f.solidness != 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));
+       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);
+
+       if (data->m_smooth_lighting)
+               return; // don't need to pre-compute anything in this case
+
+       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);
+       } 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);
+       }
+
+       color_liquid_top = encode_light(light, f->light_source);
+       color = encode_light(light, f->light_source);
+}
+
+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++) {
+               NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
+               v3s16 p2 = p + v3s16(u, 0, w);
+               MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+               neighbor.content = n2.getContent();
+               neighbor.level = -0.5 * BS;
+               neighbor.is_same_liquid = false;
+               neighbor.top_is_same_liquid = false;
+
+               if (neighbor.content == CONTENT_IGNORE)
                        continue;
-               
-               switch(f.drawtype){
-               default:
-                       infostream<<"Got "<<f.drawtype<<std::endl;
-                       assert(0);
-                       break;
-               case NDT_AIRLIKE:
-                       break;
-               case NDT_LIQUID:
-               {
-                       /*
-                               Add water sources to mesh if using new style
-                       */
-                       TileSpec tile_liquid = f.special_tiles[0];
-                       AtlasPointer &pa_liquid = tile_liquid.texture;
-
-                       bool top_is_air = false;
-                       MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
-                       if(n.getContent() == CONTENT_AIR)
-                               top_is_air = true;
-                       
-                       if(top_is_air == false)
+
+               if (neighbor.content == c_source) {
+                       neighbor.is_same_liquid = true;
+                       neighbor.level = 0.5 * BS;
+               } else if (neighbor.content == c_flowing) {
+                       neighbor.is_same_liquid = true;
+                       u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
+                       if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
+                               liquid_level = 0;
+                       else
+                               liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
+                       neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
+               }
+
+               // Check node above neighbor.
+               // NOTE: This doesn't get executed if neighbor
+               //       doesn't exist
+               p2.Y++;
+               n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+               if (n2.getContent() == c_source || n2.getContent() == c_flowing)
+                       neighbor.top_is_same_liquid = true;
+       }
+}
+
+void MapblockMeshGenerator::calculateCornerLevels()
+{
+       for (int k = 0; k < 2; k++)
+       for (int i = 0; i < 2; i++)
+               corner_levels[k][i] = getCornerLevel(i, k);
+}
+
+f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
+{
+       float sum = 0;
+       int count = 0;
+       int air_count = 0;
+       for (int dk = 0; dk < 2; dk++)
+       for (int di = 0; di < 2; di++) {
+               NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
+               content_t content = neighbor_data.content;
+
+               // If top is liquid, draw starting from top of node
+               if (neighbor_data.top_is_same_liquid)
+                       return 0.5 * BS;
+
+               // Source always has the full height
+               if (content == c_source)
+                       return 0.5 * BS;
+
+               // Flowing liquid has level information
+               if (content == c_flowing) {
+                       sum += neighbor_data.level;
+                       count++;
+               } else if (content == CONTENT_AIR) {
+                       air_count++;
+                       if (air_count >= 2)
+                               return -0.5 * BS + 0.2;
+               }
+       }
+       if (count > 0)
+               return sum / count;
+       return 0;
+}
+
+void MapblockMeshGenerator::drawLiquidSides()
+{
+       struct LiquidFaceDesc {
+               v3s16 dir; // XZ
+               v3s16 p[2]; // XZ only; 1 means +, 0 means -
+       };
+       struct UV {
+               int u, v;
+       };
+       static const LiquidFaceDesc base_faces[4] = {
+               {v3s16( 1, 0,  0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
+               {v3s16(-1, 0,  0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
+               {v3s16( 0, 0,  1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
+               {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
+       };
+       static const UV base_vertices[4] = {
+               {0, 1},
+               {1, 1},
+               {1, 0},
+               {0, 0}
+       };
+
+       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 (!top_is_same_liquid)
+                               continue;
+                       if (neighbor.top_is_same_liquid)
                                continue;
+               }
 
-                       u16 l = getInteriorLight(n, 0, data);
-                       video::SColor c = MapBlock_LightColor(f.alpha, l, decode_light(f.light_source));
-                       
-                       video::S3DVertex vertices[4] =
-                       {
-                               video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
-                                               pa_liquid.x0(), pa_liquid.y1()),
-                               video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
-                                               pa_liquid.x1(), pa_liquid.y1()),
-                               video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
-                                               pa_liquid.x1(), pa_liquid.y0()),
-                               video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
-                                               pa_liquid.x0(), pa_liquid.y0()),
-                       };
-
-                       v3f offset(p.X, p.Y + (-0.5+node_liquid_level)*BS, p.Z);
-                       for(s32 i=0; i<4; i++)
-                       {
-                               vertices[i].Pos += offset;
-                       }
+               const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
+               // Don't draw face if neighbor is blocking the view
+               if (neighbor_features.solidness == 2)
+                       continue;
 
-                       u16 indices[] = {0,1,2,2,3,0};
-                       // Add to mesh collector
-                       collector.append(tile_liquid, vertices, 4, indices, 6);
-               break;}
-               case NDT_FLOWINGLIQUID:
-               {
-                       /*
-                               Add flowing liquid to mesh
-                       */
-                       TileSpec tile_liquid = f.special_tiles[0];
-                       TileSpec tile_liquid_bfculled = f.special_tiles[1];
-                       AtlasPointer &pa_liquid = tile_liquid.texture;
-
-                       bool top_is_same_liquid = false;
-                       MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z));
-                       content_t c_flowing = nodedef->getId(f.liquid_alternative_flowing);
-                       content_t c_source = nodedef->getId(f.liquid_alternative_source);
-                       if(ntop.getContent() == c_flowing || ntop.getContent() == c_source)
-                               top_is_same_liquid = true;
-                       
-                       u16 l = 0;
-                       // If this liquid emits light and doesn't contain light, draw
-                       // it at what it emits, for an increased effect
-                       u8 light_source = nodedef->get(n).light_source;
-                       if(light_source != 0){
-                               //l = decode_light(undiminish_light(light_source));
-                               l = decode_light(light_source);
-                               l = l | (l<<8);
-                       }
-                       // Use the light of the node on top if possible
-                       else if(nodedef->get(ntop).param_type == CPT_LIGHT)
-                               l = getInteriorLight(ntop, 0, data);
-                       // Otherwise use the light of this node (the liquid)
+               video::S3DVertex vertices[4];
+               for (int j = 0; j < 4; j++) {
+                       const UV &vertex = base_vertices[j];
+                       const v3s16 &base = face.p[vertex.u];
+                       v3f pos;
+                       pos.X = (base.X - 0.5) * BS;
+                       pos.Z = (base.Z - 0.5) * BS;
+                       if (vertex.v)
+                               pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5 * BS;
                        else
-                               l = getInteriorLight(n, 0, data);
-                       video::SColor c = MapBlock_LightColor(f.alpha, l, decode_light(f.light_source));
-                       
-                       // Neighbor liquid levels (key = relative position)
-                       // Includes current node
-                       core::map<v3s16, f32> neighbor_levels;
-                       core::map<v3s16, content_t> neighbor_contents;
-                       core::map<v3s16, u8> neighbor_flags;
-                       const u8 neighborflag_top_is_same_liquid = 0x01;
-                       v3s16 neighbor_dirs[9] = {
-                               v3s16(0,0,0),
-                               v3s16(0,0,1),
-                               v3s16(0,0,-1),
-                               v3s16(1,0,0),
-                               v3s16(-1,0,0),
-                               v3s16(1,0,1),
-                               v3s16(-1,0,-1),
-                               v3s16(1,0,-1),
-                               v3s16(-1,0,1),
-                       };
-                       for(u32 i=0; i<9; i++)
-                       {
-                               content_t content = CONTENT_AIR;
-                               float level = -0.5 * BS;
-                               u8 flags = 0;
-                               // Check neighbor
-                               v3s16 p2 = p + neighbor_dirs[i];
-                               MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-                               if(n2.getContent() != CONTENT_IGNORE)
-                               {
-                                       content = n2.getContent();
-
-                                       if(n2.getContent() == c_source)
-                                               level = (-0.5+node_liquid_level) * BS;
-                                       else if(n2.getContent() == c_flowing)
-                                               level = (-0.5 + ((float)(n2.param2&LIQUID_LEVEL_MASK)
-                                                               + 0.5) / (float)LIQUID_LEVEL_SOURCE * node_liquid_level) * BS;
-
-                                       // Check node above neighbor.
-                                       // NOTE: This doesn't get executed if neighbor
-                                       //       doesn't exist
-                                       p2.Y += 1;
-                                       n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-                                       if(n2.getContent() == c_source ||
-                                                       n2.getContent() == c_flowing)
-                                               flags |= neighborflag_top_is_same_liquid;
-                               }
-                               
-                               neighbor_levels.insert(neighbor_dirs[i], level);
-                               neighbor_contents.insert(neighbor_dirs[i], content);
-                               neighbor_flags.insert(neighbor_dirs[i], flags);
-                       }
+                               pos.Y =     !top_is_same_liquid ? corner_levels[base.Z][base.X] :  0.5 * BS;
+                       if (data->m_smooth_lighting)
+                               color = blendLightColor(pos);
+                       pos += origin;
+                       vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v);
+               };
+               collector->append(tile_liquid, vertices, 4, quad_indices, 6);
+       }
+}
 
-                       // Corner heights (average between four liquids)
-                       f32 corner_levels[4];
-                       
-                       v3s16 halfdirs[4] = {
-                               v3s16(0,0,0),
-                               v3s16(1,0,0),
-                               v3s16(1,0,1),
-                               v3s16(0,0,1),
-                       };
-                       for(u32 i=0; i<4; i++)
-                       {
-                               v3s16 cornerdir = halfdirs[i];
-                               float cornerlevel = 0;
-                               u32 valid_count = 0;
-                               u32 air_count = 0;
-                               for(u32 j=0; j<4; j++)
-                               {
-                                       v3s16 neighbordir = cornerdir - halfdirs[j];
-                                       content_t content = neighbor_contents[neighbordir];
-                                       // If top is liquid, draw starting from top of node
-                                       if(neighbor_flags[neighbordir] &
-                                                       neighborflag_top_is_same_liquid)
-                                       {
-                                               cornerlevel = 0.5*BS;
-                                               valid_count = 1;
-                                               break;
-                                       }
-                                       // Source is always the same height
-                                       else if(content == c_source)
-                                       {
-                                               cornerlevel = (-0.5+node_liquid_level)*BS;
-                                               valid_count = 1;
-                                               break;
-                                       }
-                                       // Flowing liquid has level information
-                                       else if(content == c_flowing)
-                                       {
-                                               cornerlevel += neighbor_levels[neighbordir];
-                                               valid_count++;
-                                       }
-                                       else if(content == CONTENT_AIR)
-                                       {
-                                               air_count++;
-                                       }
-                               }
-                               if(air_count >= 2)
-                                       cornerlevel = -0.5*BS+0.1;
-                               else if(valid_count > 0)
-                                       cornerlevel /= valid_count;
-                               corner_levels[i] = cornerlevel;
-                       }
+void MapblockMeshGenerator::drawLiquidTop()
+{
+       // To get backface culling right, the vertices need to go
+       // clockwise around the front of the face. And we happened to
+       // calculate corner levels in exact reverse order.
+       static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
+
+       video::S3DVertex vertices[4] = {
+               video::S3DVertex(-BS / 2, 0,  BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
+               video::S3DVertex( BS / 2, 0,  BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
+               video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
+               video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
+       };
 
-                       /*
-                               Generate sides
-                       */
-
-                       v3s16 side_dirs[4] = {
-                               v3s16(1,0,0),
-                               v3s16(-1,0,0),
-                               v3s16(0,0,1),
-                               v3s16(0,0,-1),
-                       };
-                       s16 side_corners[4][2] = {
-                               {1, 2},
-                               {3, 0},
-                               {2, 3},
-                               {0, 1},
-                       };
-                       for(u32 i=0; i<4; i++)
-                       {
-                               v3s16 dir = side_dirs[i];
-
-                               /*
-                                       If our topside is liquid and neighbor's topside
-                                       is liquid, don't draw side face
-                               */
-                               if(top_is_same_liquid &&
-                                               neighbor_flags[dir] & neighborflag_top_is_same_liquid)
-                                       continue;
-
-                               content_t neighbor_content = neighbor_contents[dir];
-                               const ContentFeatures &n_feat = nodedef->get(neighbor_content);
-                               
-                               // Don't draw face if neighbor is blocking the view
-                               if(n_feat.solidness == 2)
-                                       continue;
-                               
-                               bool neighbor_is_same_liquid = (neighbor_content == c_source
-                                               || neighbor_content == c_flowing);
-                               
-                               // Don't draw any faces if neighbor same is liquid and top is
-                               // same liquid
-                               if(neighbor_is_same_liquid == true
-                                               && top_is_same_liquid == false)
-                                       continue;
-
-                               // Use backface culled material if neighbor doesn't have a
-                               // solidness of 0
-                               const TileSpec *current_tile = &tile_liquid;
-                               if(n_feat.solidness != 0 || n_feat.visual_solidness != 0)
-                                       current_tile = &tile_liquid_bfculled;
-                               
-                               video::S3DVertex vertices[4] =
-                               {
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
-                                                       pa_liquid.x0(), pa_liquid.y1()),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
-                                                       pa_liquid.x1(), pa_liquid.y1()),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
-                                                       pa_liquid.x1(), pa_liquid.y0()),
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
-                                                       pa_liquid.x0(), pa_liquid.y0()),
-                               };
-                               
-                               /*
-                                       If our topside is liquid, set upper border of face
-                                       at upper border of node
-                               */
-                               if(top_is_same_liquid)
-                               {
-                                       vertices[2].Pos.Y = 0.5*BS;
-                                       vertices[3].Pos.Y = 0.5*BS;
-                               }
-                               /*
-                                       Otherwise upper position of face is corner levels
-                               */
-                               else
-                               {
-                                       vertices[2].Pos.Y = corner_levels[side_corners[i][0]];
-                                       vertices[3].Pos.Y = corner_levels[side_corners[i][1]];
-                               }
-                               
-                               /*
-                                       If neighbor is liquid, lower border of face is corner
-                                       liquid levels
-                               */
-                               if(neighbor_is_same_liquid)
-                               {
-                                       vertices[0].Pos.Y = corner_levels[side_corners[i][1]];
-                                       vertices[1].Pos.Y = corner_levels[side_corners[i][0]];
-                               }
-                               /*
-                                       If neighbor is not liquid, lower border of face is
-                                       lower border of node
-                               */
-                               else
-                               {
-                                       vertices[0].Pos.Y = -0.5*BS;
-                                       vertices[1].Pos.Y = -0.5*BS;
-                               }
-                               
-                               for(s32 j=0; j<4; j++)
-                               {
-                                       if(dir == v3s16(0,0,1))
-                                               vertices[j].Pos.rotateXZBy(0);
-                                       if(dir == v3s16(0,0,-1))
-                                               vertices[j].Pos.rotateXZBy(180);
-                                       if(dir == v3s16(-1,0,0))
-                                               vertices[j].Pos.rotateXZBy(90);
-                                       if(dir == v3s16(1,0,-0))
-                                               vertices[j].Pos.rotateXZBy(-90);
-                                               
-                                       // Do this to not cause glitches when two liquids are
-                                       // side-by-side
-                                       /*if(neighbor_is_same_liquid == false){
-                                               vertices[j].Pos.X *= 0.98;
-                                               vertices[j].Pos.Z *= 0.98;
-                                       }*/
-
-                                       vertices[j].Pos += intToFloat(p, BS);
-                               }
-
-                               u16 indices[] = {0,1,2,2,3,0};
-                               // Add to mesh collector
-                               collector.append(*current_tile, vertices, 4, indices, 6);
-                       }
-                       
-                       /*
-                               Generate top side, if appropriate
-                       */
-                       
-                       if(top_is_same_liquid == false)
-                       {
-                               video::S3DVertex vertices[4] =
-                               {
-                                       video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c,
-                                                       pa_liquid.x0(), pa_liquid.y1()),
-                                       video::S3DVertex(BS/2,0,BS/2, 0,0,0, c,
-                                                       pa_liquid.x1(), pa_liquid.y1()),
-                                       video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c,
-                                                       pa_liquid.x1(), pa_liquid.y0()),
-                                       video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c,
-                                                       pa_liquid.x0(), pa_liquid.y0()),
-                               };
-                               
-                               // To get backface culling right, the vertices need to go
-                               // clockwise around the front of the face. And we happened to
-                               // calculate corner levels in exact reverse order.
-                               s32 corner_resolve[4] = {3,2,1,0};
-
-                               for(s32 i=0; i<4; i++)
-                               {
-                                       //vertices[i].Pos.Y += liquid_level;
-                                       //vertices[i].Pos.Y += neighbor_levels[v3s16(0,0,0)];
-                                       s32 j = corner_resolve[i];
-                                       vertices[i].Pos.Y += corner_levels[j];
-                                       vertices[i].Pos += intToFloat(p, BS);
-                               }
-                               
-                               // 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
-                               int dz = (corner_levels[side_corners[3][0]] +
-                                               corner_levels[side_corners[3][1]]) -
-                                               (corner_levels[side_corners[2][0]] +
-                                               corner_levels[side_corners[2][1]]);
-                               // Positive if liquid moves towards +X
-                               int dx = (corner_levels[side_corners[1][0]] +
-                                               corner_levels[side_corners[1][1]]) -
-                                               (corner_levels[side_corners[0][0]] +
-                                               corner_levels[side_corners[0][1]]);
-                               // -X
-                               if(-dx >= abs(dz))
-                               {
-                                       v2f t = vertices[0].TCoords;
-                                       vertices[0].TCoords = vertices[1].TCoords;
-                                       vertices[1].TCoords = vertices[2].TCoords;
-                                       vertices[2].TCoords = vertices[3].TCoords;
-                                       vertices[3].TCoords = t;
-                               }
-                               // +X
-                               if(dx >= abs(dz))
-                               {
-                                       v2f t = vertices[0].TCoords;
-                                       vertices[0].TCoords = vertices[3].TCoords;
-                                       vertices[3].TCoords = vertices[2].TCoords;
-                                       vertices[2].TCoords = vertices[1].TCoords;
-                                       vertices[1].TCoords = t;
-                               }
-                               // -Z
-                               if(-dz >= abs(dx))
-                               {
-                                       v2f t = vertices[0].TCoords;
-                                       vertices[0].TCoords = vertices[3].TCoords;
-                                       vertices[3].TCoords = vertices[2].TCoords;
-                                       vertices[2].TCoords = vertices[1].TCoords;
-                                       vertices[1].TCoords = t;
-                                       t = vertices[0].TCoords;
-                                       vertices[0].TCoords = vertices[3].TCoords;
-                                       vertices[3].TCoords = vertices[2].TCoords;
-                                       vertices[2].TCoords = vertices[1].TCoords;
-                                       vertices[1].TCoords = t;
-                               }
-
-                               u16 indices[] = {0,1,2,2,3,0};
-                               // Add to mesh collector
-                               collector.append(tile_liquid, vertices, 4, indices, 6);
-                       }
-               break;}
-               case NDT_GLASSLIKE:
-               {
-                       TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data);
-                       AtlasPointer ap = tile.texture;
-
-                       u16 l = getInteriorLight(n, 1, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       for(u32 j=0; j<6; j++)
-                       {
-                               // Check this neighbor
-                               v3s16 n2p = blockpos_nodes + p + g_6dirs[j];
-                               MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
-                               // Don't make face if neighbor is of same type
-                               if(n2.getContent() == n.getContent())
-                                       continue;
-
-                               // The face at Z+
-                               video::S3DVertex vertices[4] =
-                               {
-                                       video::S3DVertex(-BS/2,-BS/2,BS/2, 0,0,0, c,
-                                               ap.x0(), ap.y1()),
-                                       video::S3DVertex(BS/2,-BS/2,BS/2, 0,0,0, c,
-                                               ap.x1(), ap.y1()),
-                                       video::S3DVertex(BS/2,BS/2,BS/2, 0,0,0, c,
-                                               ap.x1(), ap.y0()),
-                                       video::S3DVertex(-BS/2,BS/2,BS/2, 0,0,0, c,
-                                               ap.x0(), ap.y0()),
-                               };
-                               
-                               // Rotations in the g_6dirs format
-                               if(j == 0) // Z+
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateXZBy(0);
-                               else if(j == 1) // Y+
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateYZBy(-90);
-                               else if(j == 2) // X+
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateXZBy(-90);
-                               else if(j == 3) // Z-
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateXZBy(180);
-                               else if(j == 4) // Y-
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateYZBy(90);
-                               else if(j == 5) // X-
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateXZBy(90);
-
-                               for(u16 i=0; i<4; i++){
-                                       vertices[i].Pos += intToFloat(p, BS);
-                               }
-
-                               u16 indices[] = {0,1,2,2,3,0};
-                               // Add to mesh collector
-                               collector.append(tile, vertices, 4, indices, 6);
-                       }
-               break;}
-               case NDT_ALLFACES:
-               {
-                       TileSpec tile_leaves = getNodeTile(n, p,
-                                       v3s16(0,0,0), data);
-                       AtlasPointer pa_leaves = tile_leaves.texture;
-
-                       u16 l = getInteriorLight(n, 1, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       v3f pos = intToFloat(p, BS);
-                       aabb3f box(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2);
-                       box.MinEdge += pos;
-                       box.MaxEdge += pos;
-                       makeCuboid(&collector, box, &tile_leaves, 1, c, NULL);
-               break;}
-               case NDT_ALLFACES_OPTIONAL:
-                       // This is always pre-converted to something else
-                       assert(0);
-                       break;
-               case NDT_TORCHLIKE:
-               {
-                       v3s16 dir = n.getWallMountedDir(nodedef);
-                       
-                       u8 tileindex = 0;
-                       if(dir == v3s16(0,-1,0)){
-                               tileindex = 0; // floor
-                       } else if(dir == v3s16(0,1,0)){
-                               tileindex = 1; // ceiling
-                       // For backwards compatibility
-                       } else if(dir == v3s16(0,0,0)){
-                               tileindex = 0; // floor
-                       } else {
-                               tileindex = 2; // side
-                       }
+       for (int i = 0; i < 4; i++) {
+               int u = corner_resolve[i][0];
+               int w = corner_resolve[i][1];
+               vertices[i].Pos.Y += corner_levels[w][u];
+               if (data->m_smooth_lighting)
+                       vertices[i].Color = blendLightColor(vertices[i].Pos);
+               vertices[i].Pos += origin;
+       }
 
-                       TileSpec tile = getNodeTileN(n, p, tileindex, data);
-                       tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
-                       tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
-
-                       AtlasPointer ap = tile.texture;
-
-                       u16 l = getInteriorLight(n, 1, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       // Wall at X+ of node
-                       video::S3DVertex vertices[4] =
-                       {
-                               video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c,
-                                               ap.x0(), ap.y1()),
-                               video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c,
-                                               ap.x1(), ap.y1()),
-                               video::S3DVertex(BS/2,BS/2,0, 0,0,0, c,
-                                               ap.x1(), ap.y0()),
-                               video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c,
-                                               ap.x0(), ap.y0()),
-                       };
-
-                       for(s32 i=0; i<4; i++)
-                       {
-                               if(dir == v3s16(1,0,0))
-                                       vertices[i].Pos.rotateXZBy(0);
-                               if(dir == v3s16(-1,0,0))
-                                       vertices[i].Pos.rotateXZBy(180);
-                               if(dir == v3s16(0,0,1))
-                                       vertices[i].Pos.rotateXZBy(90);
-                               if(dir == v3s16(0,0,-1))
-                                       vertices[i].Pos.rotateXZBy(-90);
-                               if(dir == v3s16(0,-1,0))
-                                       vertices[i].Pos.rotateXZBy(45);
-                               if(dir == v3s16(0,1,0))
-                                       vertices[i].Pos.rotateXZBy(-45);
-
-                               vertices[i].Pos += intToFloat(p, BS);
-                       }
+       // 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 (auto &vertice : vertices) {
+               vertice.TCoords.rotateBy(tcoord_angle, tcoord_center);
+               vertice.TCoords += tcoord_translate;
+       }
 
-                       u16 indices[] = {0,1,2,2,3,0};
-                       // Add to mesh collector
-                       collector.append(tile, vertices, 4, indices, 6);
-               break;}
-               case NDT_SIGNLIKE:
-               {
-                       TileSpec tile = getNodeTileN(n, p, 0, data);
-                       tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
-                       tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
-                       AtlasPointer ap = tile.texture;
-
-                       u16 l = getInteriorLight(n, 0, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-                               
-                       float d = (float)BS/16;
-                       // Wall at X+ of node
-                       video::S3DVertex vertices[4] =
-                       {
-                               video::S3DVertex(BS/2-d,BS/2,BS/2, 0,0,0, c,
-                                               ap.x0(), ap.y0()),
-                               video::S3DVertex(BS/2-d,BS/2,-BS/2, 0,0,0, c,
-                                               ap.x1(), ap.y0()),
-                               video::S3DVertex(BS/2-d,-BS/2,-BS/2, 0,0,0, c,
-                                               ap.x1(), ap.y1()),
-                               video::S3DVertex(BS/2-d,-BS/2,BS/2, 0,0,0, c,
-                                               ap.x0(), ap.y1()),
-                       };
-
-                       v3s16 dir = n.getWallMountedDir(nodedef);
-
-                       for(s32 i=0; i<4; i++)
-                       {
-                               if(dir == v3s16(1,0,0))
-                                       vertices[i].Pos.rotateXZBy(0);
-                               if(dir == v3s16(-1,0,0))
-                                       vertices[i].Pos.rotateXZBy(180);
-                               if(dir == v3s16(0,0,1))
-                                       vertices[i].Pos.rotateXZBy(90);
-                               if(dir == v3s16(0,0,-1))
-                                       vertices[i].Pos.rotateXZBy(-90);
-                               if(dir == v3s16(0,-1,0))
-                                       vertices[i].Pos.rotateXYBy(-90);
-                               if(dir == v3s16(0,1,0))
-                                       vertices[i].Pos.rotateXYBy(90);
-
-                               vertices[i].Pos += intToFloat(p, BS);
-                       }
+       std::swap(vertices[0].TCoords, vertices[2].TCoords);
 
-                       u16 indices[] = {0,1,2,2,3,0};
-                       // Add to mesh collector
-                       collector.append(tile, vertices, 4, indices, 6);
-               break;}
-               case NDT_PLANTLIKE:
-               {
-                       TileSpec tile = getNodeTileN(n, p, 0, data);
-                       tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
-                       AtlasPointer ap = tile.texture;
-                       
-                       u16 l = getInteriorLight(n, 1, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       for(u32 j=0; j<2; j++)
-                       {
-                               video::S3DVertex vertices[4] =
-                               {
-                                       video::S3DVertex(-BS/2*f.visual_scale,-BS/2,0, 0,0,0, c,
-                                               ap.x0(), ap.y1()),
-                                       video::S3DVertex( BS/2*f.visual_scale,-BS/2,0, 0,0,0, c,
-                                               ap.x1(), ap.y1()),
-                                       video::S3DVertex( BS/2*f.visual_scale,
-                                               -BS/2 + f.visual_scale*BS,0, 0,0,0, c,
-                                               ap.x1(), ap.y0()),
-                                       video::S3DVertex(-BS/2*f.visual_scale,
-                                               -BS/2 + f.visual_scale*BS,0, 0,0,0, c,
-                                               ap.x0(), ap.y0()),
-                               };
-
-                               if(j == 0)
-                               {
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateXZBy(45);
-                               }
-                               else if(j == 1)
-                               {
-                                       for(u16 i=0; i<4; i++)
-                                               vertices[i].Pos.rotateXZBy(-45);
-                               }
-
-                               for(u16 i=0; i<4; i++)
-                               {
-                                       vertices[i].Pos *= f.visual_scale;
-                                       vertices[i].Pos += intToFloat(p, BS);
-                               }
-
-                               u16 indices[] = {0,1,2,2,3,0};
-                               // Add to mesh collector
-                               collector.append(tile, vertices, 4, indices, 6);
-                       }
-               break;}
-               case NDT_FENCELIKE:
-               {
-                       TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data);
-                       TileSpec tile_nocrack = tile;
-                       tile_nocrack.material_flags &= ~MATERIAL_FLAG_CRACK;
-                       
-                       // A hack to put wood the right way around in the posts
-                       ITextureSource *tsrc = data->m_gamedef->tsrc();
-                       TileSpec tile_rot = tile;
-                       tile_rot.texture = tsrc->getTexture(tsrc->getTextureName(
-                                       tile.texture.id) + "^[transformR90");
-                                       
-                       u16 l = getInteriorLight(n, 1, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       const f32 post_rad=(f32)BS/8;
-                       const f32 bar_rad=(f32)BS/16;
-                       const f32 bar_len=(f32)(BS/2)-post_rad;
-
-                       v3f pos = intToFloat(p, BS);
-
-                       // The post - always present
-                       aabb3f post(-post_rad,-BS/2,-post_rad,post_rad,BS/2,post_rad);
-                       post.MinEdge += pos;
-                       post.MaxEdge += pos;
-                       f32 postuv[24]={
-                                       6/16.,6/16.,10/16.,10/16.,
-                                       6/16.,6/16.,10/16.,10/16.,
-                                       0/16.,0,4/16.,1,
-                                       4/16.,0,8/16.,1,
-                                       8/16.,0,12/16.,1,
-                                       12/16.,0,16/16.,1};
-                       makeCuboid(&collector, post, &tile_rot, 1, c, postuv);
-
-                       // Now a section of fence, +X, if there's a post there
-                       v3s16 p2 = p;
-                       p2.X++;
-                       MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-                       const ContentFeatures *f2 = &nodedef->get(n2);
-                       if(f2->drawtype == NDT_FENCELIKE)
-                       {
-                               aabb3f bar(-bar_len+BS/2,-bar_rad+BS/4,-bar_rad,
-                                               bar_len+BS/2,bar_rad+BS/4,bar_rad);
-                               bar.MinEdge += pos;
-                               bar.MaxEdge += pos;
-                               f32 xrailuv[24]={
-                                       0/16.,2/16.,16/16.,4/16.,
-                                       0/16.,4/16.,16/16.,6/16.,
-                                       6/16.,6/16.,8/16.,8/16.,
-                                       10/16.,10/16.,12/16.,12/16.,
-                                       0/16.,8/16.,16/16.,10/16.,
-                                       0/16.,14/16.,16/16.,16/16.};
-                               makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, xrailuv);
-                               bar.MinEdge.Y -= BS/2;
-                               bar.MaxEdge.Y -= BS/2;
-                               makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, xrailuv);
-                       }
+       collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
+}
 
-                       // Now a section of fence, +Z, if there's a post there
-                       p2 = p;
-                       p2.Z++;
-                       n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-                       f2 = &nodedef->get(n2);
-                       if(f2->drawtype == NDT_FENCELIKE)
-                       {
-                               aabb3f bar(-bar_rad,-bar_rad+BS/4,-bar_len+BS/2,
-                                               bar_rad,bar_rad+BS/4,bar_len+BS/2);
-                               bar.MinEdge += pos;
-                               bar.MaxEdge += pos;
-                               f32 zrailuv[24]={
-                                       3/16.,1/16.,5/16.,5/16., // cannot rotate; stretch
-                                       4/16.,1/16.,6/16.,5/16., // for wood texture instead
-                                       0/16.,9/16.,16/16.,11/16.,
-                                       0/16.,6/16.,16/16.,8/16.,
-                                       6/16.,6/16.,8/16.,8/16.,
-                                       10/16.,10/16.,12/16.,12/16.};
-                               makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, zrailuv);
-                               bar.MinEdge.Y -= BS/2;
-                               bar.MaxEdge.Y -= BS/2;
-                               makeCuboid(&collector, bar, &tile_nocrack, 1,
-                                               c, zrailuv);
-                       }
-               break;}
-               case NDT_RAILLIKE:
-               {
-                       bool is_rail_x [] = { false, false };  /* x-1, x+1 */
-                       bool is_rail_z [] = { false, false };  /* z-1, z+1 */
-
-                       bool is_rail_z_minus_y [] = { false, false };  /* z-1, z+1; y-1 */
-                       bool is_rail_x_minus_y [] = { false, false };  /* x-1, z+1; y-1 */
-                       bool is_rail_z_plus_y [] = { false, false };  /* z-1, z+1; y+1 */
-                       bool is_rail_x_plus_y [] = { false, false };  /* x-1, x+1; y+1 */
-
-                       MapNode n_minus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1,y,z));
-                       MapNode n_plus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1,y,z));
-                       MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1));
-                       MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1));
-                       MapNode n_plus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y+1, z));
-                       MapNode n_plus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y-1, z));
-                       MapNode n_minus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y+1, z));
-                       MapNode n_minus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y-1, z));
-                       MapNode n_plus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z+1));
-                       MapNode n_minus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z-1));
-                       MapNode n_plus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z+1));
-                       MapNode n_minus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z-1));
-                       
-                       content_t thiscontent = n.getContent();
-                       if(n_minus_x.getContent() == thiscontent)
-                               is_rail_x[0] = true;
-                       if (n_minus_x_minus_y.getContent() == thiscontent)
-                               is_rail_x_minus_y[0] = true;
-                       if(n_minus_x_plus_y.getContent() == thiscontent)
-                               is_rail_x_plus_y[0] = true;
-
-                       if(n_plus_x.getContent() == thiscontent)
-                               is_rail_x[1] = true;
-                       if (n_plus_x_minus_y.getContent() == thiscontent)
-                               is_rail_x_minus_y[1] = true;
-                       if(n_plus_x_plus_y.getContent() == thiscontent)
-                               is_rail_x_plus_y[1] = true;
-
-                       if(n_minus_z.getContent() == thiscontent)
-                               is_rail_z[0] = true;
-                       if (n_minus_z_minus_y.getContent() == thiscontent)
-                               is_rail_z_minus_y[0] = true;
-                       if(n_minus_z_plus_y.getContent() == thiscontent)
-                               is_rail_z_plus_y[0] = true;
-
-                       if(n_plus_z.getContent() == thiscontent)
-                               is_rail_z[1] = true;
-                       if (n_plus_z_minus_y.getContent() == thiscontent)
-                               is_rail_z_minus_y[1] = true;
-                       if(n_plus_z_plus_y.getContent() == thiscontent)
-                               is_rail_z_plus_y[1] = true;
-
-                       bool is_rail_x_all[] = {false, false};
-                       bool is_rail_z_all[] = {false, false};
-                       is_rail_x_all[0]=is_rail_x[0] || is_rail_x_minus_y[0] || is_rail_x_plus_y[0];
-                       is_rail_x_all[1]=is_rail_x[1] || is_rail_x_minus_y[1] || is_rail_x_plus_y[1];
-                       is_rail_z_all[0]=is_rail_z[0] || is_rail_z_minus_y[0] || is_rail_z_plus_y[0];
-                       is_rail_z_all[1]=is_rail_z[1] || is_rail_z_minus_y[1] || is_rail_z_plus_y[1];
-
-                       // reasonable default, flat straight unrotated rail
-                       bool is_straight = true;
-                       int adjacencies = 0;
-                       int angle = 0;
-                       u8 tileindex = 0;
-
-                       // check for sloped rail
-                       if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1])
-                       {
-                               adjacencies = 5; //5 means sloped
-                               is_straight = true; // sloped is always straight
-                       }
-                       else
-                       {
-                               // is really straight, rails on both sides
-                               is_straight = (is_rail_x_all[0] && is_rail_x_all[1]) || (is_rail_z_all[0] && is_rail_z_all[1]);
-                               adjacencies = is_rail_x_all[0] + is_rail_x_all[1] + is_rail_z_all[0] + is_rail_z_all[1];
-                       }
+void MapblockMeshGenerator::drawLiquidNode()
+{
+       prepareLiquidNodeDrawing();
+       getLiquidNeighborhood();
+       calculateCornerLevels();
+       drawLiquidSides();
+       if (!top_is_same_liquid)
+               drawLiquidTop();
+}
 
-                       switch (adjacencies) {
-                       case 1:
-                               if(is_rail_x_all[0] || is_rail_x_all[1])
-                                       angle = 90;
-                               break;
-                       case 2:
-                               if(!is_straight)
-                                       tileindex = 1; // curved
-                               if(is_rail_x_all[0] && is_rail_x_all[1])
-                                       angle = 90;
-                               if(is_rail_z_all[0] && is_rail_z_all[1]){
-                                       if (n_minus_z_plus_y.getContent() == thiscontent) angle = 180;
-                               }
-                               else if(is_rail_x_all[0] && is_rail_z_all[0])
-                                       angle = 270;
-                               else if(is_rail_x_all[0] && is_rail_z_all[1])
-                                       angle = 180;
-                               else if(is_rail_x_all[1] && is_rail_z_all[1])
-                                       angle = 90;
-                               break;
-                       case 3:
-                               // here is where the potential to 'switch' a junction is, but not implemented at present
-                               tileindex = 2; // t-junction
-                               if(!is_rail_x_all[1])
-                                       angle=180;
-                               if(!is_rail_z_all[0])
-                                       angle=90;
-                               if(!is_rail_z_all[1])
-                                       angle=270;
-                               break;
-                       case 4:
-                               tileindex = 3; // crossing
-                               break;
-                       case 5: //sloped
-                               if(is_rail_z_plus_y[0])
-                                       angle = 180;
-                               if(is_rail_x_plus_y[0])
-                                       angle = 90;
-                               if(is_rail_x_plus_y[1])
-                                       angle = -90;
-                               break;
-                       default:
-                               break;
-                       }
+void MapblockMeshGenerator::drawGlasslikeNode()
+{
+       useTile(0, 0, 0);
+
+       for (int face = 0; face < 6; face++) {
+               // Check this neighbor
+               v3s16 dir = g_6dirs[face];
+               v3s16 neighbor_pos = blockpos_nodes + p + dir;
+               MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
+               // Don't make face if neighbor is of same type
+               if (neighbor.getContent() == n.getContent())
+                       continue;
+               // Face at Z-
+               v3f vertices[4] = {
+                       v3f(-BS / 2,  BS / 2, -BS / 2),
+                       v3f( BS / 2,  BS / 2, -BS / 2),
+                       v3f( BS / 2, -BS / 2, -BS / 2),
+                       v3f(-BS / 2, -BS / 2, -BS / 2),
+               };
 
-                       TileSpec tile = getNodeTileN(n, p, tileindex, data);
-                       tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING;
-                       tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY;
-
-                       AtlasPointer ap = tile.texture;
-                       
-                       u16 l = getInteriorLight(n, 0, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       float d = (float)BS/64;
-                       
-                       char g=-1;
-                       if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1])
-                               g=1; //Object is at a slope
-
-                       video::S3DVertex vertices[4] =
-                       {
-                                       video::S3DVertex(-BS/2,-BS/2+d,-BS/2, 0,0,0, c,
-                                                       ap.x0(), ap.y1()),
-                                       video::S3DVertex(BS/2,-BS/2+d,-BS/2, 0,0,0, c,
-                                                       ap.x1(), ap.y1()),
-                                       video::S3DVertex(BS/2,g*BS/2+d,BS/2, 0,0,0, c,
-                                                       ap.x1(), ap.y0()),
-                                       video::S3DVertex(-BS/2,g*BS/2+d,BS/2, 0,0,0, c,
-                                                       ap.x0(), ap.y0()),
-                       };
-
-                       for(s32 i=0; i<4; i++)
-                       {
-                               if(angle != 0)
-                                       vertices[i].Pos.rotateXZBy(angle);
-                               vertices[i].Pos += intToFloat(p, BS);
+               for (auto &vertice : vertices) {
+                       switch (face) {
+                               case D6D_ZP:
+                                       vertice.rotateXZBy(180); break;
+                               case D6D_YP:
+                                       vertice.rotateYZBy( 90); break;
+                               case D6D_XP:
+                                       vertice.rotateXZBy( 90); break;
+                               case D6D_ZN:
+                                       vertice.rotateXZBy(  0); break;
+                               case D6D_YN:
+                                       vertice.rotateYZBy(-90); break;
+                               case D6D_XN:
+                                       vertice.rotateXZBy(-90); break;
                        }
+               }
+               drawQuad(vertices, dir);
+       }
+}
 
-                       u16 indices[] = {0,1,2,2,3,0};
-                       collector.append(tile, vertices, 4, indices, 6);
-               break;}
-               case NDT_NODEBOX:
-               {
-                       static const v3s16 tile_dirs[6] = {
-                               v3s16(0, 1, 0),
-                               v3s16(0, -1, 0),
-                               v3s16(1, 0, 0),
-                               v3s16(-1, 0, 0),
-                               v3s16(0, 0, 1),
-                               v3s16(0, 0, -1)
-                       };
-
-                       TileSpec tiles[6];
-                       for(int i = 0; i < 6; i++)
-                       {
-                               // Handles facedir rotation for textures
-                               tiles[i] = getNodeTile(n, p, tile_dirs[i], data);
-                       }
+void MapblockMeshGenerator::drawGlasslikeFramedNode()
+{
+       TileSpec tiles[6];
+       for (int face = 0; face < 6; face++)
+               getTile(g_6dirs[face], &tiles[face]);
+
+       TileSpec glass_tiles[6];
+       if (tiles[1].layers[0].texture &&
+                       tiles[2].layers[0].texture &&
+                       tiles[3].layers[0].texture) {
+               glass_tiles[0] = tiles[4];
+               glass_tiles[1] = tiles[0];
+               glass_tiles[2] = tiles[4];
+               glass_tiles[3] = tiles[4];
+               glass_tiles[4] = tiles[3];
+               glass_tiles[5] = tiles[4];
+       } else {
+               for (auto &glass_tile : glass_tiles)
+                       glass_tile = tiles[4];
+       }
+
+       u8 param2 = n.getParam2();
+       bool H_merge = !(param2 & 128);
+       bool V_merge = !(param2 & 64);
+       param2 &= 63;
+
+       static const float a = BS / 2;
+       static const float g = a - 0.003;
+       static const float b = .876 * ( BS / 2 );
+
+       static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
+               aabb3f( b,  b, -a,  a,  a,  a), // y+
+               aabb3f(-a,  b, -a, -b,  a,  a), // y+
+               aabb3f( b, -a, -a,  a, -b,  a), // y-
+               aabb3f(-a, -a, -a, -b, -b,  a), // y-
+               aabb3f( b, -a,  b,  a,  a,  a), // x+
+               aabb3f( b, -a, -a,  a,  a, -b), // x+
+               aabb3f(-a, -a,  b, -b,  a,  a), // x-
+               aabb3f(-a, -a, -a, -b,  a, -b), // x-
+               aabb3f(-a,  b,  b,  a,  a,  a), // z+
+               aabb3f(-a, -a,  b,  a, -b,  a), // z+
+               aabb3f(-a, -a, -a,  a, -b, -b), // z-
+               aabb3f(-a,  b, -a,  a,  a, -b), // z-
+       };
+       static const aabb3f glass_faces[6] = {
+               aabb3f(-g, -g,  g,  g,  g,  g), // z+
+               aabb3f(-g,  g, -g,  g,  g,  g), // y+
+               aabb3f( g, -g, -g,  g,  g,  g), // x+
+               aabb3f(-g, -g, -g,  g,  g, -g), // z-
+               aabb3f(-g, -g, -g,  g, -g,  g), // y-
+               aabb3f(-g, -g, -g, -g,  g,  g), // x-
+       };
+
+       // tables of neighbour (connect if same type and merge allowed),
+       // checked with g_26dirs
+
+       // 1 = connect, 0 = face visible
+       bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+
+       // 1 = check
+       static const bool check_nb_vertical   [FRAMED_NEIGHBOR_COUNT] = {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
+       static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] = {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
+       static const bool check_nb_all        [FRAMED_NEIGHBOR_COUNT] = {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
+       const bool *check_nb = check_nb_all;
+
+       // neighbours checks for frames visibility
+       if (H_merge || V_merge) {
+               if (!H_merge)
+                       check_nb = check_nb_vertical; // vertical-only merge
+               if (!V_merge)
+                       check_nb = check_nb_horizontal; // horizontal-only merge
+               content_t current = n.getContent();
+               for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
+                       if (!check_nb[i])
+                               continue;
+                       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)
+                               nb[i] = 1;
+               }
+       }
+
+       // edge visibility
+
+       static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
+               {1, 2,  7}, {1, 5,  6}, {4, 2, 15}, {4, 5, 14},
+               {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
+               {0, 1,  8}, {0, 4, 16}, {3, 4, 17}, {3, 1,  9},
+       };
+
+       tile = tiles[1];
+       for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
+               bool edge_invisible;
+               if (nb[nb_triplet[edge][2]])
+                       edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
+               else
+                       edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
+               if (edge_invisible)
+                       continue;
+               drawAutoLightedCuboid(frame_edges[edge]);
+       }
+
+       for (int face = 0; face < 6; face++) {
+               if (nb[face])
+                       continue;
+               tile = glass_tiles[face];
+               drawAutoLightedCuboid(glass_faces[face]);
+       }
+
+       // Optionally render internal liquid level defined by param2
+       // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
+       if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
+                       f->special_tiles[0].layers[0].texture) {
+               // Internal liquid level has param2 range 0 .. 63,
+               // convert it to -0.5 .. 0.5
+               float vlev = (param2 / 63.0) * 2.0 - 1.0;
+               getSpecialTile(0, &tile);
+               drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
+                                            -(nb[4] ? g : b),
+                                            -(nb[3] ? g : b),
+                                             (nb[2] ? g : b),
+                                             (nb[1] ? g : b) * vlev,
+                                             (nb[0] ? g : b)));
+       }
+}
 
-                       u16 l = getInteriorLight(n, 0, data);
-                       video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source));
-
-                       v3f pos = intToFloat(p, BS);
-
-                       std::vector<aabb3f> boxes = n.getNodeBoxes(nodedef);
-                       for(std::vector<aabb3f>::iterator
-                                       i = boxes.begin();
-                                       i != boxes.end(); i++)
-                       {
-                               aabb3f box = *i;
-                               box.MinEdge += pos;
-                               box.MaxEdge += pos;
-
-                               // Compute texture coords
-                               f32 tx1 = (i->MinEdge.X/BS)+0.5;
-                               f32 ty1 = (i->MinEdge.Y/BS)+0.5;
-                               f32 tz1 = (i->MinEdge.Z/BS)+0.5;
-                               f32 tx2 = (i->MaxEdge.X/BS)+0.5;
-                               f32 ty2 = (i->MaxEdge.Y/BS)+0.5;
-                               f32 tz2 = (i->MaxEdge.Z/BS)+0.5;
-                               f32 txc[24] = {
-                                       // up
-                                       tx1, 1-tz2, tx2, 1-tz1,
-                                       // down
-                                       tx1, tz1, tx2, tz2,
-                                       // right
-                                       tz1, 1-ty2, tz2, 1-ty1,
-                                       // left
-                                       1-tz2, 1-ty2, 1-tz1, 1-ty1,
-                                       // back
-                                       1-tx2, 1-ty2, 1-tx1, 1-ty1,
-                                       // front
-                                       tx1, 1-ty2, tx2, 1-ty1,
-                               };
-
-                               makeCuboid(&collector, box, tiles, 6, c, txc);
+void MapblockMeshGenerator::drawAllfacesNode()
+{
+       static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
+       useTile(0, 0, 0);
+       drawAutoLightedCuboid(box);
+}
+
+void MapblockMeshGenerator::drawTorchlikeNode()
+{
+       u8 wall = n.getWallMounted(nodedef);
+       u8 tileindex = 0;
+       switch (wall) {
+               case DWM_YP: tileindex = 1; break; // ceiling
+               case DWM_YN: tileindex = 0; break; // floor
+               default:     tileindex = 2; // side (or invalid—should we care?)
+       }
+       useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
+
+       float size = BS / 2 * f->visual_scale;
+       v3f vertices[4] = {
+               v3f(-size,  size, 0),
+               v3f( size,  size, 0),
+               v3f( size, -size, 0),
+               v3f(-size, -size, 0),
+       };
+
+       for (auto &vertice : vertices) {
+               switch (wall) {
+                       case DWM_YP:
+                               vertice.rotateXZBy(-45); break;
+                       case DWM_YN:
+                               vertice.rotateXZBy( 45); break;
+                       case DWM_XP:
+                               vertice.rotateXZBy(  0); break;
+                       case DWM_XN:
+                               vertice.rotateXZBy(180); break;
+                       case DWM_ZP:
+                               vertice.rotateXZBy( 90); break;
+                       case DWM_ZN:
+                               vertice.rotateXZBy(-90); break;
+               }
+       }
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawSignlikeNode()
+{
+       u8 wall = n.getWallMounted(nodedef);
+       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
+       v3f vertices[4] = {
+               v3f(BS / 2 - offset,  size,  size),
+               v3f(BS / 2 - offset,  size, -size),
+               v3f(BS / 2 - offset, -size, -size),
+               v3f(BS / 2 - offset, -size,  size),
+       };
+
+       for (auto &vertice : vertices) {
+               switch (wall) {
+                       case DWM_YP:
+                               vertice.rotateXYBy( 90); break;
+                       case DWM_YN:
+                               vertice.rotateXYBy(-90); break;
+                       case DWM_XP:
+                               vertice.rotateXZBy(  0); break;
+                       case DWM_XN:
+                               vertice.rotateXZBy(180); break;
+                       case DWM_ZP:
+                               vertice.rotateXZBy( 90); break;
+                       case DWM_ZN:
+                               vertice.rotateXZBy(-90); break;
+               }
+       }
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
+       bool offset_top_only)
+{
+       v3f vertices[4] = {
+               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);
+       }
+       int offset_count = offset_top_only ? 2 : 4;
+       for (int i = 0; i < offset_count; i++)
+               vertices[i].Z += quad_offset;
+
+       for (auto &vertice : vertices) {
+               vertice.rotateXZBy(rotation + rotate_degree);
+               vertice += offset;
+       }
+       drawQuad(vertices, v3s16(0, 0, 0), plant_height);
+}
+
+void MapblockMeshGenerator::drawPlantlike()
+{
+       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:
+               draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
+               if (n.param2 & MO_BIT_SCALE_SQRT2)
+                       scale *= 1.41421;
+               if (n.param2 & MO_BIT_RANDOM_OFFSET) {
+                       PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
+                       offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
+                       offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
+               }
+               if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
+                       random_offset_Y = true;
+               break;
+
+       case CPT2_DEGROTATE:
+               rotate_degree = n.param2 * 2;
+               break;
+
+       case CPT2_LEVELED:
+               plant_height = n.param2 / 16.0;
+               break;
+
+       default:
+               break;
+       }
+
+       switch (draw_style) {
+       case PLANT_STYLE_CROSS:
+               drawPlantlikeQuad(46);
+               drawPlantlikeQuad(-44);
+               break;
+
+       case PLANT_STYLE_CROSS2:
+               drawPlantlikeQuad(91);
+               drawPlantlikeQuad(1);
+               break;
+
+       case PLANT_STYLE_STAR:
+               drawPlantlikeQuad(121);
+               drawPlantlikeQuad(241);
+               drawPlantlikeQuad(1);
+               break;
+
+       case PLANT_STYLE_HASH:
+               drawPlantlikeQuad(  1, BS / 4);
+               drawPlantlikeQuad( 91, BS / 4);
+               drawPlantlikeQuad(181, BS / 4);
+               drawPlantlikeQuad(271, BS / 4);
+               break;
+
+       case PLANT_STYLE_HASH2:
+               drawPlantlikeQuad(  1, -BS / 2, true);
+               drawPlantlikeQuad( 91, -BS / 2, true);
+               drawPlantlikeQuad(181, -BS / 2, true);
+               drawPlantlikeQuad(271, -BS / 2, true);
+               break;
+       }
+}
+
+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 = getInteriorLight(ntop, 1, nodedef);
+       }
+       drawPlantlike();
+       p.Y--;
+}
+
+void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
+       float offset_h, float offset_v)
+{
+       v3f vertices[4] = {
+               v3f(-scale, -BS / 2 + scale * 2, 0),
+               v3f( scale, -BS / 2 + scale * 2, 0),
+               v3f( scale, -BS / 2, 0),
+               v3f(-scale, -BS / 2, 0),
+       };
+
+       for (auto &vertice : vertices) {
+               vertice.rotateYZBy(opening_angle);
+               vertice.Z += offset_h;
+               vertice.rotateXZBy(rotation);
+               vertice.Y += offset_v;
+       }
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawFirelikeNode()
+{
+       useTile();
+       scale = BS / 2 * f->visual_scale;
+
+       // Check for adjacent nodes
+       bool neighbors = false;
+       bool neighbor[6] = {0, 0, 0, 0, 0, 0};
+       content_t current = n.getContent();
+       for (int i = 0; i < 6; i++) {
+               v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
+               MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
+               content_t n2c = n2.getContent();
+               if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
+                       neighbor[i] = true;
+                       neighbors = true;
+               }
+       }
+       bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
+       bool drawBottomFire = neighbor[D6D_YP];
+
+       if (drawBasicFire || neighbor[D6D_ZP])
+               drawFirelikeQuad(0, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire || neighbor[D6D_XN])
+               drawFirelikeQuad(90, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire || neighbor[D6D_ZN])
+               drawFirelikeQuad(180, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire || neighbor[D6D_XP])
+               drawFirelikeQuad(270, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire) {
+               drawFirelikeQuad(45, 0, 0.0);
+               drawFirelikeQuad(-45, 0, 0.0);
+       }
+}
+
+void MapblockMeshGenerator::drawFencelikeNode()
+{
+       useTile(0, 0, 0);
+       TileSpec tile_nocrack = tile;
+
+       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;
+       tile_rot.rotation = 1;
+
+       static const f32 post_rad = BS / 8;
+       static const f32 bar_rad  = BS / 16;
+       static const f32 bar_len  = BS / 2 - post_rad;
+
+       // The post - always present
+       static const aabb3f post(-post_rad, -BS / 2, -post_rad,
+                                 post_rad,  BS / 2,  post_rad);
+       static const f32 postuv[24] = {
+               0.375, 0.375, 0.625, 0.625,
+               0.375, 0.375, 0.625, 0.625,
+               0.000, 0.000, 0.250, 1.000,
+               0.250, 0.000, 0.500, 1.000,
+               0.500, 0.000, 0.750, 1.000,
+               0.750, 0.000, 1.000, 1.000,
+       };
+       tile = tile_rot;
+       drawAutoLightedCuboid(post, postuv);
+
+       tile = tile_nocrack;
+
+       // Now a section of fence, +X, if there's a post there
+       v3s16 p2 = p;
+       p2.X++;
+       MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+       const ContentFeatures *f2 = &nodedef->get(n2);
+       if (f2->drawtype == NDT_FENCELIKE) {
+               static const aabb3f bar_x1(BS / 2 - bar_len,  BS / 4 - bar_rad, -bar_rad,
+                                          BS / 2 + bar_len,  BS / 4 + bar_rad,  bar_rad);
+               static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
+                                          BS / 2 + bar_len, -BS / 4 + bar_rad,  bar_rad);
+               static const f32 xrailuv[24] = {
+                       0.000, 0.125, 1.000, 0.250,
+                       0.000, 0.250, 1.000, 0.375,
+                       0.375, 0.375, 0.500, 0.500,
+                       0.625, 0.625, 0.750, 0.750,
+                       0.000, 0.500, 1.000, 0.625,
+                       0.000, 0.875, 1.000, 1.000,
+               };
+               drawAutoLightedCuboid(bar_x1, xrailuv);
+               drawAutoLightedCuboid(bar_x2, xrailuv);
+       }
+
+       // Now a section of fence, +Z, if there's a post there
+       p2 = p;
+       p2.Z++;
+       n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+       f2 = &nodedef->get(n2);
+       if (f2->drawtype == NDT_FENCELIKE) {
+               static const aabb3f bar_z1(-bar_rad,  BS / 4 - bar_rad, BS / 2 - bar_len,
+                                           bar_rad,  BS / 4 + bar_rad, BS / 2 + bar_len);
+               static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
+                                           bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
+               static const f32 zrailuv[24] = {
+                       0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
+                       0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
+                       0.0000, 0.5625, 1.0000, 0.6875,
+                       0.0000, 0.3750, 1.0000, 0.5000,
+                       0.3750, 0.3750, 0.5000, 0.5000,
+                       0.6250, 0.6250, 0.7500, 0.7500,
+               };
+               drawAutoLightedCuboid(bar_z1, zrailuv);
+               drawAutoLightedCuboid(bar_z2, zrailuv);
+       }
+}
+
+bool MapblockMeshGenerator::isSameRail(v3s16 dir)
+{
+       MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
+       if (node2.getContent() == n.getContent())
+               return true;
+       const ContentFeatures &def2 = nodedef->get(node2);
+       return ((def2.drawtype == NDT_RAILLIKE) &&
+               (def2.getGroup(raillike_groupname) == raillike_group));
+}
+
+void MapblockMeshGenerator::drawRaillikeNode()
+{
+       static const v3s16 direction[4] = {
+               v3s16( 0, 0,  1),
+               v3s16( 0, 0, -1),
+               v3s16(-1, 0,  0),
+               v3s16( 1, 0,  0),
+       };
+       static const int slope_angle[4] = {0, 180, 90, -90};
+
+       enum RailTile {
+               straight,
+               curved,
+               junction,
+               cross,
+       };
+       struct RailDesc {
+               int tile_index;
+               int angle;
+       };
+       static const RailDesc rail_kinds[16] = {
+                                  // +x -x -z +z
+                                  //-------------
+               {straight,   0}, //  .  .  .  .
+               {straight,   0}, //  .  .  . +Z
+               {straight,   0}, //  .  . -Z  .
+               {straight,   0}, //  .  . -Z +Z
+               {straight,  90}, //  . -X  .  .
+               {  curved, 180}, //  . -X  . +Z
+               {  curved, 270}, //  . -X -Z  .
+               {junction, 180}, //  . -X -Z +Z
+               {straight,  90}, // +X  .  .  .
+               {  curved,  90}, // +X  .  . +Z
+               {  curved,   0}, // +X  . -Z  .
+               {junction,   0}, // +X  . -Z +Z
+               {straight,  90}, // +X -X  .  .
+               {junction,  90}, // +X -X  . +Z
+               {junction, 270}, // +X -X -Z  .
+               {   cross,   0}, // +X -X -Z +Z
+       };
+
+       raillike_group = nodedef->get(n).getGroup(raillike_groupname);
+
+       int code = 0;
+       int angle;
+       int tile_index;
+       bool sloped = false;
+       for (int dir = 0; dir < 4; dir++) {
+               bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0));
+               if (rail_above) {
+                       sloped = true;
+                       angle = slope_angle[dir];
+               }
+               if (rail_above ||
+                               isSameRail(direction[dir]) ||
+                               isSameRail(direction[dir] + v3s16(0, -1, 0)))
+                       code |= 1 << dir;
+       }
+
+       if (sloped) {
+               tile_index = straight;
+       } else {
+               tile_index = rail_kinds[code].tile_index;
+               angle = rail_kinds[code].angle;
+       }
+
+       useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
+
+       static const float offset = BS / 64;
+       static const float size   = BS / 2;
+       float y2 = sloped ? size : -size;
+       v3f vertices[4] = {
+               v3f(-size,    y2 + offset,  size),
+               v3f( size,    y2 + offset,  size),
+               v3f( size, -size + offset, -size),
+               v3f(-size, -size + offset, -size),
+       };
+       if (angle)
+               for (auto &vertice : vertices)
+                       vertice.rotateXZBy(angle);
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawNodeboxNode()
+{
+       static const v3s16 tile_dirs[6] = {
+               v3s16(0, 1, 0),
+               v3s16(0, -1, 0),
+               v3s16(1, 0, 0),
+               v3s16(-1, 0, 0),
+               v3s16(0, 0, 1),
+               v3s16(0, 0, -1)
+       };
+
+       // we have this order for some reason...
+       static const v3s16 connection_dirs[6] = {
+               v3s16( 0,  1,  0), // top
+               v3s16( 0, -1,  0), // bottom
+               v3s16( 0,  0, -1), // front
+               v3s16(-1,  0,  0), // left
+               v3s16( 0,  0,  1), // back
+               v3s16( 1,  0,  0), // right
+       };
+
+       TileSpec tiles[6];
+       for (int face = 0; face < 6; face++) {
+               // Handles facedir rotation for textures
+               getTile(tile_dirs[face], &tiles[face]);
+       }
+
+       // locate possible neighboring nodes to connect to
+       int neighbors_set = 0;
+       if (f->node_box.type == NODEBOX_CONNECTED) {
+               for (int dir = 0; dir != 6; dir++) {
+                       int flag = 1 << dir;
+                       v3s16 p2 = blockpos_nodes + p + connection_dirs[dir];
+                       MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
+                       if (nodedef->nodeboxConnects(n, n2, flag))
+                               neighbors_set |= flag;
+               }
+       }
+
+       std::vector<aabb3f> boxes;
+       n.getNodeBoxes(nodedef, &boxes, neighbors_set);
+       for (const auto &box : boxes)
+               drawAutoLightedCuboid(box, NULL, tiles, 6);
+}
+
+void MapblockMeshGenerator::drawMeshNode()
+{
+       u8 facedir = 0;
+       scene::IMesh* mesh;
+       bool private_mesh; // as a grab/drop pair is not thread-safe
+
+       if (f->param_type_2 == CPT2_FACEDIR ||
+                       f->param_type_2 == CPT2_COLORED_FACEDIR) {
+               facedir = n.getFaceDir(nodedef);
+       } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
+                       f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
+               // 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 (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {
+               // use cached meshes
+               private_mesh = false;
+               mesh = f->mesh_ptr[facedir];
+       } else if (f->mesh_ptr[0]) {
+               // no cache, clone and rotate mesh
+               private_mesh = true;
+               mesh = cloneMesh(f->mesh_ptr[0]);
+               rotateMeshBy6dFacedir(mesh, facedir);
+               recalculateBoundingBox(mesh);
+               meshmanip->recalculateNormals(mesh, true, false);
+       } else
+               return;
+
+       int mesh_buffer_count = mesh->getMeshBufferCount();
+       for (int j = 0; j < mesh_buffer_count; j++) {
+               useTile(j);
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
+               int vertex_count = buf->getVertexCount();
+
+               if (data->m_smooth_lighting) {
+                       // Mesh is always private here. So the lighting is applied to each
+                       // vertex right here.
+                       for (int k = 0; k < vertex_count; k++) {
+                               video::S3DVertex &vertex = vertices[k];
+                               vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
+                               vertex.Pos += origin;
                        }
-               break;}
+                       collector->append(tile, vertices, vertex_count,
+                               buf->getIndices(), buf->getIndexCount());
+               } else {
+                       // Don't modify the mesh, it may not be private here.
+                       // Instead, let the collector process colors, etc.
+                       collector->append(tile, vertices, vertex_count,
+                               buf->getIndices(), buf->getIndexCount(), origin,
+                               color, f->light_source);
                }
        }
+       if (private_mesh)
+               mesh->drop();
+}
+
+// also called when the drawtype is known but should have been pre-converted
+void MapblockMeshGenerator::errorUnknownDrawtype()
+{
+       infostream << "Got drawtype " << f->drawtype << std::endl;
+       FATAL_ERROR("Unknown drawtype");
 }
 
+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);
+       switch (f->drawtype) {
+               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;
+               case NDT_NODEBOX:           drawNodeboxNode(); break;
+               case NDT_MESH:              drawMeshNode(); break;
+               default:                    errorUnknownDrawtype(); break;
+       }
+}
+
+/*
+       TODO: Fix alpha blending for special nodes
+       Currently only the last element rendered is blended correct
+*/
+void MapblockMeshGenerator::generate()
+{
+       for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
+       for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
+       for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
+               n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
+               f = &nodedef->get(n);
+               drawNode();
+       }
+}