diff options
Diffstat (limited to 'src/client/mapblock_mesh.cpp')
-rw-r--r-- | src/client/mapblock_mesh.cpp | 1389 |
1 files changed, 1389 insertions, 0 deletions
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp new file mode 100644 index 000000000..ed8a073de --- /dev/null +++ b/src/client/mapblock_mesh.cpp @@ -0,0 +1,1389 @@ +/* +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 "mapblock_mesh.h" +#include "client.h" +#include "mapblock.h" +#include "map.h" +#include "profiler.h" +#include "shader.h" +#include "mesh.h" +#include "minimap.h" +#include "content_mapblock.h" +#include "util/directiontables.h" +#include "client/meshgen/collector.h" +#include "client/renderingengine.h" +#include <array> + +/* + MeshMakeData +*/ + +MeshMakeData::MeshMakeData(Client *client, bool use_shaders, + bool use_tangent_vertices): + m_client(client), + m_use_shaders(use_shaders), + m_use_tangent_vertices(use_tangent_vertices) +{} + +void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) +{ + m_blockpos = blockpos; + + v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE; + + m_vmanip.clear(); + VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE, + blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1)); + m_vmanip.addArea(voxel_area); +} + +void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data) +{ + v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); + VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); + + v3s16 bp = m_blockpos + block_offset; + v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; + m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size); +} + +void MeshMakeData::fill(MapBlock *block) +{ + fillBlockDataBegin(block->getPos()); + + fillBlockData(v3s16(0,0,0), block->getData()); + + // Get map for reading neighbor blocks + Map *map = block->getParent(); + + for (const v3s16 &dir : g_26dirs) { + v3s16 bp = m_blockpos + dir; + MapBlock *b = map->getBlockNoCreateNoEx(bp); + if(b) + fillBlockData(dir, b->getData()); + } +} + +void MeshMakeData::fillSingleNode(MapNode *node) +{ + m_blockpos = v3s16(0,0,0); + + v3s16 blockpos_nodes = v3s16(0,0,0); + VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE, + blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1)); + s32 volume = area.getVolume(); + s32 our_node_index = area.index(1,1,1); + + // Allocate this block + neighbors + m_vmanip.clear(); + m_vmanip.addArea(area); + + // Fill in data + MapNode *data = new MapNode[volume]; + for(s32 i = 0; i < volume; i++) + { + if (i == our_node_index) + data[i] = *node; + else + data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0); + } + m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent()); + delete[] data; +} + +void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos) +{ + if (crack_level >= 0) + m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE; +} + +void MeshMakeData::setSmoothLighting(bool smooth_lighting) +{ + m_smooth_lighting = smooth_lighting; +} + +/* + Light and vertex color functions +*/ + +/* + Calculate non-smooth lighting at interior of node. + Single light bank. +*/ +static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment, + const NodeDefManager *ndef) +{ + u8 light = n.getLight(bank, ndef); + if (light > 0) + light = rangelim(light + increment, 0, LIGHT_SUN); + return decode_light(light); +} + +/* + Calculate non-smooth lighting at interior of node. + Both light banks. +*/ +u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef) +{ + u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef); + u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef); + return day | (night << 8); +} + +/* + Calculate non-smooth lighting at face of node. + Single light bank. +*/ +static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, + v3s16 face_dir, const NodeDefManager *ndef) +{ + u8 light; + u8 l1 = n.getLight(bank, ndef); + u8 l2 = n2.getLight(bank, ndef); + if(l1 > l2) + light = l1; + else + light = l2; + + // Boost light level for light sources + u8 light_source = MYMAX(ndef->get(n).light_source, + ndef->get(n2).light_source); + if(light_source > light) + light = light_source; + + return decode_light(light); +} + +/* + Calculate non-smooth lighting at face of node. + Both light banks. +*/ +u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir, + const NodeDefManager *ndef) +{ + u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef); + u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef); + return day | (night << 8); +} + +/* + Calculate smooth lighting at the XYZ- corner of p. + Both light banks +*/ +static u16 getSmoothLightCombined(const v3s16 &p, + const std::array<v3s16,8> &dirs, MeshMakeData *data) +{ + const NodeDefManager *ndef = data->m_client->ndef(); + + u16 ambient_occlusion = 0; + u16 light_count = 0; + u8 light_source_max = 0; + u16 light_day = 0; + u16 light_night = 0; + bool direct_sunlight = false; + + auto add_node = [&] (u8 i, bool obstructed = false) -> bool { + if (obstructed) { + ambient_occlusion++; + return false; + } + MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]); + if (n.getContent() == CONTENT_IGNORE) + return true; + const ContentFeatures &f = ndef->get(n); + if (f.light_source > light_source_max) + light_source_max = f.light_source; + // Check f.solidness because fast-style leaves look better this way + if (f.param_type == CPT_LIGHT && f.solidness != 2) { + u8 light_level_day = n.getLightNoChecks(LIGHTBANK_DAY, &f); + u8 light_level_night = n.getLightNoChecks(LIGHTBANK_NIGHT, &f); + if (light_level_day == LIGHT_SUN) + direct_sunlight = true; + light_day += decode_light(light_level_day); + light_night += decode_light(light_level_night); + light_count++; + } else { + ambient_occlusion++; + } + return f.light_propagates; + }; + + std::array<bool, 4> obstructed = {{ 1, 1, 1, 1 }}; + add_node(0); + bool opaque1 = !add_node(1); + bool opaque2 = !add_node(2); + bool opaque3 = !add_node(3); + obstructed[0] = opaque1 && opaque2; + obstructed[1] = opaque1 && opaque3; + obstructed[2] = opaque2 && opaque3; + for (u8 k = 0; k < 3; ++k) + if (add_node(k + 4, obstructed[k])) + obstructed[3] = false; + if (add_node(7, obstructed[3])) { // wrap light around nodes + ambient_occlusion -= 3; + for (u8 k = 0; k < 3; ++k) + add_node(k + 4, !obstructed[k]); + } + + if (light_count == 0) { + light_day = light_night = 0; + } else { + light_day /= light_count; + light_night /= light_count; + } + + // boost direct sunlight, if any + if (direct_sunlight) + light_day = 0xFF; + + // Boost brightness around light sources + bool skip_ambient_occlusion_day = false; + if (decode_light(light_source_max) >= light_day) { + light_day = decode_light(light_source_max); + skip_ambient_occlusion_day = true; + } + + bool skip_ambient_occlusion_night = false; + if(decode_light(light_source_max) >= light_night) { + light_night = decode_light(light_source_max); + skip_ambient_occlusion_night = true; + } + + if (ambient_occlusion > 4) { + static thread_local const float ao_gamma = rangelim( + g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0); + + // Table of gamma space multiply factors. + static thread_local const float light_amount[3] = { + powf(0.75, 1.0 / ao_gamma), + powf(0.5, 1.0 / ao_gamma), + powf(0.25, 1.0 / ao_gamma) + }; + + //calculate table index for gamma space multiplier + ambient_occlusion -= 5; + + if (!skip_ambient_occlusion_day) + light_day = rangelim(core::round32( + light_day * light_amount[ambient_occlusion]), 0, 255); + if (!skip_ambient_occlusion_night) + light_night = rangelim(core::round32( + light_night * light_amount[ambient_occlusion]), 0, 255); + } + + return light_day | (light_night << 8); +} + +/* + Calculate smooth lighting at the given corner of p. + Both light banks. + Node at p is solid, and thus the lighting is face-dependent. +*/ +u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data) +{ + return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data); +} + +/* + Calculate smooth lighting at the given corner of p. + Both light banks. + Node at p is not solid, and the lighting is not face-dependent. +*/ +u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data) +{ + const std::array<v3s16,8> dirs = {{ + // Always shine light + v3s16(0,0,0), + v3s16(corner.X,0,0), + v3s16(0,corner.Y,0), + v3s16(0,0,corner.Z), + + // Can be obstructed + v3s16(corner.X,corner.Y,0), + v3s16(corner.X,0,corner.Z), + v3s16(0,corner.Y,corner.Z), + v3s16(corner.X,corner.Y,corner.Z) + }}; + return getSmoothLightCombined(p, dirs, data); +} + +void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){ + f32 rg = daynight_ratio / 1000.0f - 0.04f; + f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f; + sunlight->r = rg; + sunlight->g = rg; + sunlight->b = b; +} + +void final_color_blend(video::SColor *result, + u16 light, u32 daynight_ratio) +{ + video::SColorf dayLight; + get_sunlight_color(&dayLight, daynight_ratio); + final_color_blend(result, + encode_light(light, 0), dayLight); +} + +void final_color_blend(video::SColor *result, + const video::SColor &data, const video::SColorf &dayLight) +{ + static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f); + + video::SColorf c(data); + f32 n = 1 - c.a; + + f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f; + f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f; + f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f; + + // Emphase blue a bit in darker places + // Each entry of this array represents a range of 8 blue levels + static const u8 emphase_blue_when_dark[32] = { + 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255), + 0, 255) / 8] / 255.0f; + + result->setRed(core::clamp((s32) (r * 255.0f), 0, 255)); + result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255)); + result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255)); +} + +/* + Mesh generation helpers +*/ + +/* + vertex_dirs: v3s16[4] +*/ +static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs) +{ + /* + If looked from outside the node towards the face, the corners are: + 0: bottom-right + 1: bottom-left + 2: top-left + 3: top-right + */ + if (dir == v3s16(0, 0, 1)) { + // If looking towards z+, this is the face that is behind + // the center point, facing towards z+. + vertex_dirs[0] = v3s16(-1,-1, 1); + vertex_dirs[1] = v3s16( 1,-1, 1); + vertex_dirs[2] = v3s16( 1, 1, 1); + vertex_dirs[3] = v3s16(-1, 1, 1); + } else if (dir == v3s16(0, 0, -1)) { + // faces towards Z- + vertex_dirs[0] = v3s16( 1,-1,-1); + vertex_dirs[1] = v3s16(-1,-1,-1); + vertex_dirs[2] = v3s16(-1, 1,-1); + vertex_dirs[3] = v3s16( 1, 1,-1); + } else if (dir == v3s16(1, 0, 0)) { + // faces towards X+ + vertex_dirs[0] = v3s16( 1,-1, 1); + vertex_dirs[1] = v3s16( 1,-1,-1); + vertex_dirs[2] = v3s16( 1, 1,-1); + vertex_dirs[3] = v3s16( 1, 1, 1); + } else if (dir == v3s16(-1, 0, 0)) { + // faces towards X- + vertex_dirs[0] = v3s16(-1,-1,-1); + vertex_dirs[1] = v3s16(-1,-1, 1); + vertex_dirs[2] = v3s16(-1, 1, 1); + vertex_dirs[3] = v3s16(-1, 1,-1); + } else if (dir == v3s16(0, 1, 0)) { + // faces towards Y+ (assume Z- as "down" in texture) + vertex_dirs[0] = v3s16( 1, 1,-1); + vertex_dirs[1] = v3s16(-1, 1,-1); + vertex_dirs[2] = v3s16(-1, 1, 1); + vertex_dirs[3] = v3s16( 1, 1, 1); + } else if (dir == v3s16(0, -1, 0)) { + // faces towards Y- (assume Z+ as "down" in texture) + vertex_dirs[0] = v3s16( 1,-1, 1); + vertex_dirs[1] = v3s16(-1,-1, 1); + vertex_dirs[2] = v3s16(-1,-1,-1); + vertex_dirs[3] = v3s16( 1,-1,-1); + } +} + +static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v) +{ + if (dir.X > 0 || dir.Y > 0 || dir.Z < 0) + base -= scale; + if (dir == v3s16(0,0,1)) { + *u = -base.X - 1; + *v = -base.Y - 1; + } else if (dir == v3s16(0,0,-1)) { + *u = base.X + 1; + *v = -base.Y - 2; + } else if (dir == v3s16(1,0,0)) { + *u = base.Z + 1; + *v = -base.Y - 2; + } else if (dir == v3s16(-1,0,0)) { + *u = -base.Z - 1; + *v = -base.Y - 1; + } else if (dir == v3s16(0,1,0)) { + *u = base.X + 1; + *v = -base.Z - 2; + } else if (dir == v3s16(0,-1,0)) { + *u = base.X; + *v = base.Z; + } +} + +struct FastFace +{ + TileSpec tile; + video::S3DVertex vertices[4]; // Precalculated vertices + /*! + * The face is divided into two triangles. If this is true, + * vertices 0 and 2 are connected, othervise vertices 1 and 3 + * are connected. + */ + bool vertex_0_2_connected; +}; + +static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3, + const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector<FastFace> &dest) +{ + // Position is at the center of the cube. + v3f pos = p * BS; + + float x0 = 0.0f; + float y0 = 0.0f; + float w = 1.0f; + float h = 1.0f; + + v3f vertex_pos[4]; + v3s16 vertex_dirs[4]; + getNodeVertexDirs(dir, vertex_dirs); + if (tile.world_aligned) + getNodeTextureCoords(tp, scale, dir, &x0, &y0); + + v3s16 t; + u16 t1; + switch (tile.rotation) { + case 0: + break; + case 1: //R90 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[3]; + vertex_dirs[3] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[1]; + vertex_dirs[1] = t; + t1 = li0; + li0 = li3; + li3 = li2; + li2 = li1; + li1 = t1; + break; + case 2: //R180 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[2]; + vertex_dirs[2] = t; + t = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li2; + li2 = t1; + t1 = li1; + li1 = li3; + li3 = t1; + break; + case 3: //R270 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li1; + li1 = li2; + li2 = li3; + li3 = t1; + break; + case 4: //FXR90 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[3]; + vertex_dirs[3] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[1]; + vertex_dirs[1] = t; + t1 = li0; + li0 = li3; + li3 = li2; + li2 = li1; + li1 = t1; + y0 += h; + h *= -1; + break; + case 5: //FXR270 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li1; + li1 = li2; + li2 = li3; + li3 = t1; + y0 += h; + h *= -1; + break; + case 6: //FYR90 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[3]; + vertex_dirs[3] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[1]; + vertex_dirs[1] = t; + t1 = li0; + li0 = li3; + li3 = li2; + li2 = li1; + li1 = t1; + x0 += w; + w *= -1; + break; + case 7: //FYR270 + t = vertex_dirs[0]; + vertex_dirs[0] = vertex_dirs[1]; + vertex_dirs[1] = vertex_dirs[2]; + vertex_dirs[2] = vertex_dirs[3]; + vertex_dirs[3] = t; + t1 = li0; + li0 = li1; + li1 = li2; + li2 = li3; + li3 = t1; + x0 += w; + w *= -1; + break; + case 8: //FX + y0 += h; + h *= -1; + break; + case 9: //FY + x0 += w; + w *= -1; + break; + default: + break; + } + + for (u16 i = 0; i < 4; i++) { + vertex_pos[i] = v3f( + BS / 2 * vertex_dirs[i].X, + BS / 2 * vertex_dirs[i].Y, + BS / 2 * vertex_dirs[i].Z + ); + } + + for (v3f &vpos : vertex_pos) { + vpos.X *= scale.X; + vpos.Y *= scale.Y; + vpos.Z *= scale.Z; + vpos += pos; + } + + f32 abs_scale = 1.0f; + if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X; + else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y; + else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z; + + v3f normal(dir.X, dir.Y, dir.Z); + + u16 li[4] = { li0, li1, li2, li3 }; + u16 day[4]; + u16 night[4]; + + for (u8 i = 0; i < 4; i++) { + day[i] = li[i] >> 8; + night[i] = li[i] & 0xFF; + } + + bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2]) + < abs(day[1] - day[3]) + abs(night[1] - night[3]); + + v2f32 f[4] = { + core::vector2d<f32>(x0 + w * abs_scale, y0 + h), + core::vector2d<f32>(x0, y0 + h), + core::vector2d<f32>(x0, y0), + core::vector2d<f32>(x0 + w * abs_scale, y0) }; + + // equivalent to dest.push_back(FastFace()) but faster + dest.emplace_back(); + FastFace& face = *dest.rbegin(); + + for (u8 i = 0; i < 4; i++) { + video::SColor c = encode_light(li[i], tile.emissive_light); + if (!tile.emissive_light) + applyFacesShading(c, normal); + + face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]); + } + + /* + Revert triangles for nicer looking gradient if the + brightness of vertices 1 and 3 differ less than + the brightness of vertices 0 and 2. + */ + face.vertex_0_2_connected = vertex_0_2_connected; + face.tile = tile; +} + +/* + Nodes make a face if contents differ and solidness differs. + Return value: + 0: No face + 1: Face uses m1's content + 2: Face uses m2's content + equivalent: Whether the blocks share the same face (eg. water and glass) + + TODO: Add 3: Both faces drawn with backface culling, remove equivalent +*/ +static u8 face_contents(content_t m1, content_t m2, bool *equivalent, + const NodeDefManager *ndef) +{ + *equivalent = false; + + if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE) + return 0; + + const ContentFeatures &f1 = ndef->get(m1); + const ContentFeatures &f2 = ndef->get(m2); + + // Contents don't differ for different forms of same liquid + if (f1.sameLiquid(f2)) + return 0; + + u8 c1 = f1.solidness; + u8 c2 = f2.solidness; + + if (c1 == c2) + return 0; + + if (c1 == 0) + c1 = f1.visual_solidness; + else if (c2 == 0) + c2 = f2.visual_solidness; + + if (c1 == c2) { + *equivalent = true; + // If same solidness, liquid takes precense + if (f1.isLiquid()) + return 1; + if (f2.isLiquid()) + return 2; + } + + if (c1 > c2) + return 1; + + return 2; +} + +/* + Gets nth node tile (0 <= n <= 5). +*/ +void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile) +{ + const NodeDefManager *ndef = data->m_client->ndef(); + const ContentFeatures &f = ndef->get(mn); + tile = f.tiles[tileindex]; + bool has_crack = p == data->m_crack_pos_relative; + for (TileLayer &layer : tile.layers) { + if (layer.texture_id == 0) + continue; + if (!layer.has_color) + mn.getColor(f, &(layer.color)); + // Apply temporary crack + if (has_crack) + layer.material_flags |= MATERIAL_FLAG_CRACK; + } +} + +/* + Gets node tile given a face direction. +*/ +void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile) +{ + const NodeDefManager *ndef = data->m_client->ndef(); + + // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0), + // (0,0,1), (0,0,-1) or (0,0,0) + assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1); + + // Convert direction to single integer for table lookup + // 0 = (0,0,0) + // 1 = (1,0,0) + // 2 = (0,1,0) + // 3 = (0,0,1) + // 4 = invalid, treat as (0,0,0) + // 5 = (0,0,-1) + // 6 = (0,-1,0) + // 7 = (-1,0,0) + u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2; + + // Get rotation for things like chests + u8 facedir = mn.getFaceDir(ndef); + + static const u16 dir_to_tile[24 * 16] = + { + // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation + 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3 + 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 , + 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 , + 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 , + + 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7 + 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 , + 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 , + 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 , + + 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11 + 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 , + 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 , + 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 , + + 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15 + 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 , + 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 , + 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 , + + 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19 + 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 , + 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 , + 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 , + + 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23 + 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 , + 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 , + 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2 + + }; + u16 tile_index = facedir * 16 + dir_i; + getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile); + tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1]; +} + +static void getTileInfo( + // Input: + MeshMakeData *data, + const v3s16 &p, + const v3s16 &face_dir, + // Output: + bool &makes_face, + v3s16 &p_corrected, + v3s16 &face_dir_corrected, + u16 *lights, + TileSpec &tile + ) +{ + VoxelManipulator &vmanip = data->m_vmanip; + const NodeDefManager *ndef = data->m_client->ndef(); + v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; + + const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); + + // Don't even try to get n1 if n0 is already CONTENT_IGNORE + if (n0.getContent() == CONTENT_IGNORE) { + makes_face = false; + return; + } + + const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); + + if (n1.getContent() == CONTENT_IGNORE) { + makes_face = false; + return; + } + + // This is hackish + bool equivalent = false; + u8 mf = face_contents(n0.getContent(), n1.getContent(), + &equivalent, ndef); + + if (mf == 0) { + makes_face = false; + return; + } + + makes_face = true; + + MapNode n = n0; + + if (mf == 1) { + p_corrected = p; + face_dir_corrected = face_dir; + } else { + n = n1; + p_corrected = p + face_dir; + face_dir_corrected = -face_dir; + } + + getNodeTile(n, p_corrected, face_dir_corrected, data, tile); + const ContentFeatures &f = ndef->get(n); + tile.emissive_light = f.light_source; + + // eg. water and glass + if (equivalent) { + for (TileLayer &layer : tile.layers) + layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; + } + + if (!data->m_smooth_lighting) { + lights[0] = lights[1] = lights[2] = lights[3] = + getFaceLight(n0, n1, face_dir, ndef); + } else { + v3s16 vertex_dirs[4]; + getNodeVertexDirs(face_dir_corrected, vertex_dirs); + + v3s16 light_p = blockpos_nodes + p_corrected; + for (u16 i = 0; i < 4; i++) + lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data); + } +} + +/* + startpos: + translate_dir: unit vector with only one of x, y or z + face_dir: unit vector with only one of x, y or z +*/ +static void updateFastFaceRow( + MeshMakeData *data, + const v3s16 &&startpos, + v3s16 translate_dir, + const v3f &&translate_dir_f, + const v3s16 &&face_dir, + std::vector<FastFace> &dest) +{ + v3s16 p = startpos; + + u16 continuous_tiles_count = 1; + + bool makes_face = false; + v3s16 p_corrected; + v3s16 face_dir_corrected; + u16 lights[4] = {0, 0, 0, 0}; + TileSpec tile; + getTileInfo(data, p, face_dir, + makes_face, p_corrected, face_dir_corrected, + lights, tile); + + // Unroll this variable which has a significant build cost + TileSpec next_tile; + for (u16 j = 0; j < MAP_BLOCKSIZE; j++) { + // If tiling can be done, this is set to false in the next step + bool next_is_different = true; + + v3s16 p_next; + + bool next_makes_face = false; + v3s16 next_p_corrected; + v3s16 next_face_dir_corrected; + u16 next_lights[4] = {0, 0, 0, 0}; + + // If at last position, there is nothing to compare to and + // the face must be drawn anyway + if (j != MAP_BLOCKSIZE - 1) { + p_next = p + translate_dir; + + getTileInfo(data, p_next, face_dir, + next_makes_face, next_p_corrected, + next_face_dir_corrected, next_lights, + next_tile); + + if (next_makes_face == makes_face + && next_p_corrected == p_corrected + translate_dir + && next_face_dir_corrected == face_dir_corrected + && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0 + && next_tile.isTileable(tile)) { + next_is_different = false; + continuous_tiles_count++; + } + } + if (next_is_different) { + /* + Create a face if there should be one + */ + if (makes_face) { + // Floating point conversion of the position vector + v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); + // Center point of face (kind of) + v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f) + * translate_dir_f; + v3f scale(1, 1, 1); + + if (translate_dir.X != 0) + scale.X = continuous_tiles_count; + if (translate_dir.Y != 0) + scale.Y = continuous_tiles_count; + if (translate_dir.Z != 0) + scale.Z = continuous_tiles_count; + + makeFastFace(tile, lights[0], lights[1], lights[2], lights[3], + pf, sp, face_dir_corrected, scale, dest); + + g_profiler->avg("Meshgen: faces drawn by tiling", 0); + for (int i = 1; i < continuous_tiles_count; i++) + g_profiler->avg("Meshgen: faces drawn by tiling", 1); + } + + continuous_tiles_count = 1; + } + + makes_face = next_makes_face; + p_corrected = next_p_corrected; + face_dir_corrected = next_face_dir_corrected; + std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16)); + if (next_is_different) + tile = next_tile; + p = p_next; + } +} + +static void updateAllFastFaceRows(MeshMakeData *data, + std::vector<FastFace> &dest) +{ + /* + Go through every y,z and get top(y+) faces in rows of x+ + */ + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + updateFastFaceRow(data, + v3s16(0, y, z), + v3s16(1, 0, 0), //dir + v3f (1, 0, 0), + v3s16(0, 1, 0), //face dir + dest); + + /* + Go through every x,y and get right(x+) faces in rows of z+ + */ + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + updateFastFaceRow(data, + v3s16(x, y, 0), + v3s16(0, 0, 1), //dir + v3f (0, 0, 1), + v3s16(1, 0, 0), //face dir + dest); + + /* + Go through every y,z and get back(z+) faces in rows of x+ + */ + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) + updateFastFaceRow(data, + v3s16(0, y, z), + v3s16(1, 0, 0), //dir + v3f (1, 0, 0), + v3s16(0, 0, 1), //face dir + dest); +} + +static void applyTileColor(PreMeshBuffer &pmb) +{ + video::SColor tc = pmb.layer.color; + if (tc == video::SColor(0xFFFFFFFF)) + return; + for (video::S3DVertex &vertex : pmb.vertices) { + video::SColor *c = &vertex.Color; + c->set(c->getAlpha(), + c->getRed() * tc.getRed() / 255, + c->getGreen() * tc.getGreen() / 255, + c->getBlue() * tc.getBlue() / 255); + } +} + +/* + MapBlockMesh +*/ + +MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): + m_minimap_mapblock(NULL), + m_tsrc(data->m_client->getTextureSource()), + m_shdrsrc(data->m_client->getShaderSource()), + m_animation_force_timer(0), // force initial animation + m_last_crack(-1), + m_last_daynight_ratio((u32) -1) +{ + for (auto &m : m_mesh) + m = new scene::SMesh(); + m_enable_shaders = data->m_use_shaders; + m_use_tangent_vertices = data->m_use_tangent_vertices; + m_enable_vbo = g_settings->getBool("enable_vbo"); + + if (g_settings->getBool("enable_minimap")) { + m_minimap_mapblock = new MinimapMapblock; + m_minimap_mapblock->getMinimapNodes( + &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); + } + + // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) + // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) + //TimeTaker timer1("MapBlockMesh()"); + + std::vector<FastFace> fastfaces_new; + fastfaces_new.reserve(512); + + /* + We are including the faces of the trailing edges of the block. + This means that when something changes, the caller must + also update the meshes of the blocks at the leading edges. + + NOTE: This is the slowest part of this method. + */ + { + // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) + //TimeTaker timer2("updateAllFastFaceRows()"); + updateAllFastFaceRows(data, fastfaces_new); + } + // End of slow part + + /* + Convert FastFaces to MeshCollector + */ + + MeshCollector collector; + + { + // avg 0ms (100ms spikes when loading textures the first time) + // (NOTE: probably outdated) + //TimeTaker timer2("MeshCollector building"); + + for (const FastFace &f : fastfaces_new) { + static const u16 indices[] = {0, 1, 2, 2, 3, 0}; + static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1}; + const u16 *indices_p = + f.vertex_0_2_connected ? indices : indices_alternate; + collector.append(f.tile, f.vertices, 4, indices_p, 6); + } + } + + /* + Add special graphics: + - torches + - flowing water + - fences + - whatever + */ + + { + MapblockMeshGenerator generator(data, &collector); + generator.generate(); + } + + /* + Convert MeshCollector to SMesh + */ + + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) + { + PreMeshBuffer &p = collector.prebuffers[layer][i]; + + applyTileColor(p); + + // Generate animation data + // - Cracks + if (p.layer.material_flags & MATERIAL_FLAG_CRACK) { + // Find the texture name plus ^[crack:N: + std::ostringstream os(std::ios::binary); + os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack"; + if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) + os << "o"; // use ^[cracko + u8 tiles = p.layer.scale; + if (tiles > 1) + os << ":" << (u32)tiles; + os << ":" << (u32)p.layer.animation_frame_count << ":"; + m_crack_materials.insert(std::make_pair( + std::pair<u8, u32>(layer, i), os.str())); + // Replace tile texture with the cracked one + p.layer.texture = m_tsrc->getTextureForMesh( + os.str() + "0", + &p.layer.texture_id); + } + // - Texture animation + if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { + // Add to MapBlockMesh in order to animate these tiles + m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer; + m_animation_frames[std::pair<u8, u32>(layer, i)] = 0; + if (g_settings->getBool( + "desynchronize_mapblock_texture_animation")) { + // Get starting position from noise + m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = + 100000 * (2.0 + noise3d( + data->m_blockpos.X, data->m_blockpos.Y, + data->m_blockpos.Z, 0)); + } else { + // Play all synchronized + m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0; + } + // Replace tile texture with the first animation frame + p.layer.texture = (*p.layer.frames)[0].texture; + } + + if (!m_enable_shaders) { + // Extract colors for day-night animation + // Dummy sunlight to handle non-sunlit areas + video::SColorf sunlight; + get_sunlight_color(&sunlight, 0); + u32 vertex_count = p.vertices.size(); + for (u32 j = 0; j < vertex_count; j++) { + video::SColor *vc = &p.vertices[j].Color; + video::SColor copy = *vc; + if (vc->getAlpha() == 0) // No sunlight - no need to animate + final_color_blend(vc, copy, sunlight); // Finalize color + else // Record color to animate + m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy; + + // The sunlight ratio has been stored, + // delete alpha (for the final rendering). + vc->setAlpha(255); + } + } + + // Create material + video::SMaterial material; + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, true); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_FOG_ENABLE, true); + material.setTexture(0, p.layer.texture); + + if (m_enable_shaders) { + material.MaterialType = m_shdrsrc->getShaderInfo( + p.layer.shader_id).material; + p.layer.applyMaterialOptionsWithShaders(material); + if (p.layer.normal_texture) + material.setTexture(1, p.layer.normal_texture); + material.setTexture(2, p.layer.flags_texture); + } else { + p.layer.applyMaterialOptions(material); + } + + scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer]; + + // Create meshbuffer, add to mesh + if (m_use_tangent_vertices) { + scene::SMeshBufferTangents *buf = + new scene::SMeshBufferTangents(); + buf->Material = material; + buf->Vertices.reallocate(p.vertices.size()); + buf->Indices.reallocate(p.indices.size()); + for (const video::S3DVertex &v: p.vertices) + buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords)); + for (u16 i: p.indices) + buf->Indices.push_back(i); + buf->recalculateBoundingBox(); + mesh->addMeshBuffer(buf); + buf->drop(); + } else { + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Material = material; + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + mesh->addMeshBuffer(buf); + buf->drop(); + } + } + + /* + Do some stuff to the mesh + */ + m_camera_offset = camera_offset; + translateMesh(m_mesh[layer], + intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); + + if (m_use_tangent_vertices) { + scene::IMeshManipulator* meshmanip = + RenderingEngine::get_scene_manager()->getMeshManipulator(); + meshmanip->recalculateTangents(m_mesh[layer], true, false, false); + } + + if (m_mesh[layer]) { +#if 0 + // Usually 1-700 faces and 1-7 materials + std::cout << "Updated MapBlock has " << fastfaces_new.size() + << " faces and uses " << m_mesh[layer]->getMeshBufferCount() + << " materials (meshbuffers)" << std::endl; +#endif + + // Use VBO for mesh (this just would set this for ever buffer) + if (m_enable_vbo) + m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC); + } + } + + //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl; + + // Check if animation is required for this mesh + m_has_animation = + !m_crack_materials.empty() || + !m_daynight_diffs.empty() || + !m_animation_tiles.empty(); +} + +MapBlockMesh::~MapBlockMesh() +{ + for (scene::IMesh *m : m_mesh) { + if (m_enable_vbo && m) + for (u32 i = 0; i < m->getMeshBufferCount(); i++) { + scene::IMeshBuffer *buf = m->getMeshBuffer(i); + RenderingEngine::get_video_driver()->removeHardwareBuffer(buf); + } + m->drop(); + m = NULL; + } + delete m_minimap_mapblock; +} + +bool MapBlockMesh::animate(bool faraway, float time, int crack, + u32 daynight_ratio) +{ + if (!m_has_animation) { + m_animation_force_timer = 100000; + return false; + } + + m_animation_force_timer = myrand_range(5, 100); + + // Cracks + if (crack != m_last_crack) { + for (auto &crack_material : m_crack_materials) { + scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> + getMeshBuffer(crack_material.first.second); + std::string basename = crack_material.second; + + // Create new texture name from original + std::ostringstream os; + os << basename << crack; + u32 new_texture_id = 0; + video::ITexture *new_texture = + m_tsrc->getTextureForMesh(os.str(), &new_texture_id); + buf->getMaterial().setTexture(0, new_texture); + + // If the current material is also animated, + // update animation info + auto anim_iter = m_animation_tiles.find(crack_material.first); + if (anim_iter != m_animation_tiles.end()) { + TileLayer &tile = anim_iter->second; + tile.texture = new_texture; + tile.texture_id = new_texture_id; + // force animation update + m_animation_frames[crack_material.first] = -1; + } + } + + m_last_crack = crack; + } + + // Texture animation + for (auto &animation_tile : m_animation_tiles) { + const TileLayer &tile = animation_tile.second; + // Figure out current frame + int frameoffset = m_animation_frame_offsets[animation_tile.first]; + int frame = (int)(time * 1000 / tile.animation_frame_length_ms + + frameoffset) % tile.animation_frame_count; + // If frame doesn't change, skip + if (frame == m_animation_frames[animation_tile.first]) + continue; + + m_animation_frames[animation_tile.first] = frame; + + scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]-> + getMeshBuffer(animation_tile.first.second); + + const FrameSpec &animation_frame = (*tile.frames)[frame]; + buf->getMaterial().setTexture(0, animation_frame.texture); + if (m_enable_shaders) { + if (animation_frame.normal_texture) + buf->getMaterial().setTexture(1, + animation_frame.normal_texture); + buf->getMaterial().setTexture(2, animation_frame.flags_texture); + } + } + + // Day-night transition + if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) { + // Force reload mesh to VBO + if (m_enable_vbo) + for (scene::IMesh *m : m_mesh) + m->setDirty(); + video::SColorf day_color; + get_sunlight_color(&day_color, daynight_ratio); + + for (auto &daynight_diff : m_daynight_diffs) { + scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]-> + getMeshBuffer(daynight_diff.first.second); + video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); + for (const auto &j : daynight_diff.second) + final_color_blend(&(vertices[j.first].Color), j.second, + day_color); + } + m_last_daynight_ratio = daynight_ratio; + } + + return true; +} + +void MapBlockMesh::updateCameraOffset(v3s16 camera_offset) +{ + if (camera_offset != m_camera_offset) { + for (scene::IMesh *layer : m_mesh) { + translateMesh(layer, + intToFloat(m_camera_offset - camera_offset, BS)); + if (m_enable_vbo) + layer->setDirty(); + } + m_camera_offset = camera_offset; + } +} + +video::SColor encode_light(u16 light, u8 emissive_light) +{ + // Get components + u32 day = (light & 0xff); + u32 night = (light >> 8); + // Add emissive light + night += emissive_light * 2.5f; + if (night > 255) + night = 255; + // Since we don't know if the day light is sunlight or + // artificial light, assume it is artificial when the night + // light bank is also lit. + if (day < night) + day = 0; + else + day = day - night; + u32 sum = day + night; + // Ratio of sunlight: + u32 r; + if (sum > 0) + r = day * 255 / sum; + else + r = 0; + // Average light: + float b = (day + night) / 2; + return video::SColor(r, b, b, b); +} |