3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "mapblock_mesh.h"
28 #include "content_mapblock.h"
29 #include "util/directiontables.h"
30 #include "client/meshgen/collector.h"
31 #include "client/renderingengine.h"
39 MeshMakeData::MeshMakeData(Client *client, bool use_shaders):
41 m_use_shaders(use_shaders),
42 m_mesh_grid(client->getMeshGrid()),
43 side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size)
46 void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
48 m_blockpos = blockpos;
50 v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE;
53 VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE,
54 blockpos_nodes + v3s16(1,1,1) * (side_length + MAP_BLOCKSIZE /* extra layer of blocks around the mesh */) - v3s16(1,1,1));
55 m_vmanip.addArea(voxel_area);
58 void MeshMakeData::fillBlockData(const v3s16 &bp, MapNode *data)
60 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
61 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
63 v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
64 m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
67 void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos)
70 m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE;
73 void MeshMakeData::setSmoothLighting(bool smooth_lighting)
75 m_smooth_lighting = smooth_lighting;
79 Light and vertex color functions
83 Calculate non-smooth lighting at interior of node.
86 static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment,
87 const NodeDefManager *ndef)
89 u8 light = n.getLight(bank, ndef->getLightingFlags(n));
90 light = rangelim(light + increment, 0, LIGHT_SUN);
91 return decode_light(light);
95 Calculate non-smooth lighting at interior of node.
98 u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef)
100 u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef);
101 u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef);
102 return day | (night << 8);
106 Calculate non-smooth lighting at face of node.
109 static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2,
110 v3s16 face_dir, const NodeDefManager *ndef)
112 ContentLightingFlags f1 = ndef->getLightingFlags(n);
113 ContentLightingFlags f2 = ndef->getLightingFlags(n2);
116 u8 l1 = n.getLight(bank, f1);
117 u8 l2 = n2.getLight(bank, f2);
123 // Boost light level for light sources
124 u8 light_source = MYMAX(f1.light_source, f2.light_source);
125 if(light_source > light)
126 light = light_source;
128 return decode_light(light);
132 Calculate non-smooth lighting at face of node.
135 u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
136 const NodeDefManager *ndef)
138 u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef);
139 u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef);
140 return day | (night << 8);
144 Calculate smooth lighting at the XYZ- corner of p.
147 static u16 getSmoothLightCombined(const v3s16 &p,
148 const std::array<v3s16,8> &dirs, MeshMakeData *data)
150 const NodeDefManager *ndef = data->m_client->ndef();
152 u16 ambient_occlusion = 0;
154 u8 light_source_max = 0;
157 bool direct_sunlight = false;
159 auto add_node = [&] (u8 i, bool obstructed = false) -> bool {
164 MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]);
165 if (n.getContent() == CONTENT_IGNORE)
167 const ContentFeatures &f = ndef->get(n);
168 if (f.light_source > light_source_max)
169 light_source_max = f.light_source;
170 // Check f.solidness because fast-style leaves look better this way
171 if (f.param_type == CPT_LIGHT && f.solidness != 2) {
172 u8 light_level_day = n.getLight(LIGHTBANK_DAY, f.getLightingFlags());
173 u8 light_level_night = n.getLight(LIGHTBANK_NIGHT, f.getLightingFlags());
174 if (light_level_day == LIGHT_SUN)
175 direct_sunlight = true;
176 light_day += decode_light(light_level_day);
177 light_night += decode_light(light_level_night);
182 return f.light_propagates;
185 bool obstructed[4] = { true, true, true, true };
187 bool opaque1 = !add_node(1);
188 bool opaque2 = !add_node(2);
189 bool opaque3 = !add_node(3);
190 obstructed[0] = opaque1 && opaque2;
191 obstructed[1] = opaque1 && opaque3;
192 obstructed[2] = opaque2 && opaque3;
193 for (u8 k = 0; k < 3; ++k)
194 if (add_node(k + 4, obstructed[k]))
195 obstructed[3] = false;
196 if (add_node(7, obstructed[3])) { // wrap light around nodes
197 ambient_occlusion -= 3;
198 for (u8 k = 0; k < 3; ++k)
199 add_node(k + 4, !obstructed[k]);
202 if (light_count == 0) {
203 light_day = light_night = 0;
205 light_day /= light_count;
206 light_night /= light_count;
209 // boost direct sunlight, if any
213 // Boost brightness around light sources
214 bool skip_ambient_occlusion_day = false;
215 if (decode_light(light_source_max) >= light_day) {
216 light_day = decode_light(light_source_max);
217 skip_ambient_occlusion_day = true;
220 bool skip_ambient_occlusion_night = false;
221 if(decode_light(light_source_max) >= light_night) {
222 light_night = decode_light(light_source_max);
223 skip_ambient_occlusion_night = true;
226 if (ambient_occlusion > 4) {
227 static thread_local const float ao_gamma = rangelim(
228 g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0);
230 // Table of gamma space multiply factors.
231 static thread_local const float light_amount[3] = {
232 powf(0.75, 1.0 / ao_gamma),
233 powf(0.5, 1.0 / ao_gamma),
234 powf(0.25, 1.0 / ao_gamma)
237 //calculate table index for gamma space multiplier
238 ambient_occlusion -= 5;
240 if (!skip_ambient_occlusion_day)
241 light_day = rangelim(core::round32(
242 light_day * light_amount[ambient_occlusion]), 0, 255);
243 if (!skip_ambient_occlusion_night)
244 light_night = rangelim(core::round32(
245 light_night * light_amount[ambient_occlusion]), 0, 255);
248 return light_day | (light_night << 8);
252 Calculate smooth lighting at the given corner of p.
254 Node at p is solid, and thus the lighting is face-dependent.
256 u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data)
258 return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data);
262 Calculate smooth lighting at the given corner of p.
264 Node at p is not solid, and the lighting is not face-dependent.
266 u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data)
268 const std::array<v3s16,8> dirs = {{
269 // Always shine light
276 v3s16(corner.X,corner.Y,0),
277 v3s16(corner.X,0,corner.Z),
278 v3s16(0,corner.Y,corner.Z),
279 v3s16(corner.X,corner.Y,corner.Z)
281 return getSmoothLightCombined(p, dirs, data);
284 void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){
285 f32 rg = daynight_ratio / 1000.0f - 0.04f;
286 f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f;
292 void final_color_blend(video::SColor *result,
293 u16 light, u32 daynight_ratio)
295 video::SColorf dayLight;
296 get_sunlight_color(&dayLight, daynight_ratio);
297 final_color_blend(result,
298 encode_light(light, 0), dayLight);
301 void final_color_blend(video::SColor *result,
302 const video::SColor &data, const video::SColorf &dayLight)
304 static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f);
306 video::SColorf c(data);
309 f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f;
310 f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f;
311 f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f;
313 // Emphase blue a bit in darker places
314 // Each entry of this array represents a range of 8 blue levels
315 static const u8 emphase_blue_when_dark[32] = {
316 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
317 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
320 b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255),
321 0, 255) / 8] / 255.0f;
323 result->setRed(core::clamp((s32) (r * 255.0f), 0, 255));
324 result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255));
325 result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255));
329 Mesh generation helpers
332 // This table is moved outside getNodeVertexDirs to avoid the compiler using
333 // a mutex to initialize this table at runtime right in the hot path.
334 // For details search the internet for "cxa_guard_acquire".
335 static const v3s16 vertex_dirs_table[] = {
337 v3s16( 1,-1, 1), v3s16( 1,-1,-1),
338 v3s16( 1, 1,-1), v3s16( 1, 1, 1),
340 v3s16( 1, 1,-1), v3s16(-1, 1,-1),
341 v3s16(-1, 1, 1), v3s16( 1, 1, 1),
343 v3s16(-1,-1, 1), v3s16( 1,-1, 1),
344 v3s16( 1, 1, 1), v3s16(-1, 1, 1),
346 v3s16(), v3s16(), v3s16(), v3s16(),
348 v3s16( 1,-1,-1), v3s16(-1,-1,-1),
349 v3s16(-1, 1,-1), v3s16( 1, 1,-1),
351 v3s16( 1,-1, 1), v3s16(-1,-1, 1),
352 v3s16(-1,-1,-1), v3s16( 1,-1,-1),
354 v3s16(-1,-1,-1), v3s16(-1,-1, 1),
355 v3s16(-1, 1, 1), v3s16(-1, 1,-1)
359 vertex_dirs: v3s16[4]
361 static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
364 If looked from outside the node towards the face, the corners are:
371 // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
373 assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z == 1);
375 // Convert direction to single integer for table lookup
376 u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7;
379 #if defined(__GNUC__) && !defined(__clang__)
380 #pragma GCC diagnostic push
382 #pragma GCC diagnostic ignored "-Wclass-memaccess"
385 memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16));
386 #if defined(__GNUC__) && !defined(__clang__)
387 #pragma GCC diagnostic pop
391 static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
393 if (dir.X > 0 || dir.Y != 0 || dir.Z < 0)
395 if (dir == v3s16(0,0,1)) {
398 } else if (dir == v3s16(0,0,-1)) {
401 } else if (dir == v3s16(1,0,0)) {
404 } else if (dir == v3s16(-1,0,0)) {
407 } else if (dir == v3s16(0,1,0)) {
410 } else if (dir == v3s16(0,-1,0)) {
419 video::S3DVertex vertices[4]; // Precalculated vertices
421 * The face is divided into two triangles. If this is true,
422 * vertices 0 and 2 are connected, othervise vertices 1 and 3
425 bool vertex_0_2_connected;
428 static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3,
429 const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector<FastFace> &dest)
431 // Position is at the center of the cube.
440 v3s16 vertex_dirs[4];
441 getNodeVertexDirs(dir, vertex_dirs);
442 if (tile.world_aligned)
443 getNodeTextureCoords(tp, scale, dir, &x0, &y0);
447 switch (tile.rotation) {
452 vertex_dirs[0] = vertex_dirs[3];
453 vertex_dirs[3] = vertex_dirs[2];
454 vertex_dirs[2] = vertex_dirs[1];
464 vertex_dirs[0] = vertex_dirs[2];
467 vertex_dirs[1] = vertex_dirs[3];
478 vertex_dirs[0] = vertex_dirs[1];
479 vertex_dirs[1] = vertex_dirs[2];
480 vertex_dirs[2] = vertex_dirs[3];
490 vertex_dirs[0] = vertex_dirs[3];
491 vertex_dirs[3] = vertex_dirs[2];
492 vertex_dirs[2] = vertex_dirs[1];
504 vertex_dirs[0] = vertex_dirs[1];
505 vertex_dirs[1] = vertex_dirs[2];
506 vertex_dirs[2] = vertex_dirs[3];
518 vertex_dirs[0] = vertex_dirs[3];
519 vertex_dirs[3] = vertex_dirs[2];
520 vertex_dirs[2] = vertex_dirs[1];
532 vertex_dirs[0] = vertex_dirs[1];
533 vertex_dirs[1] = vertex_dirs[2];
534 vertex_dirs[2] = vertex_dirs[3];
556 for (u16 i = 0; i < 4; i++) {
558 BS / 2 * vertex_dirs[i].X,
559 BS / 2 * vertex_dirs[i].Y,
560 BS / 2 * vertex_dirs[i].Z
564 for (v3f &vpos : vertex_pos) {
571 f32 abs_scale = 1.0f;
572 if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X;
573 else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y;
574 else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z;
576 v3f normal(dir.X, dir.Y, dir.Z);
578 u16 li[4] = { li0, li1, li2, li3 };
582 for (u8 i = 0; i < 4; i++) {
584 night[i] = li[i] & 0xFF;
587 bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2])
588 < abs(day[1] - day[3]) + abs(night[1] - night[3]);
591 core::vector2d<f32>(x0 + w * abs_scale, y0 + h),
592 core::vector2d<f32>(x0, y0 + h),
593 core::vector2d<f32>(x0, y0),
594 core::vector2d<f32>(x0 + w * abs_scale, y0) };
596 // equivalent to dest.push_back(FastFace()) but faster
598 FastFace& face = *dest.rbegin();
600 for (u8 i = 0; i < 4; i++) {
601 video::SColor c = encode_light(li[i], tile.emissive_light);
602 if (!tile.emissive_light)
603 applyFacesShading(c, normal);
605 face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]);
609 Revert triangles for nicer looking gradient if the
610 brightness of vertices 1 and 3 differ less than
611 the brightness of vertices 0 and 2.
613 face.vertex_0_2_connected = vertex_0_2_connected;
618 Nodes make a face if contents differ and solidness differs.
621 1: Face uses m1's content
622 2: Face uses m2's content
623 equivalent: Whether the blocks share the same face (eg. water and glass)
625 TODO: Add 3: Both faces drawn with backface culling, remove equivalent
627 static u8 face_contents(content_t m1, content_t m2, bool *equivalent,
628 const NodeDefManager *ndef)
632 if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
635 const ContentFeatures &f1 = ndef->get(m1);
636 const ContentFeatures &f2 = ndef->get(m2);
638 // Contents don't differ for different forms of same liquid
639 if (f1.sameLiquidRender(f2))
642 u8 c1 = f1.solidness;
643 u8 c2 = f2.solidness;
649 c1 = f1.visual_solidness;
651 c2 = f2.visual_solidness;
655 // If same solidness, liquid takes precense
656 if (f1.isLiquidRender())
658 if (f2.isLiquidRender())
669 Gets nth node tile (0 <= n <= 5).
671 void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile)
673 const NodeDefManager *ndef = data->m_client->ndef();
674 const ContentFeatures &f = ndef->get(mn);
675 tile = f.tiles[tileindex];
676 bool has_crack = p == data->m_crack_pos_relative;
677 for (TileLayer &layer : tile.layers) {
678 if (layer.texture_id == 0)
680 if (!layer.has_color)
681 mn.getColor(f, &(layer.color));
682 // Apply temporary crack
684 layer.material_flags |= MATERIAL_FLAG_CRACK;
689 Gets node tile given a face direction.
691 void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile)
693 const NodeDefManager *ndef = data->m_client->ndef();
695 // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
696 // (0,0,1), (0,0,-1) or (0,0,0)
697 assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1);
699 // Convert direction to single integer for table lookup
704 // 4 = invalid, treat as (0,0,0)
708 u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2;
710 // Get rotation for things like chests
711 u8 facedir = mn.getFaceDir(ndef, true);
713 static const u16 dir_to_tile[24 * 16] =
715 // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation
716 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3
717 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 ,
718 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 ,
719 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 ,
721 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7
722 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 ,
723 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 ,
724 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 ,
726 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11
727 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 ,
728 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 ,
729 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 ,
731 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15
732 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 ,
733 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 ,
734 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 ,
736 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19
737 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 ,
738 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 ,
739 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 ,
741 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23
742 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 ,
743 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 ,
744 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2
747 u16 tile_index = facedir * 16 + dir_i;
748 getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile);
749 tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
752 static void getTileInfo(
756 const v3s16 &face_dir,
760 v3s16 &face_dir_corrected,
766 VoxelManipulator &vmanip = data->m_vmanip;
767 const NodeDefManager *ndef = data->m_client->ndef();
768 v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
770 const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
772 // Don't even try to get n1 if n0 is already CONTENT_IGNORE
773 if (n0.getContent() == CONTENT_IGNORE) {
778 const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
780 if (n1.getContent() == CONTENT_IGNORE) {
786 bool equivalent = false;
787 u8 mf = face_contents(n0.getContent(), n1.getContent(),
801 face_dir_corrected = face_dir;
804 p_corrected = p + face_dir;
805 face_dir_corrected = -face_dir;
808 getNodeTile(n, p_corrected, face_dir_corrected, data, tile);
809 const ContentFeatures &f = ndef->get(n);
811 tile.emissive_light = f.light_source;
813 // eg. water and glass
815 for (TileLayer &layer : tile.layers)
816 layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
819 if (!data->m_smooth_lighting) {
820 lights[0] = lights[1] = lights[2] = lights[3] =
821 getFaceLight(n0, n1, face_dir, ndef);
823 v3s16 vertex_dirs[4];
824 getNodeVertexDirs(face_dir_corrected, vertex_dirs);
826 v3s16 light_p = blockpos_nodes + p_corrected;
827 for (u16 i = 0; i < 4; i++)
828 lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data);
834 translate_dir: unit vector with only one of x, y or z
835 face_dir: unit vector with only one of x, y or z
837 static void updateFastFaceRow(
839 const v3s16 &&startpos,
841 const v3f &&translate_dir_f,
842 const v3s16 &&face_dir,
843 std::vector<FastFace> &dest)
845 static thread_local const bool waving_liquids =
846 g_settings->getBool("enable_shaders") &&
847 g_settings->getBool("enable_waving_water");
849 static thread_local const bool force_not_tiling =
850 g_settings->getBool("enable_dynamic_shadows");
854 u16 continuous_tiles_count = 1;
856 bool makes_face = false;
858 v3s16 face_dir_corrected;
859 u16 lights[4] = {0, 0, 0, 0};
863 // Get info of first tile
864 getTileInfo(data, p, face_dir,
865 makes_face, p_corrected, face_dir_corrected,
866 lights, waving, tile);
868 // Unroll this variable which has a significant build cost
870 for (u16 j = 0; j < data->side_length; j++) {
871 // If tiling can be done, this is set to false in the next step
872 bool next_is_different = true;
874 bool next_makes_face = false;
875 v3s16 next_p_corrected;
876 v3s16 next_face_dir_corrected;
877 u16 next_lights[4] = {0, 0, 0, 0};
879 // If at last position, there is nothing to compare to and
880 // the face must be drawn anyway
881 if (j != data->side_length - 1) {
884 getTileInfo(data, p, face_dir,
885 next_makes_face, next_p_corrected,
886 next_face_dir_corrected, next_lights,
890 if (!force_not_tiling
891 && next_makes_face == makes_face
892 && next_p_corrected == p_corrected + translate_dir
893 && next_face_dir_corrected == face_dir_corrected
894 && memcmp(next_lights, lights, sizeof(lights)) == 0
895 // Don't apply fast faces to waving water.
896 && (waving != 3 || !waving_liquids)
897 && next_tile.isTileable(tile)) {
898 next_is_different = false;
899 continuous_tiles_count++;
902 if (next_is_different) {
904 Create a face if there should be one
907 // Floating point conversion of the position vector
908 v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z);
909 // Center point of face (kind of)
910 v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f)
914 if (translate_dir.X != 0)
915 scale.X = continuous_tiles_count;
916 if (translate_dir.Y != 0)
917 scale.Y = continuous_tiles_count;
918 if (translate_dir.Z != 0)
919 scale.Z = continuous_tiles_count;
921 makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
922 pf, sp, face_dir_corrected, scale, dest);
923 g_profiler->avg("Meshgen: Tiles per face [#]", continuous_tiles_count);
926 continuous_tiles_count = 1;
929 makes_face = next_makes_face;
930 p_corrected = next_p_corrected;
931 face_dir_corrected = next_face_dir_corrected;
932 memcpy(lights, next_lights, sizeof(lights));
933 if (next_is_different)
934 tile = std::move(next_tile); // faster than copy
938 static void updateAllFastFaceRows(MeshMakeData *data,
939 std::vector<FastFace> &dest)
942 Go through every y,z and get top(y+) faces in rows of x+
944 for (s16 y = 0; y < data->side_length; y++)
945 for (s16 z = 0; z < data->side_length; z++)
946 updateFastFaceRow(data,
948 v3s16(1, 0, 0), //dir
950 v3s16(0, 1, 0), //face dir
954 Go through every x,y and get right(x+) faces in rows of z+
956 for (s16 x = 0; x < data->side_length; x++)
957 for (s16 y = 0; y < data->side_length; y++)
958 updateFastFaceRow(data,
960 v3s16(0, 0, 1), //dir
962 v3s16(1, 0, 0), //face dir
966 Go through every y,z and get back(z+) faces in rows of x+
968 for (s16 z = 0; z < data->side_length; z++)
969 for (s16 y = 0; y < data->side_length; y++)
970 updateFastFaceRow(data,
972 v3s16(1, 0, 0), //dir
974 v3s16(0, 0, 1), //face dir
978 static void applyTileColor(PreMeshBuffer &pmb)
980 video::SColor tc = pmb.layer.color;
981 if (tc == video::SColor(0xFFFFFFFF))
983 for (video::S3DVertex &vertex : pmb.vertices) {
984 video::SColor *c = &vertex.Color;
985 c->set(c->getAlpha(),
986 c->getRed() * tc.getRed() / 255,
987 c->getGreen() * tc.getGreen() / 255,
988 c->getBlue() * tc.getBlue() / 255);
996 void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles, u16 side_length)
998 this->triangles = triangles;
1002 // assert that triangle index can fit into s32
1003 assert(triangles->size() <= 0x7FFFFFFFL);
1004 std::vector<s32> indexes;
1005 indexes.reserve(triangles->size());
1006 for (u32 i = 0; i < triangles->size(); i++)
1007 indexes.push_back(i);
1009 if (!indexes.empty()) {
1010 // Start in the center of the block with increment of one quarter in each direction
1011 root = buildTree(v3f(1, 0, 0), v3f((side_length + 1) * 0.5f * BS), side_length * 0.25f * BS, indexes, 0);
1018 * @brief Find a candidate plane to split a set of triangles in two
1020 * The candidate plane is represented by one of the triangles from the set.
1022 * @param list Vector of indexes of the triangles in the set
1023 * @param triangles Vector of all triangles in the BSP tree
1024 * @return Address of the triangle that represents the proposed split plane
1026 static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
1028 // find the center of the cluster.
1029 v3f center(0, 0, 0);
1030 size_t n = list.size();
1031 for (s32 i : list) {
1032 center += triangles[i].centroid / n;
1035 // find the triangle with the largest area and closest to the center
1036 const MeshTriangle *candidate_triangle = &triangles[list[0]];
1037 const MeshTriangle *ith_triangle;
1038 for (s32 i : list) {
1039 ith_triangle = &triangles[i];
1040 if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
1041 (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
1042 ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
1043 candidate_triangle = ith_triangle;
1046 return candidate_triangle;
1049 s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
1051 // if the list is empty, don't bother
1055 // if there is only one triangle, or the delta is insanely small, this is a leaf node
1056 if (list.size() == 1 || delta < 0.01) {
1057 nodes.emplace_back(normal, origin, list, -1, -1);
1058 return nodes.size() - 1;
1061 std::vector<s32> front_list;
1062 std::vector<s32> back_list;
1063 std::vector<s32> node_list;
1066 for (s32 i : list) {
1067 const MeshTriangle &triangle = (*triangles)[i];
1068 float factor = normal.dotProduct(triangle.centroid - origin);
1070 node_list.push_back(i);
1071 else if (factor > 0)
1072 front_list.push_back(i);
1074 back_list.push_back(i);
1077 // define the new split-plane
1078 v3f candidate_normal(normal.Z, normal.X, normal.Y);
1079 float candidate_delta = delta;
1081 candidate_delta /= 2;
1083 s32 front_index = -1;
1084 s32 back_index = -1;
1086 if (!front_list.empty()) {
1087 v3f next_normal = candidate_normal;
1088 v3f next_origin = origin + delta * normal;
1089 float next_delta = candidate_delta;
1090 if (next_delta < 5) {
1091 const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
1092 next_normal = candidate->getNormal();
1093 next_origin = candidate->centroid;
1095 front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
1097 // if there are no other triangles, don't create a new node
1098 if (back_list.empty() && node_list.empty())
1102 if (!back_list.empty()) {
1103 v3f next_normal = candidate_normal;
1104 v3f next_origin = origin - delta * normal;
1105 float next_delta = candidate_delta;
1106 if (next_delta < 5) {
1107 const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
1108 next_normal = candidate->getNormal();
1109 next_origin = candidate->centroid;
1112 back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
1114 // if there are no other triangles, don't create a new node
1115 if (front_list.empty() && node_list.empty())
1119 nodes.emplace_back(normal, origin, node_list, front_index, back_index);
1121 return nodes.size() - 1;
1124 void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
1126 if (node < 0) return; // recursion break;
1128 const TreeNode &n = nodes[node];
1129 float factor = n.normal.dotProduct(viewpoint - n.origin);
1132 traverse(n.back_ref, viewpoint, output);
1134 traverse(n.front_ref, viewpoint, output);
1137 for (s32 i : n.triangle_refs)
1138 output.push_back(i);
1141 traverse(n.front_ref, viewpoint, output);
1143 traverse(n.back_ref, viewpoint, output);
1152 void PartialMeshBuffer::beforeDraw() const
1154 // Patch the indexes in the mesh buffer before draw
1155 m_buffer->Indices = std::move(m_vertex_indexes);
1156 m_buffer->setDirty(scene::EBT_INDEX);
1159 void PartialMeshBuffer::afterDraw() const
1161 // Take the data back
1162 m_vertex_indexes = m_buffer->Indices.steal();
1169 MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
1170 m_tsrc(data->m_client->getTextureSource()),
1171 m_shdrsrc(data->m_client->getShaderSource()),
1172 m_animation_force_timer(0), // force initial animation
1174 m_last_daynight_ratio((u32) -1)
1176 for (auto &m : m_mesh)
1177 m = new scene::SMesh();
1178 m_enable_shaders = data->m_use_shaders;
1179 m_enable_vbo = g_settings->getBool("enable_vbo");
1181 v3s16 bp = data->m_blockpos;
1182 // Only generate minimap mapblocks at even coordinates.
1183 if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) {
1184 m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr);
1187 // See also client.cpp for the code that reads the array of minimap blocks.
1188 for (ofs.Z = 0; ofs.Z < data->m_mesh_grid.cell_size; ofs.Z++)
1189 for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++)
1190 for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) {
1191 v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
1192 if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
1193 MinimapMapblock *block = new MinimapMapblock;
1194 m_minimap_mapblocks[data->m_mesh_grid.getOffsetIndex(ofs)] = block;
1195 block->getMinimapNodes(&data->m_vmanip, p);
1200 // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
1201 // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated)
1202 //TimeTaker timer1("MapBlockMesh()");
1204 std::vector<FastFace> fastfaces_new;
1205 fastfaces_new.reserve(512);
1208 We are including the faces of the trailing edges of the block.
1209 This means that when something changes, the caller must
1210 also update the meshes of the blocks at the leading edges.
1212 NOTE: This is the slowest part of this method.
1215 // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
1216 //TimeTaker timer2("updateAllFastFaceRows()");
1217 updateAllFastFaceRows(data, fastfaces_new);
1222 Convert FastFaces to MeshCollector
1225 v3f offset = intToFloat((data->m_blockpos - data->m_mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS);
1226 MeshCollector collector(m_bounding_sphere_center, offset);
1229 // avg 0ms (100ms spikes when loading textures the first time)
1230 // (NOTE: probably outdated)
1231 //TimeTaker timer2("MeshCollector building");
1233 for (const FastFace &f : fastfaces_new) {
1234 static const u16 indices[] = {0, 1, 2, 2, 3, 0};
1235 static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1};
1236 const u16 *indices_p =
1237 f.vertex_0_2_connected ? indices : indices_alternate;
1238 collector.append(f.tile, f.vertices, 4, indices_p, 6);
1243 Add special graphics:
1251 MapblockMeshGenerator(data, &collector,
1252 data->m_client->getSceneManager()->getMeshManipulator()).generate();
1256 Convert MeshCollector to SMesh
1259 const bool desync_animations = g_settings->getBool(
1260 "desynchronize_mapblock_texture_animation");
1262 m_bounding_radius = std::sqrt(collector.m_bounding_radius_sq);
1264 for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
1265 for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
1267 PreMeshBuffer &p = collector.prebuffers[layer][i];
1271 // Generate animation data
1273 if (p.layer.material_flags & MATERIAL_FLAG_CRACK) {
1274 // Find the texture name plus ^[crack:N:
1275 std::ostringstream os(std::ios::binary);
1276 os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack";
1277 if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
1278 os << "o"; // use ^[cracko
1279 u8 tiles = p.layer.scale;
1281 os << ":" << (u32)tiles;
1282 os << ":" << (u32)p.layer.animation_frame_count << ":";
1283 m_crack_materials.insert(std::make_pair(
1284 std::pair<u8, u32>(layer, i), os.str()));
1285 // Replace tile texture with the cracked one
1286 p.layer.texture = m_tsrc->getTextureForMesh(
1288 &p.layer.texture_id);
1290 // - Texture animation
1291 if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
1292 // Add to MapBlockMesh in order to animate these tiles
1293 auto &info = m_animation_info[{layer, i}];
1294 info.tile = p.layer;
1296 if (desync_animations) {
1297 // Get starting position from noise
1299 100000 * (2.0 + noise3d(
1300 data->m_blockpos.X, data->m_blockpos.Y,
1301 data->m_blockpos.Z, 0));
1303 // Play all synchronized
1304 info.frame_offset = 0;
1306 // Replace tile texture with the first animation frame
1307 p.layer.texture = (*p.layer.frames)[0].texture;
1310 if (!m_enable_shaders) {
1311 // Extract colors for day-night animation
1312 // Dummy sunlight to handle non-sunlit areas
1313 video::SColorf sunlight;
1314 get_sunlight_color(&sunlight, 0);
1316 std::map<u32, video::SColor> colors;
1317 const u32 vertex_count = p.vertices.size();
1318 for (u32 j = 0; j < vertex_count; j++) {
1319 video::SColor *vc = &p.vertices[j].Color;
1320 video::SColor copy = *vc;
1321 if (vc->getAlpha() == 0) // No sunlight - no need to animate
1322 final_color_blend(vc, copy, sunlight); // Finalize color
1323 else // Record color to animate
1326 // The sunlight ratio has been stored,
1327 // delete alpha (for the final rendering).
1330 if (!colors.empty())
1331 m_daynight_diffs[{layer, i}] = std::move(colors);
1335 video::SMaterial material;
1336 material.setFlag(video::EMF_LIGHTING, false);
1337 material.setFlag(video::EMF_BACK_FACE_CULLING, true);
1338 material.setFlag(video::EMF_BILINEAR_FILTER, false);
1339 material.setFlag(video::EMF_FOG_ENABLE, true);
1340 material.setTexture(0, p.layer.texture);
1342 if (m_enable_shaders) {
1343 material.MaterialType = m_shdrsrc->getShaderInfo(
1344 p.layer.shader_id).material;
1345 p.layer.applyMaterialOptionsWithShaders(material);
1346 if (p.layer.normal_texture)
1347 material.setTexture(1, p.layer.normal_texture);
1348 material.setTexture(2, p.layer.flags_texture);
1350 p.layer.applyMaterialOptions(material);
1353 scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer];
1355 scene::SMeshBuffer *buf = new scene::SMeshBuffer();
1356 buf->Material = material;
1357 if (p.layer.isTransparent()) {
1358 buf->append(&p.vertices[0], p.vertices.size(), nullptr, 0);
1362 m_transparent_triangles.reserve(p.indices.size() / 3);
1363 for (u32 i = 0; i < p.indices.size(); i += 3) {
1364 t.p1 = p.indices[i];
1365 t.p2 = p.indices[i + 1];
1366 t.p3 = p.indices[i + 2];
1367 t.updateAttributes();
1368 m_transparent_triangles.push_back(t);
1371 buf->append(&p.vertices[0], p.vertices.size(),
1372 &p.indices[0], p.indices.size());
1374 mesh->addMeshBuffer(buf);
1378 if (m_mesh[layer]) {
1379 // Use VBO for mesh (this just would set this for ever buffer)
1381 m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC);
1385 //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
1386 m_bsp_tree.buildTree(&m_transparent_triangles, data->side_length);
1388 // Check if animation is required for this mesh
1390 !m_crack_materials.empty() ||
1391 !m_daynight_diffs.empty() ||
1392 !m_animation_info.empty();
1395 MapBlockMesh::~MapBlockMesh()
1397 for (scene::IMesh *m : m_mesh) {
1398 #if IRRLICHT_VERSION_MT_REVISION < 5
1400 for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
1401 scene::IMeshBuffer *buf = m->getMeshBuffer(i);
1402 RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
1408 for (MinimapMapblock *block : m_minimap_mapblocks)
1412 bool MapBlockMesh::animate(bool faraway, float time, int crack,
1415 if (!m_has_animation) {
1416 m_animation_force_timer = 100000;
1420 m_animation_force_timer = myrand_range(5, 100);
1423 if (crack != m_last_crack) {
1424 for (auto &crack_material : m_crack_materials) {
1425 scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
1426 getMeshBuffer(crack_material.first.second);
1428 // Create new texture name from original
1429 std::string s = crack_material.second + itos(crack);
1430 u32 new_texture_id = 0;
1431 video::ITexture *new_texture =
1432 m_tsrc->getTextureForMesh(s, &new_texture_id);
1433 buf->getMaterial().setTexture(0, new_texture);
1435 // If the current material is also animated, update animation info
1436 auto anim_it = m_animation_info.find(crack_material.first);
1437 if (anim_it != m_animation_info.end()) {
1438 TileLayer &tile = anim_it->second.tile;
1439 tile.texture = new_texture;
1440 tile.texture_id = new_texture_id;
1441 // force animation update
1442 anim_it->second.frame = -1;
1446 m_last_crack = crack;
1449 // Texture animation
1450 for (auto &it : m_animation_info) {
1451 const TileLayer &tile = it.second.tile;
1452 // Figure out current frame
1453 int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
1454 + it.second.frame_offset) % tile.animation_frame_count;
1455 // If frame doesn't change, skip
1456 if (frameno == it.second.frame)
1459 it.second.frame = frameno;
1461 scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
1463 const FrameSpec &frame = (*tile.frames)[frameno];
1464 buf->getMaterial().setTexture(0, frame.texture);
1465 if (m_enable_shaders) {
1466 if (frame.normal_texture)
1467 buf->getMaterial().setTexture(1, frame.normal_texture);
1468 buf->getMaterial().setTexture(2, frame.flags_texture);
1472 // Day-night transition
1473 if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) {
1474 // Force reload mesh to VBO
1476 for (scene::IMesh *m : m_mesh)
1478 video::SColorf day_color;
1479 get_sunlight_color(&day_color, daynight_ratio);
1481 for (auto &daynight_diff : m_daynight_diffs) {
1482 scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]->
1483 getMeshBuffer(daynight_diff.first.second);
1484 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1485 for (const auto &j : daynight_diff.second)
1486 final_color_blend(&(vertices[j.first].Color), j.second,
1489 m_last_daynight_ratio = daynight_ratio;
1495 void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
1497 // nothing to do if the entire block is opaque
1498 if (m_transparent_triangles.empty())
1501 v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
1502 v3f rel_camera_pos = camera_pos - block_posf;
1504 std::vector<s32> triangle_refs;
1505 m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
1507 // arrange index sequences into partial buffers
1508 m_transparent_buffers.clear();
1510 scene::SMeshBuffer *current_buffer = nullptr;
1511 std::vector<u16> current_strain;
1512 for (auto i : triangle_refs) {
1513 const auto &t = m_transparent_triangles[i];
1514 if (current_buffer != t.buffer) {
1515 if (current_buffer) {
1516 m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1517 current_strain.clear();
1519 current_buffer = t.buffer;
1521 current_strain.push_back(t.p1);
1522 current_strain.push_back(t.p2);
1523 current_strain.push_back(t.p3);
1526 if (!current_strain.empty())
1527 m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1530 void MapBlockMesh::consolidateTransparentBuffers()
1532 m_transparent_buffers.clear();
1534 scene::SMeshBuffer *current_buffer = nullptr;
1535 std::vector<u16> current_strain;
1537 // use the fact that m_transparent_triangles is already arranged by buffer
1538 for (const auto &t : m_transparent_triangles) {
1539 if (current_buffer != t.buffer) {
1540 if (current_buffer != nullptr) {
1541 this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1542 current_strain.clear();
1544 current_buffer = t.buffer;
1546 current_strain.push_back(t.p1);
1547 current_strain.push_back(t.p2);
1548 current_strain.push_back(t.p3);
1551 if (!current_strain.empty()) {
1552 this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1556 video::SColor encode_light(u16 light, u8 emissive_light)
1559 u32 day = (light & 0xff);
1560 u32 night = (light >> 8);
1561 // Add emissive light
1562 night += emissive_light * 2.5f;
1565 // Since we don't know if the day light is sunlight or
1566 // artificial light, assume it is artificial when the night
1567 // light bank is also lit.
1572 u32 sum = day + night;
1573 // Ratio of sunlight:
1576 r = day * 255 / sum;
1580 float b = (day + night) / 2;
1581 return video::SColor(r, b, b, b);
1584 std::unordered_map<v3s16, u8> get_solid_sides(MeshMakeData *data)
1586 std::unordered_map<v3s16, u8> results;
1588 const u16 mesh_chunk = data->side_length / MAP_BLOCKSIZE;
1590 for (ofs.X = 0; ofs.X < mesh_chunk; ofs.X++)
1591 for (ofs.Y = 0; ofs.Y < mesh_chunk; ofs.Y++)
1592 for (ofs.Z = 0; ofs.Z < mesh_chunk; ofs.Z++) {
1593 v3s16 blockpos = data->m_blockpos + ofs;
1594 v3s16 blockpos_nodes = blockpos * MAP_BLOCKSIZE;
1595 const NodeDefManager *ndef = data->m_client->ndef();
1597 u8 result = 0x3F; // all sides solid;
1599 for (s16 i = 0; i < MAP_BLOCKSIZE && result != 0; i++)
1600 for (s16 j = 0; j < MAP_BLOCKSIZE && result != 0; j++) {
1601 v3s16 positions[6] = {
1603 v3s16(MAP_BLOCKSIZE - 1, i, j),
1605 v3s16(i, MAP_BLOCKSIZE - 1, j),
1607 v3s16(i, j, MAP_BLOCKSIZE - 1)
1610 for (u8 k = 0; k < 6; k++) {
1611 const MapNode &top = data->m_vmanip.getNodeRefUnsafe(blockpos_nodes + positions[k]);
1612 if (ndef->get(top).solidness != 2)
1613 result &= ~(1 << k);
1617 results[blockpos] = result;