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)
44 void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
46 m_blockpos = blockpos;
48 v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE;
51 VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE,
52 blockpos_nodes + v3s16(1,1,1) * (side_length + MAP_BLOCKSIZE /* extra layer of blocks around the mesh */) - v3s16(1,1,1));
53 m_vmanip.addArea(voxel_area);
56 void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data)
58 v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
59 VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
61 v3s16 bp = m_blockpos + block_offset;
62 v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
63 m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
66 void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos)
69 m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE;
72 void MeshMakeData::setSmoothLighting(bool smooth_lighting)
74 m_smooth_lighting = smooth_lighting;
78 Light and vertex color functions
82 Calculate non-smooth lighting at interior of node.
85 static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment,
86 const NodeDefManager *ndef)
88 u8 light = n.getLight(bank, ndef->getLightingFlags(n));
89 light = rangelim(light + increment, 0, LIGHT_SUN);
90 return decode_light(light);
94 Calculate non-smooth lighting at interior of node.
97 u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef)
99 u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef);
100 u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef);
101 return day | (night << 8);
105 Calculate non-smooth lighting at face of node.
108 static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2,
109 v3s16 face_dir, const NodeDefManager *ndef)
111 ContentLightingFlags f1 = ndef->getLightingFlags(n);
112 ContentLightingFlags f2 = ndef->getLightingFlags(n2);
115 u8 l1 = n.getLight(bank, f1);
116 u8 l2 = n2.getLight(bank, f2);
122 // Boost light level for light sources
123 u8 light_source = MYMAX(f1.light_source, f2.light_source);
124 if(light_source > light)
125 light = light_source;
127 return decode_light(light);
131 Calculate non-smooth lighting at face of node.
134 u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
135 const NodeDefManager *ndef)
137 u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef);
138 u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef);
139 return day | (night << 8);
143 Calculate smooth lighting at the XYZ- corner of p.
146 static u16 getSmoothLightCombined(const v3s16 &p,
147 const std::array<v3s16,8> &dirs, MeshMakeData *data)
149 const NodeDefManager *ndef = data->m_client->ndef();
151 u16 ambient_occlusion = 0;
153 u8 light_source_max = 0;
156 bool direct_sunlight = false;
158 auto add_node = [&] (u8 i, bool obstructed = false) -> bool {
163 MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]);
164 if (n.getContent() == CONTENT_IGNORE)
166 const ContentFeatures &f = ndef->get(n);
167 if (f.light_source > light_source_max)
168 light_source_max = f.light_source;
169 // Check f.solidness because fast-style leaves look better this way
170 if (f.param_type == CPT_LIGHT && f.solidness != 2) {
171 u8 light_level_day = n.getLight(LIGHTBANK_DAY, f.getLightingFlags());
172 u8 light_level_night = n.getLight(LIGHTBANK_NIGHT, f.getLightingFlags());
173 if (light_level_day == LIGHT_SUN)
174 direct_sunlight = true;
175 light_day += decode_light(light_level_day);
176 light_night += decode_light(light_level_night);
181 return f.light_propagates;
184 bool obstructed[4] = { true, true, true, true };
186 bool opaque1 = !add_node(1);
187 bool opaque2 = !add_node(2);
188 bool opaque3 = !add_node(3);
189 obstructed[0] = opaque1 && opaque2;
190 obstructed[1] = opaque1 && opaque3;
191 obstructed[2] = opaque2 && opaque3;
192 for (u8 k = 0; k < 3; ++k)
193 if (add_node(k + 4, obstructed[k]))
194 obstructed[3] = false;
195 if (add_node(7, obstructed[3])) { // wrap light around nodes
196 ambient_occlusion -= 3;
197 for (u8 k = 0; k < 3; ++k)
198 add_node(k + 4, !obstructed[k]);
201 if (light_count == 0) {
202 light_day = light_night = 0;
204 light_day /= light_count;
205 light_night /= light_count;
208 // boost direct sunlight, if any
212 // Boost brightness around light sources
213 bool skip_ambient_occlusion_day = false;
214 if (decode_light(light_source_max) >= light_day) {
215 light_day = decode_light(light_source_max);
216 skip_ambient_occlusion_day = true;
219 bool skip_ambient_occlusion_night = false;
220 if(decode_light(light_source_max) >= light_night) {
221 light_night = decode_light(light_source_max);
222 skip_ambient_occlusion_night = true;
225 if (ambient_occlusion > 4) {
226 static thread_local const float ao_gamma = rangelim(
227 g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0);
229 // Table of gamma space multiply factors.
230 static thread_local const float light_amount[3] = {
231 powf(0.75, 1.0 / ao_gamma),
232 powf(0.5, 1.0 / ao_gamma),
233 powf(0.25, 1.0 / ao_gamma)
236 //calculate table index for gamma space multiplier
237 ambient_occlusion -= 5;
239 if (!skip_ambient_occlusion_day)
240 light_day = rangelim(core::round32(
241 light_day * light_amount[ambient_occlusion]), 0, 255);
242 if (!skip_ambient_occlusion_night)
243 light_night = rangelim(core::round32(
244 light_night * light_amount[ambient_occlusion]), 0, 255);
247 return light_day | (light_night << 8);
251 Calculate smooth lighting at the given corner of p.
253 Node at p is solid, and thus the lighting is face-dependent.
255 u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data)
257 return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data);
261 Calculate smooth lighting at the given corner of p.
263 Node at p is not solid, and the lighting is not face-dependent.
265 u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data)
267 const std::array<v3s16,8> dirs = {{
268 // Always shine light
275 v3s16(corner.X,corner.Y,0),
276 v3s16(corner.X,0,corner.Z),
277 v3s16(0,corner.Y,corner.Z),
278 v3s16(corner.X,corner.Y,corner.Z)
280 return getSmoothLightCombined(p, dirs, data);
283 void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){
284 f32 rg = daynight_ratio / 1000.0f - 0.04f;
285 f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f;
291 void final_color_blend(video::SColor *result,
292 u16 light, u32 daynight_ratio)
294 video::SColorf dayLight;
295 get_sunlight_color(&dayLight, daynight_ratio);
296 final_color_blend(result,
297 encode_light(light, 0), dayLight);
300 void final_color_blend(video::SColor *result,
301 const video::SColor &data, const video::SColorf &dayLight)
303 static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f);
305 video::SColorf c(data);
308 f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f;
309 f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f;
310 f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f;
312 // Emphase blue a bit in darker places
313 // Each entry of this array represents a range of 8 blue levels
314 static const u8 emphase_blue_when_dark[32] = {
315 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
316 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
319 b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255),
320 0, 255) / 8] / 255.0f;
322 result->setRed(core::clamp((s32) (r * 255.0f), 0, 255));
323 result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255));
324 result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255));
328 Mesh generation helpers
331 // This table is moved outside getNodeVertexDirs to avoid the compiler using
332 // a mutex to initialize this table at runtime right in the hot path.
333 // For details search the internet for "cxa_guard_acquire".
334 static const v3s16 vertex_dirs_table[] = {
336 v3s16( 1,-1, 1), v3s16( 1,-1,-1),
337 v3s16( 1, 1,-1), v3s16( 1, 1, 1),
339 v3s16( 1, 1,-1), v3s16(-1, 1,-1),
340 v3s16(-1, 1, 1), v3s16( 1, 1, 1),
342 v3s16(-1,-1, 1), v3s16( 1,-1, 1),
343 v3s16( 1, 1, 1), v3s16(-1, 1, 1),
345 v3s16(), v3s16(), v3s16(), v3s16(),
347 v3s16( 1,-1,-1), v3s16(-1,-1,-1),
348 v3s16(-1, 1,-1), v3s16( 1, 1,-1),
350 v3s16( 1,-1, 1), v3s16(-1,-1, 1),
351 v3s16(-1,-1,-1), v3s16( 1,-1,-1),
353 v3s16(-1,-1,-1), v3s16(-1,-1, 1),
354 v3s16(-1, 1, 1), v3s16(-1, 1,-1)
358 vertex_dirs: v3s16[4]
360 static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
363 If looked from outside the node towards the face, the corners are:
370 // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
372 assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z == 1);
374 // Convert direction to single integer for table lookup
375 u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7;
378 #if defined(__GNUC__) && !defined(__clang__)
379 #pragma GCC diagnostic push
381 #pragma GCC diagnostic ignored "-Wclass-memaccess"
384 memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16));
385 #if defined(__GNUC__) && !defined(__clang__)
386 #pragma GCC diagnostic pop
390 static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
392 if (dir.X > 0 || dir.Y != 0 || dir.Z < 0)
394 if (dir == v3s16(0,0,1)) {
397 } else if (dir == v3s16(0,0,-1)) {
400 } else if (dir == v3s16(1,0,0)) {
403 } else if (dir == v3s16(-1,0,0)) {
406 } else if (dir == v3s16(0,1,0)) {
409 } else if (dir == v3s16(0,-1,0)) {
418 video::S3DVertex vertices[4]; // Precalculated vertices
420 * The face is divided into two triangles. If this is true,
421 * vertices 0 and 2 are connected, othervise vertices 1 and 3
424 bool vertex_0_2_connected;
427 static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3,
428 const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector<FastFace> &dest)
430 // Position is at the center of the cube.
439 v3s16 vertex_dirs[4];
440 getNodeVertexDirs(dir, vertex_dirs);
441 if (tile.world_aligned)
442 getNodeTextureCoords(tp, scale, dir, &x0, &y0);
446 switch (tile.rotation) {
451 vertex_dirs[0] = vertex_dirs[3];
452 vertex_dirs[3] = vertex_dirs[2];
453 vertex_dirs[2] = vertex_dirs[1];
463 vertex_dirs[0] = vertex_dirs[2];
466 vertex_dirs[1] = vertex_dirs[3];
477 vertex_dirs[0] = vertex_dirs[1];
478 vertex_dirs[1] = vertex_dirs[2];
479 vertex_dirs[2] = vertex_dirs[3];
489 vertex_dirs[0] = vertex_dirs[3];
490 vertex_dirs[3] = vertex_dirs[2];
491 vertex_dirs[2] = vertex_dirs[1];
503 vertex_dirs[0] = vertex_dirs[1];
504 vertex_dirs[1] = vertex_dirs[2];
505 vertex_dirs[2] = vertex_dirs[3];
517 vertex_dirs[0] = vertex_dirs[3];
518 vertex_dirs[3] = vertex_dirs[2];
519 vertex_dirs[2] = vertex_dirs[1];
531 vertex_dirs[0] = vertex_dirs[1];
532 vertex_dirs[1] = vertex_dirs[2];
533 vertex_dirs[2] = vertex_dirs[3];
555 for (u16 i = 0; i < 4; i++) {
557 BS / 2 * vertex_dirs[i].X,
558 BS / 2 * vertex_dirs[i].Y,
559 BS / 2 * vertex_dirs[i].Z
563 for (v3f &vpos : vertex_pos) {
570 f32 abs_scale = 1.0f;
571 if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X;
572 else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y;
573 else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z;
575 v3f normal(dir.X, dir.Y, dir.Z);
577 u16 li[4] = { li0, li1, li2, li3 };
581 for (u8 i = 0; i < 4; i++) {
583 night[i] = li[i] & 0xFF;
586 bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2])
587 < abs(day[1] - day[3]) + abs(night[1] - night[3]);
590 core::vector2d<f32>(x0 + w * abs_scale, y0 + h),
591 core::vector2d<f32>(x0, y0 + h),
592 core::vector2d<f32>(x0, y0),
593 core::vector2d<f32>(x0 + w * abs_scale, y0) };
595 // equivalent to dest.push_back(FastFace()) but faster
597 FastFace& face = *dest.rbegin();
599 for (u8 i = 0; i < 4; i++) {
600 video::SColor c = encode_light(li[i], tile.emissive_light);
601 if (!tile.emissive_light)
602 applyFacesShading(c, normal);
604 face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]);
608 Revert triangles for nicer looking gradient if the
609 brightness of vertices 1 and 3 differ less than
610 the brightness of vertices 0 and 2.
612 face.vertex_0_2_connected = vertex_0_2_connected;
617 Nodes make a face if contents differ and solidness differs.
620 1: Face uses m1's content
621 2: Face uses m2's content
622 equivalent: Whether the blocks share the same face (eg. water and glass)
624 TODO: Add 3: Both faces drawn with backface culling, remove equivalent
626 static u8 face_contents(content_t m1, content_t m2, bool *equivalent,
627 const NodeDefManager *ndef)
631 if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
634 const ContentFeatures &f1 = ndef->get(m1);
635 const ContentFeatures &f2 = ndef->get(m2);
637 // Contents don't differ for different forms of same liquid
638 if (f1.sameLiquidRender(f2))
641 u8 c1 = f1.solidness;
642 u8 c2 = f2.solidness;
648 c1 = f1.visual_solidness;
650 c2 = f2.visual_solidness;
654 // If same solidness, liquid takes precense
655 if (f1.isLiquidRender())
657 if (f2.isLiquidRender())
668 Gets nth node tile (0 <= n <= 5).
670 void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile)
672 const NodeDefManager *ndef = data->m_client->ndef();
673 const ContentFeatures &f = ndef->get(mn);
674 tile = f.tiles[tileindex];
675 bool has_crack = p == data->m_crack_pos_relative;
676 for (TileLayer &layer : tile.layers) {
677 if (layer.texture_id == 0)
679 if (!layer.has_color)
680 mn.getColor(f, &(layer.color));
681 // Apply temporary crack
683 layer.material_flags |= MATERIAL_FLAG_CRACK;
688 Gets node tile given a face direction.
690 void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile)
692 const NodeDefManager *ndef = data->m_client->ndef();
694 // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
695 // (0,0,1), (0,0,-1) or (0,0,0)
696 assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1);
698 // Convert direction to single integer for table lookup
703 // 4 = invalid, treat as (0,0,0)
707 u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2;
709 // Get rotation for things like chests
710 u8 facedir = mn.getFaceDir(ndef, true);
712 static const u16 dir_to_tile[24 * 16] =
714 // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation
715 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3
716 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 ,
717 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 ,
718 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 ,
720 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7
721 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 ,
722 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 ,
723 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 ,
725 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11
726 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 ,
727 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 ,
728 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 ,
730 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15
731 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 ,
732 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 ,
733 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 ,
735 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19
736 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 ,
737 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 ,
738 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 ,
740 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23
741 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 ,
742 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 ,
743 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2
746 u16 tile_index = facedir * 16 + dir_i;
747 getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile);
748 tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
751 static void getTileInfo(
755 const v3s16 &face_dir,
759 v3s16 &face_dir_corrected,
765 VoxelManipulator &vmanip = data->m_vmanip;
766 const NodeDefManager *ndef = data->m_client->ndef();
767 v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
769 const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
771 // Don't even try to get n1 if n0 is already CONTENT_IGNORE
772 if (n0.getContent() == CONTENT_IGNORE) {
777 const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
779 if (n1.getContent() == CONTENT_IGNORE) {
785 bool equivalent = false;
786 u8 mf = face_contents(n0.getContent(), n1.getContent(),
800 face_dir_corrected = face_dir;
803 p_corrected = p + face_dir;
804 face_dir_corrected = -face_dir;
807 getNodeTile(n, p_corrected, face_dir_corrected, data, tile);
808 const ContentFeatures &f = ndef->get(n);
810 tile.emissive_light = f.light_source;
812 // eg. water and glass
814 for (TileLayer &layer : tile.layers)
815 layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
818 if (!data->m_smooth_lighting) {
819 lights[0] = lights[1] = lights[2] = lights[3] =
820 getFaceLight(n0, n1, face_dir, ndef);
822 v3s16 vertex_dirs[4];
823 getNodeVertexDirs(face_dir_corrected, vertex_dirs);
825 v3s16 light_p = blockpos_nodes + p_corrected;
826 for (u16 i = 0; i < 4; i++)
827 lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data);
833 translate_dir: unit vector with only one of x, y or z
834 face_dir: unit vector with only one of x, y or z
836 static void updateFastFaceRow(
838 const v3s16 &&startpos,
840 const v3f &&translate_dir_f,
841 const v3s16 &&face_dir,
842 std::vector<FastFace> &dest)
844 static thread_local const bool waving_liquids =
845 g_settings->getBool("enable_shaders") &&
846 g_settings->getBool("enable_waving_water");
848 static thread_local const bool force_not_tiling =
849 g_settings->getBool("enable_dynamic_shadows");
853 u16 continuous_tiles_count = 1;
855 bool makes_face = false;
857 v3s16 face_dir_corrected;
858 u16 lights[4] = {0, 0, 0, 0};
862 // Get info of first tile
863 getTileInfo(data, p, face_dir,
864 makes_face, p_corrected, face_dir_corrected,
865 lights, waving, tile);
867 // Unroll this variable which has a significant build cost
869 for (u16 j = 0; j < data->side_length; j++) {
870 // If tiling can be done, this is set to false in the next step
871 bool next_is_different = true;
873 bool next_makes_face = false;
874 v3s16 next_p_corrected;
875 v3s16 next_face_dir_corrected;
876 u16 next_lights[4] = {0, 0, 0, 0};
878 // If at last position, there is nothing to compare to and
879 // the face must be drawn anyway
880 if (j != data->side_length - 1) {
883 getTileInfo(data, p, face_dir,
884 next_makes_face, next_p_corrected,
885 next_face_dir_corrected, next_lights,
889 if (!force_not_tiling
890 && next_makes_face == makes_face
891 && next_p_corrected == p_corrected + translate_dir
892 && next_face_dir_corrected == face_dir_corrected
893 && memcmp(next_lights, lights, sizeof(lights)) == 0
894 // Don't apply fast faces to waving water.
895 && (waving != 3 || !waving_liquids)
896 && next_tile.isTileable(tile)) {
897 next_is_different = false;
898 continuous_tiles_count++;
901 if (next_is_different) {
903 Create a face if there should be one
906 // Floating point conversion of the position vector
907 v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z);
908 // Center point of face (kind of)
909 v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f)
913 if (translate_dir.X != 0)
914 scale.X = continuous_tiles_count;
915 if (translate_dir.Y != 0)
916 scale.Y = continuous_tiles_count;
917 if (translate_dir.Z != 0)
918 scale.Z = continuous_tiles_count;
920 makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
921 pf, sp, face_dir_corrected, scale, dest);
922 g_profiler->avg("Meshgen: Tiles per face [#]", continuous_tiles_count);
925 continuous_tiles_count = 1;
928 makes_face = next_makes_face;
929 p_corrected = next_p_corrected;
930 face_dir_corrected = next_face_dir_corrected;
931 memcpy(lights, next_lights, sizeof(lights));
932 if (next_is_different)
933 tile = std::move(next_tile); // faster than copy
937 static void updateAllFastFaceRows(MeshMakeData *data,
938 std::vector<FastFace> &dest)
941 Go through every y,z and get top(y+) faces in rows of x+
943 for (s16 y = 0; y < data->side_length; y++)
944 for (s16 z = 0; z < data->side_length; z++)
945 updateFastFaceRow(data,
947 v3s16(1, 0, 0), //dir
949 v3s16(0, 1, 0), //face dir
953 Go through every x,y and get right(x+) faces in rows of z+
955 for (s16 x = 0; x < data->side_length; x++)
956 for (s16 y = 0; y < data->side_length; y++)
957 updateFastFaceRow(data,
959 v3s16(0, 0, 1), //dir
961 v3s16(1, 0, 0), //face dir
965 Go through every y,z and get back(z+) faces in rows of x+
967 for (s16 z = 0; z < data->side_length; z++)
968 for (s16 y = 0; y < data->side_length; y++)
969 updateFastFaceRow(data,
971 v3s16(1, 0, 0), //dir
973 v3s16(0, 0, 1), //face dir
977 static void applyTileColor(PreMeshBuffer &pmb)
979 video::SColor tc = pmb.layer.color;
980 if (tc == video::SColor(0xFFFFFFFF))
982 for (video::S3DVertex &vertex : pmb.vertices) {
983 video::SColor *c = &vertex.Color;
984 c->set(c->getAlpha(),
985 c->getRed() * tc.getRed() / 255,
986 c->getGreen() * tc.getGreen() / 255,
987 c->getBlue() * tc.getBlue() / 255);
995 void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles, u16 side_length)
997 this->triangles = triangles;
1001 // assert that triangle index can fit into s32
1002 assert(triangles->size() <= 0x7FFFFFFFL);
1003 std::vector<s32> indexes;
1004 indexes.reserve(triangles->size());
1005 for (u32 i = 0; i < triangles->size(); i++)
1006 indexes.push_back(i);
1008 if (!indexes.empty()) {
1009 // Start in the center of the block with increment of one quarter in each direction
1010 root = buildTree(v3f(1, 0, 0), v3f((side_length + 1) * 0.5f * BS), side_length * 0.25f * BS, indexes, 0);
1017 * @brief Find a candidate plane to split a set of triangles in two
1019 * The candidate plane is represented by one of the triangles from the set.
1021 * @param list Vector of indexes of the triangles in the set
1022 * @param triangles Vector of all triangles in the BSP tree
1023 * @return Address of the triangle that represents the proposed split plane
1025 static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
1027 // find the center of the cluster.
1028 v3f center(0, 0, 0);
1029 size_t n = list.size();
1030 for (s32 i : list) {
1031 center += triangles[i].centroid / n;
1034 // find the triangle with the largest area and closest to the center
1035 const MeshTriangle *candidate_triangle = &triangles[list[0]];
1036 const MeshTriangle *ith_triangle;
1037 for (s32 i : list) {
1038 ith_triangle = &triangles[i];
1039 if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
1040 (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
1041 ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
1042 candidate_triangle = ith_triangle;
1045 return candidate_triangle;
1048 s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
1050 // if the list is empty, don't bother
1054 // if there is only one triangle, or the delta is insanely small, this is a leaf node
1055 if (list.size() == 1 || delta < 0.01) {
1056 nodes.emplace_back(normal, origin, list, -1, -1);
1057 return nodes.size() - 1;
1060 std::vector<s32> front_list;
1061 std::vector<s32> back_list;
1062 std::vector<s32> node_list;
1065 for (s32 i : list) {
1066 const MeshTriangle &triangle = (*triangles)[i];
1067 float factor = normal.dotProduct(triangle.centroid - origin);
1069 node_list.push_back(i);
1070 else if (factor > 0)
1071 front_list.push_back(i);
1073 back_list.push_back(i);
1076 // define the new split-plane
1077 v3f candidate_normal(normal.Z, normal.X, normal.Y);
1078 float candidate_delta = delta;
1080 candidate_delta /= 2;
1082 s32 front_index = -1;
1083 s32 back_index = -1;
1085 if (!front_list.empty()) {
1086 v3f next_normal = candidate_normal;
1087 v3f next_origin = origin + delta * normal;
1088 float next_delta = candidate_delta;
1089 if (next_delta < 5) {
1090 const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
1091 next_normal = candidate->getNormal();
1092 next_origin = candidate->centroid;
1094 front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
1096 // if there are no other triangles, don't create a new node
1097 if (back_list.empty() && node_list.empty())
1101 if (!back_list.empty()) {
1102 v3f next_normal = candidate_normal;
1103 v3f next_origin = origin - delta * normal;
1104 float next_delta = candidate_delta;
1105 if (next_delta < 5) {
1106 const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
1107 next_normal = candidate->getNormal();
1108 next_origin = candidate->centroid;
1111 back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
1113 // if there are no other triangles, don't create a new node
1114 if (front_list.empty() && node_list.empty())
1118 nodes.emplace_back(normal, origin, node_list, front_index, back_index);
1120 return nodes.size() - 1;
1123 void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
1125 if (node < 0) return; // recursion break;
1127 const TreeNode &n = nodes[node];
1128 float factor = n.normal.dotProduct(viewpoint - n.origin);
1131 traverse(n.back_ref, viewpoint, output);
1133 traverse(n.front_ref, viewpoint, output);
1136 for (s32 i : n.triangle_refs)
1137 output.push_back(i);
1140 traverse(n.front_ref, viewpoint, output);
1142 traverse(n.back_ref, viewpoint, output);
1151 void PartialMeshBuffer::beforeDraw() const
1153 // Patch the indexes in the mesh buffer before draw
1154 m_buffer->Indices = std::move(m_vertex_indexes);
1155 m_buffer->setDirty(scene::EBT_INDEX);
1158 void PartialMeshBuffer::afterDraw() const
1160 // Take the data back
1161 m_vertex_indexes = m_buffer->Indices.steal();
1168 MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
1169 m_tsrc(data->m_client->getTextureSource()),
1170 m_shdrsrc(data->m_client->getShaderSource()),
1171 m_animation_force_timer(0), // force initial animation
1173 m_last_daynight_ratio((u32) -1)
1175 for (auto &m : m_mesh)
1176 m = new scene::SMesh();
1177 m_enable_shaders = data->m_use_shaders;
1178 m_enable_vbo = g_settings->getBool("enable_vbo");
1180 v3s16 bp = data->m_blockpos;
1181 // Only generate minimap mapblocks at even coordinates.
1182 if (((bp.X | bp.Y | bp.Z) & 1) == 0 && data->m_client->getMinimap()) {
1183 m_minimap_mapblocks.resize(8, nullptr);
1186 // See also client.cpp for the code that reads the array of minimap blocks.
1187 for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++)
1188 for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++)
1189 for (ofs.X = 0; ofs.X <= 1; ofs.X++) {
1190 v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
1191 if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
1192 MinimapMapblock *block = new MinimapMapblock;
1193 m_minimap_mapblocks[ofs.Z * 4 + ofs.Y * 2 + ofs.X] = block;
1194 block->getMinimapNodes(&data->m_vmanip, p);
1199 // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
1200 // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated)
1201 //TimeTaker timer1("MapBlockMesh()");
1203 std::vector<FastFace> fastfaces_new;
1204 fastfaces_new.reserve(512);
1207 We are including the faces of the trailing edges of the block.
1208 This means that when something changes, the caller must
1209 also update the meshes of the blocks at the leading edges.
1211 NOTE: This is the slowest part of this method.
1214 // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
1215 //TimeTaker timer2("updateAllFastFaceRows()");
1216 updateAllFastFaceRows(data, fastfaces_new);
1221 Convert FastFaces to MeshCollector
1224 v3f offset = intToFloat((data->m_blockpos - data->m_blockpos / 8 * 8) * MAP_BLOCKSIZE, BS);
1225 MeshCollector collector(m_bounding_sphere_center, offset);
1228 // avg 0ms (100ms spikes when loading textures the first time)
1229 // (NOTE: probably outdated)
1230 //TimeTaker timer2("MeshCollector building");
1232 for (const FastFace &f : fastfaces_new) {
1233 static const u16 indices[] = {0, 1, 2, 2, 3, 0};
1234 static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1};
1235 const u16 *indices_p =
1236 f.vertex_0_2_connected ? indices : indices_alternate;
1237 collector.append(f.tile, f.vertices, 4, indices_p, 6);
1242 Add special graphics:
1250 MapblockMeshGenerator(data, &collector,
1251 data->m_client->getSceneManager()->getMeshManipulator()).generate();
1255 Convert MeshCollector to SMesh
1258 const bool desync_animations = g_settings->getBool(
1259 "desynchronize_mapblock_texture_animation");
1261 m_bounding_radius = std::sqrt(collector.m_bounding_radius_sq);
1263 for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
1264 for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
1266 PreMeshBuffer &p = collector.prebuffers[layer][i];
1270 // Generate animation data
1272 if (p.layer.material_flags & MATERIAL_FLAG_CRACK) {
1273 // Find the texture name plus ^[crack:N:
1274 std::ostringstream os(std::ios::binary);
1275 os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack";
1276 if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
1277 os << "o"; // use ^[cracko
1278 u8 tiles = p.layer.scale;
1280 os << ":" << (u32)tiles;
1281 os << ":" << (u32)p.layer.animation_frame_count << ":";
1282 m_crack_materials.insert(std::make_pair(
1283 std::pair<u8, u32>(layer, i), os.str()));
1284 // Replace tile texture with the cracked one
1285 p.layer.texture = m_tsrc->getTextureForMesh(
1287 &p.layer.texture_id);
1289 // - Texture animation
1290 if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
1291 // Add to MapBlockMesh in order to animate these tiles
1292 auto &info = m_animation_info[{layer, i}];
1293 info.tile = p.layer;
1295 if (desync_animations) {
1296 // Get starting position from noise
1298 100000 * (2.0 + noise3d(
1299 data->m_blockpos.X, data->m_blockpos.Y,
1300 data->m_blockpos.Z, 0));
1302 // Play all synchronized
1303 info.frame_offset = 0;
1305 // Replace tile texture with the first animation frame
1306 p.layer.texture = (*p.layer.frames)[0].texture;
1309 if (!m_enable_shaders) {
1310 // Extract colors for day-night animation
1311 // Dummy sunlight to handle non-sunlit areas
1312 video::SColorf sunlight;
1313 get_sunlight_color(&sunlight, 0);
1315 std::map<u32, video::SColor> colors;
1316 const u32 vertex_count = p.vertices.size();
1317 for (u32 j = 0; j < vertex_count; j++) {
1318 video::SColor *vc = &p.vertices[j].Color;
1319 video::SColor copy = *vc;
1320 if (vc->getAlpha() == 0) // No sunlight - no need to animate
1321 final_color_blend(vc, copy, sunlight); // Finalize color
1322 else // Record color to animate
1325 // The sunlight ratio has been stored,
1326 // delete alpha (for the final rendering).
1329 if (!colors.empty())
1330 m_daynight_diffs[{layer, i}] = std::move(colors);
1334 video::SMaterial material;
1335 material.setFlag(video::EMF_LIGHTING, false);
1336 material.setFlag(video::EMF_BACK_FACE_CULLING, true);
1337 material.setFlag(video::EMF_BILINEAR_FILTER, false);
1338 material.setFlag(video::EMF_FOG_ENABLE, true);
1339 material.setTexture(0, p.layer.texture);
1341 if (m_enable_shaders) {
1342 material.MaterialType = m_shdrsrc->getShaderInfo(
1343 p.layer.shader_id).material;
1344 p.layer.applyMaterialOptionsWithShaders(material);
1345 if (p.layer.normal_texture)
1346 material.setTexture(1, p.layer.normal_texture);
1347 material.setTexture(2, p.layer.flags_texture);
1349 p.layer.applyMaterialOptions(material);
1352 scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer];
1354 scene::SMeshBuffer *buf = new scene::SMeshBuffer();
1355 buf->Material = material;
1356 if (p.layer.isTransparent()) {
1357 buf->append(&p.vertices[0], p.vertices.size(), nullptr, 0);
1361 m_transparent_triangles.reserve(p.indices.size() / 3);
1362 for (u32 i = 0; i < p.indices.size(); i += 3) {
1363 t.p1 = p.indices[i];
1364 t.p2 = p.indices[i + 1];
1365 t.p3 = p.indices[i + 2];
1366 t.updateAttributes();
1367 m_transparent_triangles.push_back(t);
1370 buf->append(&p.vertices[0], p.vertices.size(),
1371 &p.indices[0], p.indices.size());
1373 mesh->addMeshBuffer(buf);
1377 if (m_mesh[layer]) {
1378 // Use VBO for mesh (this just would set this for ever buffer)
1380 m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC);
1384 //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
1385 m_bsp_tree.buildTree(&m_transparent_triangles, data->side_length);
1387 // Check if animation is required for this mesh
1389 !m_crack_materials.empty() ||
1390 !m_daynight_diffs.empty() ||
1391 !m_animation_info.empty();
1394 MapBlockMesh::~MapBlockMesh()
1396 for (scene::IMesh *m : m_mesh) {
1397 #if IRRLICHT_VERSION_MT_REVISION < 5
1399 for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
1400 scene::IMeshBuffer *buf = m->getMeshBuffer(i);
1401 RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
1407 for (MinimapMapblock *block : m_minimap_mapblocks)
1411 bool MapBlockMesh::animate(bool faraway, float time, int crack,
1414 if (!m_has_animation) {
1415 m_animation_force_timer = 100000;
1419 m_animation_force_timer = myrand_range(5, 100);
1422 if (crack != m_last_crack) {
1423 for (auto &crack_material : m_crack_materials) {
1424 scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
1425 getMeshBuffer(crack_material.first.second);
1427 // Create new texture name from original
1428 std::string s = crack_material.second + itos(crack);
1429 u32 new_texture_id = 0;
1430 video::ITexture *new_texture =
1431 m_tsrc->getTextureForMesh(s, &new_texture_id);
1432 buf->getMaterial().setTexture(0, new_texture);
1434 // If the current material is also animated, update animation info
1435 auto anim_it = m_animation_info.find(crack_material.first);
1436 if (anim_it != m_animation_info.end()) {
1437 TileLayer &tile = anim_it->second.tile;
1438 tile.texture = new_texture;
1439 tile.texture_id = new_texture_id;
1440 // force animation update
1441 anim_it->second.frame = -1;
1445 m_last_crack = crack;
1448 // Texture animation
1449 for (auto &it : m_animation_info) {
1450 const TileLayer &tile = it.second.tile;
1451 // Figure out current frame
1452 int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
1453 + it.second.frame_offset) % tile.animation_frame_count;
1454 // If frame doesn't change, skip
1455 if (frameno == it.second.frame)
1458 it.second.frame = frameno;
1460 scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
1462 const FrameSpec &frame = (*tile.frames)[frameno];
1463 buf->getMaterial().setTexture(0, frame.texture);
1464 if (m_enable_shaders) {
1465 if (frame.normal_texture)
1466 buf->getMaterial().setTexture(1, frame.normal_texture);
1467 buf->getMaterial().setTexture(2, frame.flags_texture);
1471 // Day-night transition
1472 if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) {
1473 // Force reload mesh to VBO
1475 for (scene::IMesh *m : m_mesh)
1477 video::SColorf day_color;
1478 get_sunlight_color(&day_color, daynight_ratio);
1480 for (auto &daynight_diff : m_daynight_diffs) {
1481 scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]->
1482 getMeshBuffer(daynight_diff.first.second);
1483 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1484 for (const auto &j : daynight_diff.second)
1485 final_color_blend(&(vertices[j.first].Color), j.second,
1488 m_last_daynight_ratio = daynight_ratio;
1494 void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
1496 // nothing to do if the entire block is opaque
1497 if (m_transparent_triangles.empty())
1500 v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
1501 v3f rel_camera_pos = camera_pos - block_posf;
1503 std::vector<s32> triangle_refs;
1504 m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
1506 // arrange index sequences into partial buffers
1507 m_transparent_buffers.clear();
1509 scene::SMeshBuffer *current_buffer = nullptr;
1510 std::vector<u16> current_strain;
1511 for (auto i : triangle_refs) {
1512 const auto &t = m_transparent_triangles[i];
1513 if (current_buffer != t.buffer) {
1514 if (current_buffer) {
1515 m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1516 current_strain.clear();
1518 current_buffer = t.buffer;
1520 current_strain.push_back(t.p1);
1521 current_strain.push_back(t.p2);
1522 current_strain.push_back(t.p3);
1525 if (!current_strain.empty())
1526 m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1529 void MapBlockMesh::consolidateTransparentBuffers()
1531 m_transparent_buffers.clear();
1533 scene::SMeshBuffer *current_buffer = nullptr;
1534 std::vector<u16> current_strain;
1536 // use the fact that m_transparent_triangles is already arranged by buffer
1537 for (const auto &t : m_transparent_triangles) {
1538 if (current_buffer != t.buffer) {
1539 if (current_buffer != nullptr) {
1540 this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1541 current_strain.clear();
1543 current_buffer = t.buffer;
1545 current_strain.push_back(t.p1);
1546 current_strain.push_back(t.p2);
1547 current_strain.push_back(t.p3);
1550 if (!current_strain.empty()) {
1551 this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1555 video::SColor encode_light(u16 light, u8 emissive_light)
1558 u32 day = (light & 0xff);
1559 u32 night = (light >> 8);
1560 // Add emissive light
1561 night += emissive_light * 2.5f;
1564 // Since we don't know if the day light is sunlight or
1565 // artificial light, assume it is artificial when the night
1566 // light bank is also lit.
1571 u32 sum = day + night;
1572 // Ratio of sunlight:
1575 r = day * 255 / sum;
1579 float b = (day + night) / 2;
1580 return video::SColor(r, b, b, b);
1583 std::unordered_map<v3s16, u8> get_solid_sides(MeshMakeData *data)
1585 std::unordered_map<v3s16, u8> results;
1588 for (ofs.X = 0; ofs.X < 2; ofs.X++)
1589 for (ofs.Y = 0; ofs.Y < 2; ofs.Y++)
1590 for (ofs.Z = 0; ofs.Z < 2; ofs.Z++) {
1591 v3s16 blockpos = data->m_blockpos + ofs;
1592 v3s16 blockpos_nodes = blockpos * MAP_BLOCKSIZE;
1593 const NodeDefManager *ndef = data->m_client->ndef();
1595 u8 result = 0x3F; // all sides solid;
1597 for (s16 i = 0; i < MAP_BLOCKSIZE && result != 0; i++)
1598 for (s16 j = 0; j < MAP_BLOCKSIZE && result != 0; j++) {
1599 v3s16 positions[6] = {
1601 v3s16(MAP_BLOCKSIZE - 1, i, j),
1603 v3s16(i, MAP_BLOCKSIZE - 1, j),
1605 v3s16(i, j, MAP_BLOCKSIZE - 1)
1608 for (u8 k = 0; k < 6; k++) {
1609 const MapNode &top = data->m_vmanip.getNodeRefUnsafe(blockpos_nodes + positions[k]);
1610 if (ndef->get(top).solidness != 2)
1611 result &= ~(1 << k);
1615 results[blockpos] = result;