+ // 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)));
+ }
+}
+
+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;