diff options
Diffstat (limited to 'src/client/content_mapblock.cpp')
-rw-r--r-- | src/client/content_mapblock.cpp | 1430 |
1 files changed, 1430 insertions, 0 deletions
diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp new file mode 100644 index 000000000..4a0df6171 --- /dev/null +++ b/src/client/content_mapblock.cpp @@ -0,0 +1,1430 @@ +/* +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 +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "content_mapblock.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/meshgen/collector.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 + getTile(index, &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; + } +} + +// Returns a tile, ready for use, non-rotated. +void MapblockMeshGenerator::getTile(int index, TileSpec *tile) +{ + getNodeTileN(n, p, index, data, *tile); +} + +// Returns a tile, ready for use, rotated according to the node facedir. +void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile) +{ + getNodeTile(n, p, direction, data, *tile); +} + +// Returns a special tile, ready for use, non-rotated. +void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack) +{ + *tile = f->special_tiles[index]; + TileLayer *top_layer = nullptr; + + for (auto &layernum : tile->layers) { + TileLayer *layer = &layernum; + if (layer->texture_id == 0) + continue; + top_layer = layer; + if (!layer->has_color) + n.getColor(*f, &layer->color); + } + + if (apply_crack) + top_layer->material_flags |= MATERIAL_FLAG_CRACK; +} + +void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, + float vertical_tiling) +{ + const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0), + v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)}; + video::S3DVertex vertices[4]; + bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0)); + v3f normal2(normal.X, normal.Y, normal.Z); + 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. +// tiles - the tiles (materials) to use (for all 6 faces) +// tilecount - number of entries in tiles, 1<=tilecount<=6 +// 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. The order of +// the faces in the list is up-down-right-left-back-front +// (compatible with ContentFeatures). +void MapblockMeshGenerator::drawCuboid(const aabb3f &box, + TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) +{ + assert(tilecount >= 1 && tilecount <= 6); // pre-condition + + v3f min = box.MinEdge; + v3f max = box.MaxEdge; + + 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] = { + // 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, 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, 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, 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, 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]), + }; + + 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; + } + } + } + + if (data->m_smooth_lighting) { + for (int j = 0; j < 24; ++j) { + video::S3DVertex &vertex = vertices[j]; + vertex.Color = encode_light( + lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)), + f->light_source); + if (!f->light_source) + applyFacesShading(vertex.Color, vertex.Normal); + } + } + + // Add to mesh collector + 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) + frame.sunlight[k] = false; + for (int k = 0; k < 8; ++k) { + LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data)); + frame.lightsDay[k] = light.lightDay; + frame.lightsNight[k] = light.lightNight; + // If there is direct sunlight and no ambient occlusion at some corner, + // mark the vertical edge (top and bottom corners) containing it. + if (light.lightDay == 255) { + frame.sunlight[k] = true; + frame.sunlight[k ^ 2] = true; + } + } +} + +// Calculates vertex light level +// vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) +LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos) +{ + // Light levels at (logical) node corners are known. Here, + // trilinear interpolation is used to calculate light level + // at a given point in the node. + f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); + f32 lightDay = 0.0; // daylight + f32 lightNight = 0.0; + f32 lightBoosted = 0.0; // daylight + direct sunlight, if any + for (int k = 0; k < 8; ++k) { + f32 dx = (k & 4) ? x : 1 - x; + f32 dy = (k & 2) ? y : 1 - y; + f32 dz = (k & 1) ? z : 1 - z; + // Use direct sunlight (255), if any; use daylight otherwise. + f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k]; + lightDay += dx * dy * dz * frame.lightsDay[k]; + lightNight += dx * dy * dz * frame.lightsNight[k]; + lightBoosted += dx * dy * dz * light_boosted; + } + return LightInfo{lightDay, lightNight, lightBoosted}; +} + +// 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) +{ + LightInfo light = blendLight(vertex_pos); + return encode_light(light.getPair(), f->light_source); +} + +video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, + const v3f &vertex_normal) +{ + LightInfo light = blendLight(vertex_pos); + video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source); + if (!f->light_source) + applyFacesShading(color, vertex_normal); + return color; +} + +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) { + LightInfo 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, nullptr, txc); + } +} + +void MapblockMeshGenerator::prepareLiquidNodeDrawing() +{ + getSpecialTile(0, &tile_liquid_top); + getSpecialTile(1, &tile_liquid); + + MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z)); + MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z)); + c_flowing = nodedef->getId(f->liquid_alternative_flowing); + c_source = nodedef->getId(f->liquid_alternative_source); + top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source); + draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source); + if (draw_liquid_bottom) { + const ContentFeatures &f2 = nodedef->get(nbottom.getContent()); + if (f2.solidness > 1) + draw_liquid_bottom = false; + } + + if (data->m_smooth_lighting) + return; // don't need to pre-compute anything in this case + + 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 + u8 e = decode_light(f->light_source); + light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight)); + } else if (nodedef->get(ntop).param_type == CPT_LIGHT) { + // Otherwise, use the light of the node on top if possible + light = LightPair(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; + + 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; + } + + const ContentFeatures &neighbor_features = nodedef->get(neighbor.content); + // Don't draw face if neighbor is blocking the view + if (neighbor_features.solidness == 2) + continue; + + 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 + 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); + } +} + +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), + }; + + 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; + } + + // Default downwards-flowing texture animation goes from + // -Z towards +Z, thus the direction is +Z. + // Rotate texture to make animation go in flow direction + // Positive if liquid moves towards +Z + f32 dz = (corner_levels[0][0] + corner_levels[0][1]) - + (corner_levels[1][0] + corner_levels[1][1]); + // Positive if liquid moves towards +X + f32 dx = (corner_levels[0][0] + corner_levels[1][0]) - + (corner_levels[0][1] + corner_levels[1][1]); + f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG; + v2f tcoord_center(0.5, 0.5); + v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X); + tcoord_translate.rotateBy(tcoord_angle); + tcoord_translate.X -= floor(tcoord_translate.X); + tcoord_translate.Y -= floor(tcoord_translate.Y); + + for (video::S3DVertex &vertex : vertices) { + vertex.TCoords.rotateBy(tcoord_angle, tcoord_center); + vertex.TCoords += tcoord_translate; + } + + std::swap(vertices[0].TCoords, vertices[2].TCoords); + + collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); +} + +void MapblockMeshGenerator::drawLiquidBottom() +{ + video::S3DVertex vertices[4] = { + video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), + video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), + video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), + video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), + }; + + for (int i = 0; i < 4; i++) { + if (data->m_smooth_lighting) + vertices[i].Color = blendLightColor(vertices[i].Pos); + vertices[i].Pos += origin; + } + + collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); +} + +void MapblockMeshGenerator::drawLiquidNode() +{ + prepareLiquidNodeDrawing(); + getLiquidNeighborhood(); + calculateCornerLevels(); + drawLiquidSides(); + if (!top_is_same_liquid) + drawLiquidTop(); + if (draw_liquid_bottom) + drawLiquidBottom(); +} + +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), + }; + + for (v3f &vertex : vertices) { + switch (face) { + case D6D_ZP: + vertex.rotateXZBy(180); break; + case D6D_YP: + vertex.rotateYZBy( 90); break; + case D6D_XP: + vertex.rotateXZBy( 90); break; + case D6D_ZN: + vertex.rotateXZBy( 0); break; + case D6D_YN: + vertex.rotateYZBy(-90); break; + case D6D_XN: + vertex.rotateXZBy(-90); break; + } + } + drawQuad(vertices, dir); + } +} + +void MapblockMeshGenerator::drawGlasslikeFramedNode() +{ + TileSpec tiles[6]; + for (int face = 0; face < 6; face++) + getTile(g_6dirs[face], &tiles[face]); + + if (!data->m_smooth_lighting) + color = encode_light(light, f->light_source); + + TileSpec glass_tiles[6]; + 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.0f; + static const float g = a - 0.03f; + static const float b = 0.876f * (BS / 2.0f); + + 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- + }; + + // 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) + 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]; + // Face at Z- + v3f vertices[4] = { + v3f(-a, a, -g), + v3f( a, a, -g), + v3f( a, -a, -g), + v3f(-a, -a, -g), + }; + + for (v3f &vertex : vertices) { + switch (face) { + case D6D_ZP: + vertex.rotateXZBy(180); break; + case D6D_YP: + vertex.rotateYZBy( 90); break; + case D6D_XP: + vertex.rotateXZBy( 90); break; + case D6D_ZN: + vertex.rotateXZBy( 0); break; + case D6D_YN: + vertex.rotateYZBy(-90); break; + case D6D_XN: + vertex.rotateXZBy(-90); break; + } + } + v3s16 dir = g_6dirs[face]; + drawQuad(vertices, dir); + } + + // 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.0f) * 2.0f - 1.0f; + 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 (v3f &vertex : vertices) { + switch (wall) { + case DWM_YP: + vertex.rotateXZBy(-45); break; + case DWM_YN: + vertex.rotateXZBy( 45); break; + case DWM_XP: + vertex.rotateXZBy( 0); break; + case DWM_XN: + vertex.rotateXZBy(180); break; + case DWM_ZP: + vertex.rotateXZBy( 90); break; + case DWM_ZN: + vertex.rotateXZBy(-90); break; + } + } + drawQuad(vertices); +} + +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 (v3f &vertex : vertices) { + switch (wall) { + case DWM_YP: + vertex.rotateXYBy( 90); break; + case DWM_YN: + vertex.rotateXYBy(-90); break; + case DWM_XP: + vertex.rotateXZBy( 0); break; + case DWM_XN: + vertex.rotateXZBy(180); break; + case DWM_ZP: + vertex.rotateXZBy( 90); break; + case DWM_ZN: + vertex.rotateXZBy(-90); break; + } + } + drawQuad(vertices); +} + +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 (v3f &vertex : vertices) { + vertex.rotateXZBy(rotation + rotate_degree); + vertex += 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 = LightPair(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 (v3f &vertex : vertices) { + vertex.rotateYZBy(opening_angle); + vertex.Z += offset_h; + vertex.rotateXZBy(rotation); + vertex.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 (v3f &vertex : vertices) + vertex.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, nullptr, 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) + facedir = wallmounted_to_facedir[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; + } + 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 = LightPair(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(); + } +} + +void MapblockMeshGenerator::renderSingle(content_t node) +{ + p = {0, 0, 0}; + n = MapNode(node, 0xff, 0x00); + f = &nodedef->get(n); + drawNode(); +} |