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) * MAP_BLOCKSIZE*2-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::fill(MapBlock *block)
68 fillBlockDataBegin(block->getPos());
70 fillBlockData(v3s16(0,0,0), block->getData());
72 // Get map for reading neighbor blocks
73 Map *map = block->getParent();
75 for (const v3s16 &dir : g_26dirs) {
76 v3s16 bp = m_blockpos + dir;
77 MapBlock *b = map->getBlockNoCreateNoEx(bp);
79 fillBlockData(dir, b->getData());
83 void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos)
86 m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE;
89 void MeshMakeData::setSmoothLighting(bool smooth_lighting)
91 m_smooth_lighting = smooth_lighting && ! g_settings->getBool("fullbright");
95 Light and vertex color functions
99 Calculate non-smooth lighting at interior of node.
102 static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment,
103 const NodeDefManager *ndef)
105 u8 light = n.getLight(bank, ndef);
107 light = rangelim(light + increment, 0, LIGHT_SUN);
108 if(g_settings->getBool("fullbright"))
110 return decode_light(light);
114 Calculate non-smooth lighting at interior of node.
117 u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef)
119 u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef);
120 u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef);
121 return day | (night << 8);
125 Calculate non-smooth lighting at face of node.
128 static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2,
129 v3s16 face_dir, const NodeDefManager *ndef)
132 u8 l1 = n.getLight(bank, ndef);
133 u8 l2 = n2.getLight(bank, ndef);
139 // Boost light level for light sources
140 u8 light_source = MYMAX(ndef->get(n).light_source,
141 ndef->get(n2).light_source);
142 if(light_source > light)
143 light = light_source;
144 if(g_settings->getBool("fullbright"))
146 return decode_light(light);
150 Calculate non-smooth lighting at face of node.
153 u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
154 const NodeDefManager *ndef)
156 u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef);
157 u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef);
158 return day | (night << 8);
162 Calculate smooth lighting at the XYZ- corner of p.
165 static u16 getSmoothLightCombined(const v3s16 &p,
166 const std::array<v3s16,8> &dirs, MeshMakeData *data)
168 const NodeDefManager *ndef = data->m_client->ndef();
170 u16 ambient_occlusion = 0;
172 u8 light_source_max = 0;
175 bool direct_sunlight = false;
177 auto add_node = [&] (u8 i, bool obstructed = false) -> bool {
182 MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]);
183 if (n.getContent() == CONTENT_IGNORE)
185 const ContentFeatures &f = ndef->get(n);
186 if (f.light_source > light_source_max)
187 light_source_max = f.light_source;
188 // Check f.solidness because fast-style leaves look better this way
189 if (f.param_type == CPT_LIGHT && f.solidness != 2) {
190 u8 light_level_day = n.getLightNoChecks(LIGHTBANK_DAY, &f);
191 u8 light_level_night = n.getLightNoChecks(LIGHTBANK_NIGHT, &f);
192 if (light_level_day == LIGHT_SUN)
193 direct_sunlight = true;
194 light_day += decode_light(light_level_day);
195 light_night += decode_light(light_level_night);
200 return f.light_propagates;
203 bool obstructed[4] = { true, true, true, true };
205 bool opaque1 = !add_node(1);
206 bool opaque2 = !add_node(2);
207 bool opaque3 = !add_node(3);
208 obstructed[0] = opaque1 && opaque2;
209 obstructed[1] = opaque1 && opaque3;
210 obstructed[2] = opaque2 && opaque3;
211 for (u8 k = 0; k < 3; ++k)
212 if (add_node(k + 4, obstructed[k]))
213 obstructed[3] = false;
214 if (add_node(7, obstructed[3])) { // wrap light around nodes
215 ambient_occlusion -= 3;
216 for (u8 k = 0; k < 3; ++k)
217 add_node(k + 4, !obstructed[k]);
220 if (light_count == 0) {
221 light_day = light_night = 0;
223 light_day /= light_count;
224 light_night /= light_count;
227 // boost direct sunlight, if any
231 // Boost brightness around light sources
232 bool skip_ambient_occlusion_day = false;
233 if (decode_light(light_source_max) >= light_day) {
234 light_day = decode_light(light_source_max);
235 skip_ambient_occlusion_day = true;
238 bool skip_ambient_occlusion_night = false;
239 if(decode_light(light_source_max) >= light_night) {
240 light_night = decode_light(light_source_max);
241 skip_ambient_occlusion_night = true;
244 if (ambient_occlusion > 4) {
245 static thread_local const float ao_gamma = rangelim(
246 g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0);
248 // Table of gamma space multiply factors.
249 static thread_local const float light_amount[3] = {
250 powf(0.75, 1.0 / ao_gamma),
251 powf(0.5, 1.0 / ao_gamma),
252 powf(0.25, 1.0 / ao_gamma)
255 //calculate table index for gamma space multiplier
256 ambient_occlusion -= 5;
258 if (!skip_ambient_occlusion_day)
259 light_day = rangelim(core::round32(
260 light_day * light_amount[ambient_occlusion]), 0, 255);
261 if (!skip_ambient_occlusion_night)
262 light_night = rangelim(core::round32(
263 light_night * light_amount[ambient_occlusion]), 0, 255);
266 return light_day | (light_night << 8);
270 Calculate smooth lighting at the given corner of p.
272 Node at p is solid, and thus the lighting is face-dependent.
274 u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data)
276 return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data);
280 Calculate smooth lighting at the given corner of p.
282 Node at p is not solid, and the lighting is not face-dependent.
284 u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data)
286 const std::array<v3s16,8> dirs = {{
287 // Always shine light
294 v3s16(corner.X,corner.Y,0),
295 v3s16(corner.X,0,corner.Z),
296 v3s16(0,corner.Y,corner.Z),
297 v3s16(corner.X,corner.Y,corner.Z)
299 return getSmoothLightCombined(p, dirs, data);
302 void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){
303 f32 rg = daynight_ratio / 1000.0f - 0.04f;
304 f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f;
310 void final_color_blend(video::SColor *result,
311 u16 light, u32 daynight_ratio)
313 video::SColorf dayLight;
314 get_sunlight_color(&dayLight, daynight_ratio);
315 final_color_blend(result,
316 encode_light(light, 0), dayLight);
319 void final_color_blend(video::SColor *result,
320 const video::SColor &data, const video::SColorf &dayLight)
322 static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f);
324 video::SColorf c(data);
327 f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f;
328 f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f;
329 f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f;
331 // Emphase blue a bit in darker places
332 // Each entry of this array represents a range of 8 blue levels
333 static const u8 emphase_blue_when_dark[32] = {
334 1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
335 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
338 b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255),
339 0, 255) / 8] / 255.0f;
341 result->setRed(core::clamp((s32) (r * 255.0f), 0, 255));
342 result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255));
343 result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255));
347 Mesh generation helpers
350 // This table is moved outside getNodeVertexDirs to avoid the compiler using
351 // a mutex to initialize this table at runtime right in the hot path.
352 // For details search the internet for "cxa_guard_acquire".
353 static const v3s16 vertex_dirs_table[] = {
355 v3s16( 1,-1, 1), v3s16( 1,-1,-1),
356 v3s16( 1, 1,-1), v3s16( 1, 1, 1),
358 v3s16( 1, 1,-1), v3s16(-1, 1,-1),
359 v3s16(-1, 1, 1), v3s16( 1, 1, 1),
361 v3s16(-1,-1, 1), v3s16( 1,-1, 1),
362 v3s16( 1, 1, 1), v3s16(-1, 1, 1),
364 v3s16(), v3s16(), v3s16(), v3s16(),
366 v3s16( 1,-1,-1), v3s16(-1,-1,-1),
367 v3s16(-1, 1,-1), v3s16( 1, 1,-1),
369 v3s16( 1,-1, 1), v3s16(-1,-1, 1),
370 v3s16(-1,-1,-1), v3s16( 1,-1,-1),
372 v3s16(-1,-1,-1), v3s16(-1,-1, 1),
373 v3s16(-1, 1, 1), v3s16(-1, 1,-1)
377 vertex_dirs: v3s16[4]
379 static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
382 If looked from outside the node towards the face, the corners are:
389 // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
391 assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z == 1);
393 // Convert direction to single integer for table lookup
394 u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7;
397 #if defined(__GNUC__) && !defined(__clang__)
398 #pragma GCC diagnostic push
400 #pragma GCC diagnostic ignored "-Wclass-memaccess"
403 memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16));
404 #if defined(__GNUC__) && !defined(__clang__)
405 #pragma GCC diagnostic pop
409 static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
411 if (dir.X > 0 || dir.Y != 0 || dir.Z < 0)
413 if (dir == v3s16(0,0,1)) {
416 } else if (dir == v3s16(0,0,-1)) {
419 } else if (dir == v3s16(1,0,0)) {
422 } else if (dir == v3s16(-1,0,0)) {
425 } else if (dir == v3s16(0,1,0)) {
428 } else if (dir == v3s16(0,-1,0)) {
437 video::S3DVertex vertices[4]; // Precalculated vertices
439 * The face is divided into two triangles. If this is true,
440 * vertices 0 and 2 are connected, othervise vertices 1 and 3
443 bool vertex_0_2_connected;
446 static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3,
447 const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector<FastFace> &dest)
449 // Position is at the center of the cube.
458 v3s16 vertex_dirs[4];
459 getNodeVertexDirs(dir, vertex_dirs);
460 if (tile.world_aligned)
461 getNodeTextureCoords(tp, scale, dir, &x0, &y0);
465 switch (tile.rotation) {
470 vertex_dirs[0] = vertex_dirs[3];
471 vertex_dirs[3] = vertex_dirs[2];
472 vertex_dirs[2] = vertex_dirs[1];
482 vertex_dirs[0] = vertex_dirs[2];
485 vertex_dirs[1] = vertex_dirs[3];
496 vertex_dirs[0] = vertex_dirs[1];
497 vertex_dirs[1] = vertex_dirs[2];
498 vertex_dirs[2] = vertex_dirs[3];
508 vertex_dirs[0] = vertex_dirs[3];
509 vertex_dirs[3] = vertex_dirs[2];
510 vertex_dirs[2] = vertex_dirs[1];
522 vertex_dirs[0] = vertex_dirs[1];
523 vertex_dirs[1] = vertex_dirs[2];
524 vertex_dirs[2] = vertex_dirs[3];
536 vertex_dirs[0] = vertex_dirs[3];
537 vertex_dirs[3] = vertex_dirs[2];
538 vertex_dirs[2] = vertex_dirs[1];
550 vertex_dirs[0] = vertex_dirs[1];
551 vertex_dirs[1] = vertex_dirs[2];
552 vertex_dirs[2] = vertex_dirs[3];
574 for (u16 i = 0; i < 4; i++) {
576 BS / 2 * vertex_dirs[i].X,
577 BS / 2 * vertex_dirs[i].Y,
578 BS / 2 * vertex_dirs[i].Z
582 for (v3f &vpos : vertex_pos) {
589 f32 abs_scale = 1.0f;
590 if (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X;
591 else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y;
592 else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z;
594 v3f normal(dir.X, dir.Y, dir.Z);
596 u16 li[4] = { li0, li1, li2, li3 };
600 for (u8 i = 0; i < 4; i++) {
602 night[i] = li[i] & 0xFF;
605 bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2])
606 < abs(day[1] - day[3]) + abs(night[1] - night[3]);
609 core::vector2d<f32>(x0 + w * abs_scale, y0 + h),
610 core::vector2d<f32>(x0, y0 + h),
611 core::vector2d<f32>(x0, y0),
612 core::vector2d<f32>(x0 + w * abs_scale, y0) };
614 // equivalent to dest.push_back(FastFace()) but faster
616 FastFace& face = *dest.rbegin();
618 for (u8 i = 0; i < 4; i++) {
619 video::SColor c = encode_light(li[i], tile.emissive_light);
620 if (!tile.emissive_light)
621 applyFacesShading(c, normal);
623 face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]);
627 Revert triangles for nicer looking gradient if the
628 brightness of vertices 1 and 3 differ less than
629 the brightness of vertices 0 and 2.
631 face.vertex_0_2_connected = vertex_0_2_connected;
636 Nodes make a face if contents differ and solidness differs.
639 1: Face uses m1's content
640 2: Face uses m2's content
641 equivalent: Whether the blocks share the same face (eg. water and glass)
643 TODO: Add 3: Both faces drawn with backface culling, remove equivalent
645 static u8 face_contents(content_t m1, content_t m2, bool *equivalent,
646 const NodeDefManager *ndef)
650 if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
653 const ContentFeatures &f1 = ndef->get(m1);
654 const ContentFeatures &f2 = ndef->get(m2);
656 // Contents don't differ for different forms of same liquid
657 if (f1.sameLiquid(f2))
660 u8 c1 = f1.solidness;
661 u8 c2 = f2.solidness;
668 c1 = f1.visual_solidness;
670 c2 = f2.visual_solidness;
675 // If same solidness, liquid takes precense
689 Gets nth node tile (0 <= n <= 5).
691 void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile)
693 const NodeDefManager *ndef = data->m_client->ndef();
694 const ContentFeatures &f = ndef->get(mn);
695 tile = f.tiles[tileindex];
696 bool has_crack = p == data->m_crack_pos_relative;
697 for (TileLayer &layer : tile.layers) {
698 if (layer.texture_id == 0)
700 if (!layer.has_color)
701 mn.getColor(f, &(layer.color));
702 // Apply temporary crack
704 layer.material_flags |= MATERIAL_FLAG_CRACK;
709 Gets node tile given a face direction.
711 void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile)
713 const NodeDefManager *ndef = data->m_client->ndef();
715 // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
716 // (0,0,1), (0,0,-1) or (0,0,0)
717 assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1);
719 // Convert direction to single integer for table lookup
724 // 4 = invalid, treat as (0,0,0)
728 u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2;
730 // Get rotation for things like chests
731 u8 facedir = mn.getFaceDir(ndef, true);
733 static const u16 dir_to_tile[24 * 16] =
735 // 0 +X +Y +Z -Z -Y -X -> value=tile,rotation
736 0,0, 2,0 , 0,0 , 4,0 , 0,0, 5,0 , 1,0 , 3,0 , // rotate around y+ 0 - 3
737 0,0, 4,0 , 0,3 , 3,0 , 0,0, 2,0 , 1,1 , 5,0 ,
738 0,0, 3,0 , 0,2 , 5,0 , 0,0, 4,0 , 1,2 , 2,0 ,
739 0,0, 5,0 , 0,1 , 2,0 , 0,0, 3,0 , 1,3 , 4,0 ,
741 0,0, 2,3 , 5,0 , 0,2 , 0,0, 1,0 , 4,2 , 3,1 , // rotate around z+ 4 - 7
742 0,0, 4,3 , 2,0 , 0,1 , 0,0, 1,1 , 3,2 , 5,1 ,
743 0,0, 3,3 , 4,0 , 0,0 , 0,0, 1,2 , 5,2 , 2,1 ,
744 0,0, 5,3 , 3,0 , 0,3 , 0,0, 1,3 , 2,2 , 4,1 ,
746 0,0, 2,1 , 4,2 , 1,2 , 0,0, 0,0 , 5,0 , 3,3 , // rotate around z- 8 - 11
747 0,0, 4,1 , 3,2 , 1,3 , 0,0, 0,3 , 2,0 , 5,3 ,
748 0,0, 3,1 , 5,2 , 1,0 , 0,0, 0,2 , 4,0 , 2,3 ,
749 0,0, 5,1 , 2,2 , 1,1 , 0,0, 0,1 , 3,0 , 4,3 ,
751 0,0, 0,3 , 3,3 , 4,1 , 0,0, 5,3 , 2,3 , 1,3 , // rotate around x+ 12 - 15
752 0,0, 0,2 , 5,3 , 3,1 , 0,0, 2,3 , 4,3 , 1,0 ,
753 0,0, 0,1 , 2,3 , 5,1 , 0,0, 4,3 , 3,3 , 1,1 ,
754 0,0, 0,0 , 4,3 , 2,1 , 0,0, 3,3 , 5,3 , 1,2 ,
756 0,0, 1,1 , 2,1 , 4,3 , 0,0, 5,1 , 3,1 , 0,1 , // rotate around x- 16 - 19
757 0,0, 1,2 , 4,1 , 3,3 , 0,0, 2,1 , 5,1 , 0,0 ,
758 0,0, 1,3 , 3,1 , 5,3 , 0,0, 4,1 , 2,1 , 0,3 ,
759 0,0, 1,0 , 5,1 , 2,3 , 0,0, 3,1 , 4,1 , 0,2 ,
761 0,0, 3,2 , 1,2 , 4,2 , 0,0, 5,2 , 0,2 , 2,2 , // rotate around y- 20 - 23
762 0,0, 5,2 , 1,3 , 3,2 , 0,0, 2,2 , 0,1 , 4,2 ,
763 0,0, 2,2 , 1,0 , 5,2 , 0,0, 4,2 , 0,0 , 3,2 ,
764 0,0, 4,2 , 1,1 , 2,2 , 0,0, 3,2 , 0,3 , 5,2
767 u16 tile_index = facedir * 16 + dir_i;
768 getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile);
769 tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
772 std::set<content_t> splitToContentT(std::string str, const NodeDefManager *ndef)
775 std::set<content_t> dat;
778 if (c == ',' || c == '\n') {
780 dat.insert(ndef->getId(buf));
783 } else if (c != ' ') {
790 static void getTileInfo(
794 const v3s16 &face_dir,
798 v3s16 &face_dir_corrected,
804 std::set<content_t> xraySet)
806 VoxelManipulator &vmanip = data->m_vmanip;
807 const NodeDefManager *ndef = data->m_client->ndef();
808 v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
810 const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
812 content_t c0 = n0.getContent();
813 if (xray && xraySet.find(c0) != xraySet.end())
815 // Don't even try to get n1 if n0 is already CONTENT_IGNORE
816 if (c0 == CONTENT_IGNORE) {
821 const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
823 content_t c1 = n1.getContent();
824 if (xray && xraySet.find(c1) != xraySet.end())
827 if (c1 == CONTENT_IGNORE) {
833 bool equivalent = false;
834 u8 mf = face_contents(c0, c1,
848 face_dir_corrected = face_dir;
851 p_corrected = p + face_dir;
852 face_dir_corrected = -face_dir;
855 getNodeTile(n, p_corrected, face_dir_corrected, data, tile);
856 const ContentFeatures &f = ndef->get(n);
858 tile.emissive_light = f.light_source;
860 // eg. water and glass
862 for (TileLayer &layer : tile.layers)
863 layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
866 if (!data->m_smooth_lighting) {
867 lights[0] = lights[1] = lights[2] = lights[3] =
868 getFaceLight(n0, n1, face_dir, ndef);
870 v3s16 vertex_dirs[4];
871 getNodeVertexDirs(face_dir_corrected, vertex_dirs);
873 v3s16 light_p = blockpos_nodes + p_corrected;
874 for (u16 i = 0; i < 4; i++)
875 lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data);
881 translate_dir: unit vector with only one of x, y or z
882 face_dir: unit vector with only one of x, y or z
884 static void updateFastFaceRow(
886 const v3s16 &&startpos,
888 const v3f &&translate_dir_f,
889 const v3s16 &&face_dir,
890 std::vector<FastFace> &dest,
892 std::set<content_t> xraySet)
894 static thread_local const bool waving_liquids =
895 g_settings->getBool("enable_shaders") &&
896 g_settings->getBool("enable_waving_water");
898 static thread_local const bool force_not_tiling =
899 g_settings->getBool("enable_dynamic_shadows");
903 u16 continuous_tiles_count = 1;
905 bool makes_face = false;
907 v3s16 face_dir_corrected;
908 u16 lights[4] = {0, 0, 0, 0};
912 // Get info of first tile
913 getTileInfo(data, p, face_dir,
914 makes_face, p_corrected, face_dir_corrected,
915 lights, waving, tile, xray, xraySet);
917 // Unroll this variable which has a significant build cost
919 for (u16 j = 0; j < MAP_BLOCKSIZE; j++) {
920 // If tiling can be done, this is set to false in the next step
921 bool next_is_different = true;
923 bool next_makes_face = false;
924 v3s16 next_p_corrected;
925 v3s16 next_face_dir_corrected;
926 u16 next_lights[4] = {0, 0, 0, 0};
928 // If at last position, there is nothing to compare to and
929 // the face must be drawn anyway
930 if (j != MAP_BLOCKSIZE - 1) {
933 getTileInfo(data, p, face_dir,
934 next_makes_face, next_p_corrected,
935 next_face_dir_corrected, next_lights,
941 if (!force_not_tiling
942 && next_makes_face == makes_face
943 && next_p_corrected == p_corrected + translate_dir
944 && next_face_dir_corrected == face_dir_corrected
945 && memcmp(next_lights, lights, sizeof(lights)) == 0
946 // Don't apply fast faces to waving water.
947 && (waving != 3 || !waving_liquids)
948 && next_tile.isTileable(tile)) {
949 next_is_different = false;
950 continuous_tiles_count++;
953 if (next_is_different) {
955 Create a face if there should be one
958 // Floating point conversion of the position vector
959 v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z);
960 // Center point of face (kind of)
961 v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f)
965 if (translate_dir.X != 0)
966 scale.X = continuous_tiles_count;
967 if (translate_dir.Y != 0)
968 scale.Y = continuous_tiles_count;
969 if (translate_dir.Z != 0)
970 scale.Z = continuous_tiles_count;
972 makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
973 pf, sp, face_dir_corrected, scale, dest);
974 g_profiler->avg("Meshgen: Tiles per face [#]", continuous_tiles_count);
977 continuous_tiles_count = 1;
980 makes_face = next_makes_face;
981 p_corrected = next_p_corrected;
982 face_dir_corrected = next_face_dir_corrected;
983 memcpy(lights, next_lights, sizeof(lights));
984 if (next_is_different)
985 tile = std::move(next_tile); // faster than copy
989 static void updateAllFastFaceRows(MeshMakeData *data,
990 std::vector<FastFace> &dest, bool xray, std::set<content_t> xraySet)
993 Go through every y,z and get top(y+) faces in rows of x+
995 for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
996 for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
997 updateFastFaceRow(data,
999 v3s16(1, 0, 0), //dir
1001 v3s16(0, 1, 0), //face dir
1007 Go through every x,y and get right(x+) faces in rows of z+
1009 for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
1010 for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
1011 updateFastFaceRow(data,
1013 v3s16(0, 0, 1), //dir
1015 v3s16(1, 0, 0), //face dir
1021 Go through every y,z and get back(z+) faces in rows of x+
1023 for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
1024 for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
1025 updateFastFaceRow(data,
1027 v3s16(1, 0, 0), //dir
1029 v3s16(0, 0, 1), //face dir
1035 static void applyTileColor(PreMeshBuffer &pmb)
1037 video::SColor tc = pmb.layer.color;
1038 if (tc == video::SColor(0xFFFFFFFF))
1040 for (video::S3DVertex &vertex : pmb.vertices) {
1041 video::SColor *c = &vertex.Color;
1042 c->set(c->getAlpha(),
1043 c->getRed() * tc.getRed() / 255,
1044 c->getGreen() * tc.getGreen() / 255,
1045 c->getBlue() * tc.getBlue() / 255);
1053 void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles)
1055 this->triangles = triangles;
1059 // assert that triangle index can fit into s32
1060 assert(triangles->size() <= 0x7FFFFFFFL);
1061 std::vector<s32> indexes;
1062 indexes.reserve(triangles->size());
1063 for (u32 i = 0; i < triangles->size(); i++)
1064 indexes.push_back(i);
1066 root = buildTree(v3f(1, 0, 0), v3f(85, 85, 85), 40, indexes, 0);
1070 * @brief Find a candidate plane to split a set of triangles in two
1072 * The candidate plane is represented by one of the triangles from the set.
1074 * @param list Vector of indexes of the triangles in the set
1075 * @param triangles Vector of all triangles in the BSP tree
1076 * @return Address of the triangle that represents the proposed split plane
1078 static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
1080 // find the center of the cluster.
1081 v3f center(0, 0, 0);
1082 size_t n = list.size();
1083 for (s32 i : list) {
1084 center += triangles[i].centroid / n;
1087 // find the triangle with the largest area and closest to the center
1088 const MeshTriangle *candidate_triangle = &triangles[list[0]];
1089 const MeshTriangle *ith_triangle;
1090 for (s32 i : list) {
1091 ith_triangle = &triangles[i];
1092 if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
1093 (ith_triangle->areaSQ == candidate_triangle->areaSQ &&
1094 ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
1095 candidate_triangle = ith_triangle;
1098 return candidate_triangle;
1101 s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
1103 // if the list is empty, don't bother
1107 // if there is only one triangle, or the delta is insanely small, this is a leaf node
1108 if (list.size() == 1 || delta < 0.01) {
1109 nodes.emplace_back(normal, origin, list, -1, -1);
1110 return nodes.size() - 1;
1113 std::vector<s32> front_list;
1114 std::vector<s32> back_list;
1115 std::vector<s32> node_list;
1118 for (s32 i : list) {
1119 const MeshTriangle &triangle = (*triangles)[i];
1120 float factor = normal.dotProduct(triangle.centroid - origin);
1122 node_list.push_back(i);
1123 else if (factor > 0)
1124 front_list.push_back(i);
1126 back_list.push_back(i);
1129 // define the new split-plane
1130 v3f candidate_normal(normal.Z, normal.X, normal.Y);
1131 float candidate_delta = delta;
1133 candidate_delta /= 2;
1135 s32 front_index = -1;
1136 s32 back_index = -1;
1138 if (!front_list.empty()) {
1139 v3f next_normal = candidate_normal;
1140 v3f next_origin = origin + delta * normal;
1141 float next_delta = candidate_delta;
1142 if (next_delta < 10) {
1143 const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
1144 next_normal = candidate->getNormal();
1145 next_origin = candidate->centroid;
1147 front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
1149 // if there are no other triangles, don't create a new node
1150 if (back_list.empty() && node_list.empty())
1154 if (!back_list.empty()) {
1155 v3f next_normal = candidate_normal;
1156 v3f next_origin = origin - delta * normal;
1157 float next_delta = candidate_delta;
1158 if (next_delta < 10) {
1159 const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
1160 next_normal = candidate->getNormal();
1161 next_origin = candidate->centroid;
1164 back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
1166 // if there are no other triangles, don't create a new node
1167 if (front_list.empty() && node_list.empty())
1171 nodes.emplace_back(normal, origin, node_list, front_index, back_index);
1173 return nodes.size() - 1;
1176 void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
1178 if (node < 0) return; // recursion break;
1180 const TreeNode &n = nodes[node];
1181 float factor = n.normal.dotProduct(viewpoint - n.origin);
1184 traverse(n.back_ref, viewpoint, output);
1186 traverse(n.front_ref, viewpoint, output);
1189 for (s32 i : n.triangle_refs)
1190 output.push_back(i);
1193 traverse(n.front_ref, viewpoint, output);
1195 traverse(n.back_ref, viewpoint, output);
1204 void PartialMeshBuffer::beforeDraw() const
1206 // Patch the indexes in the mesh buffer before draw
1207 m_buffer->Indices = std::move(m_vertex_indexes);
1208 m_buffer->setDirty(scene::EBT_INDEX);
1211 void PartialMeshBuffer::afterDraw() const
1213 // Take the data back
1214 m_vertex_indexes = std::move(m_buffer->Indices.steal());
1221 MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
1222 m_minimap_mapblock(NULL),
1223 m_tsrc(data->m_client->getTextureSource()),
1224 m_shdrsrc(data->m_client->getShaderSource()),
1225 m_animation_force_timer(0), // force initial animation
1227 m_last_daynight_ratio((u32) -1)
1229 for (auto &m : m_mesh)
1230 m = new scene::SMesh();
1231 m_enable_shaders = data->m_use_shaders;
1232 m_enable_vbo = g_settings->getBool("enable_vbo");
1234 if (data->m_client->getMinimap()) {
1235 m_minimap_mapblock = new MinimapMapblock;
1236 m_minimap_mapblock->getMinimapNodes(
1237 &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);
1240 // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
1241 // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated)
1242 //TimeTaker timer1("MapBlockMesh()");
1244 std::vector<FastFace> fastfaces_new;
1245 fastfaces_new.reserve(512);
1249 bool xray = g_settings->getBool("xray");
1250 std::set<content_t> xraySet, nodeESPSet;
1252 xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef());
1254 nodeESPSet = splitToContentT(g_settings->get("node_esp_nodes"), data->m_client->ndef());
1257 We are including the faces of the trailing edges of the block.
1258 This means that when something changes, the caller must
1259 also update the meshes of the blocks at the leading edges.
1261 NOTE: This is the slowest part of this method.
1264 // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated)
1265 //TimeTaker timer2("updateAllFastFaceRows()");
1266 updateAllFastFaceRows(data, fastfaces_new, xray, xraySet);
1274 v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
1275 for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
1276 for (s16 y = 0; y < MAP_BLOCKSIZE; y++) {
1277 for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
1278 v3s16 pos = v3s16(x, y, z) + blockpos_nodes;
1279 const MapNode &node = data->m_vmanip.getNodeRefUnsafeCheckFlags(pos);
1280 if (nodeESPSet.find(node.getContent()) != nodeESPSet.end())
1281 esp_nodes.insert(pos);
1288 Convert FastFaces to MeshCollector
1291 MeshCollector collector;
1294 // avg 0ms (100ms spikes when loading textures the first time)
1295 // (NOTE: probably outdated)
1296 //TimeTaker timer2("MeshCollector building");
1298 for (const FastFace &f : fastfaces_new) {
1299 static const u16 indices[] = {0, 1, 2, 2, 3, 0};
1300 static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1};
1301 const u16 *indices_p =
1302 f.vertex_0_2_connected ? indices : indices_alternate;
1303 collector.append(f.tile, f.vertices, 4, indices_p, 6);
1308 Add special graphics:
1316 MapblockMeshGenerator(data, &collector,
1317 data->m_client->getSceneManager()->getMeshManipulator()).generate();
1321 Convert MeshCollector to SMesh
1324 const bool desync_animations = g_settings->getBool(
1325 "desynchronize_mapblock_texture_animation");
1327 for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
1328 for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
1330 PreMeshBuffer &p = collector.prebuffers[layer][i];
1334 // Generate animation data
1336 if (p.layer.material_flags & MATERIAL_FLAG_CRACK) {
1337 // Find the texture name plus ^[crack:N:
1338 std::ostringstream os(std::ios::binary);
1339 os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack";
1340 if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
1341 os << "o"; // use ^[cracko
1342 u8 tiles = p.layer.scale;
1344 os << ":" << (u32)tiles;
1345 os << ":" << (u32)p.layer.animation_frame_count << ":";
1346 m_crack_materials.insert(std::make_pair(
1347 std::pair<u8, u32>(layer, i), os.str()));
1348 // Replace tile texture with the cracked one
1349 p.layer.texture = m_tsrc->getTextureForMesh(
1351 &p.layer.texture_id);
1353 // - Texture animation
1354 if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
1355 // Add to MapBlockMesh in order to animate these tiles
1356 auto &info = m_animation_info[{layer, i}];
1357 info.tile = p.layer;
1359 if (desync_animations) {
1360 // Get starting position from noise
1362 100000 * (2.0 + noise3d(
1363 data->m_blockpos.X, data->m_blockpos.Y,
1364 data->m_blockpos.Z, 0));
1366 // Play all synchronized
1367 info.frame_offset = 0;
1369 // Replace tile texture with the first animation frame
1370 p.layer.texture = (*p.layer.frames)[0].texture;
1373 if (!m_enable_shaders) {
1374 // Extract colors for day-night animation
1375 // Dummy sunlight to handle non-sunlit areas
1376 video::SColorf sunlight;
1377 get_sunlight_color(&sunlight, 0);
1379 std::map<u32, video::SColor> colors;
1380 const u32 vertex_count = p.vertices.size();
1381 for (u32 j = 0; j < vertex_count; j++) {
1382 video::SColor *vc = &p.vertices[j].Color;
1383 video::SColor copy = *vc;
1384 if (vc->getAlpha() == 0) // No sunlight - no need to animate
1385 final_color_blend(vc, copy, sunlight); // Finalize color
1386 else // Record color to animate
1389 // The sunlight ratio has been stored,
1390 // delete alpha (for the final rendering).
1393 if (!colors.empty())
1394 m_daynight_diffs[{layer, i}] = std::move(colors);
1398 video::SMaterial material;
1399 material.setFlag(video::EMF_LIGHTING, false);
1400 material.setFlag(video::EMF_BACK_FACE_CULLING, true);
1401 material.setFlag(video::EMF_BILINEAR_FILTER, false);
1402 material.setFlag(video::EMF_FOG_ENABLE, true);
1403 material.setTexture(0, p.layer.texture);
1405 if (m_enable_shaders) {
1406 material.MaterialType = m_shdrsrc->getShaderInfo(
1407 p.layer.shader_id).material;
1408 p.layer.applyMaterialOptionsWithShaders(material);
1409 if (p.layer.normal_texture)
1410 material.setTexture(1, p.layer.normal_texture);
1411 material.setTexture(2, p.layer.flags_texture);
1413 p.layer.applyMaterialOptions(material);
1416 scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer];
1418 scene::SMeshBuffer *buf = new scene::SMeshBuffer();
1419 buf->Material = material;
1420 switch (p.layer.material_type) {
1421 // list of transparent materials taken from tile.h
1422 case TILE_MATERIAL_ALPHA:
1423 case TILE_MATERIAL_LIQUID_TRANSPARENT:
1424 case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
1426 buf->append(&p.vertices[0], p.vertices.size(),
1431 for (u32 i = 0; i < p.indices.size(); i += 3) {
1432 t.p1 = p.indices[i];
1433 t.p2 = p.indices[i + 1];
1434 t.p3 = p.indices[i + 2];
1435 t.updateAttributes();
1436 m_transparent_triangles.push_back(t);
1441 buf->append(&p.vertices[0], p.vertices.size(),
1442 &p.indices[0], p.indices.size());
1445 mesh->addMeshBuffer(buf);
1449 if (m_mesh[layer]) {
1450 // Use VBO for mesh (this just would set this for ever buffer)
1452 m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC);
1456 //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
1457 m_bsp_tree.buildTree(&m_transparent_triangles);
1459 // Check if animation is required for this mesh
1461 !m_crack_materials.empty() ||
1462 !m_daynight_diffs.empty() ||
1463 !m_animation_info.empty();
1466 MapBlockMesh::~MapBlockMesh()
1468 for (scene::IMesh *m : m_mesh) {
1469 #if IRRLICHT_VERSION_MT_REVISION < 5
1471 for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
1472 scene::IMeshBuffer *buf = m->getMeshBuffer(i);
1473 RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
1479 delete m_minimap_mapblock;
1482 bool MapBlockMesh::animate(bool faraway, float time, int crack,
1485 if (!m_has_animation) {
1486 m_animation_force_timer = 100000;
1490 m_animation_force_timer = myrand_range(5, 100);
1493 if (crack != m_last_crack) {
1494 for (auto &crack_material : m_crack_materials) {
1495 scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
1496 getMeshBuffer(crack_material.first.second);
1498 // Create new texture name from original
1499 std::string s = crack_material.second + itos(crack);
1500 u32 new_texture_id = 0;
1501 video::ITexture *new_texture =
1502 m_tsrc->getTextureForMesh(s, &new_texture_id);
1503 buf->getMaterial().setTexture(0, new_texture);
1505 // If the current material is also animated, update animation info
1506 auto anim_it = m_animation_info.find(crack_material.first);
1507 if (anim_it != m_animation_info.end()) {
1508 TileLayer &tile = anim_it->second.tile;
1509 tile.texture = new_texture;
1510 tile.texture_id = new_texture_id;
1511 // force animation update
1512 anim_it->second.frame = -1;
1516 m_last_crack = crack;
1519 // Texture animation
1520 for (auto &it : m_animation_info) {
1521 const TileLayer &tile = it.second.tile;
1522 // Figure out current frame
1523 int frameno = (int)(time * 1000 / tile.animation_frame_length_ms
1524 + it.second.frame_offset) % tile.animation_frame_count;
1525 // If frame doesn't change, skip
1526 if (frameno == it.second.frame)
1529 it.second.frame = frameno;
1531 scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
1533 const FrameSpec &frame = (*tile.frames)[frameno];
1534 buf->getMaterial().setTexture(0, frame.texture);
1535 if (m_enable_shaders) {
1536 if (frame.normal_texture)
1537 buf->getMaterial().setTexture(1, frame.normal_texture);
1538 buf->getMaterial().setTexture(2, frame.flags_texture);
1542 // Day-night transition
1543 if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) {
1544 // Force reload mesh to VBO
1546 for (scene::IMesh *m : m_mesh)
1548 video::SColorf day_color;
1549 get_sunlight_color(&day_color, daynight_ratio);
1551 for (auto &daynight_diff : m_daynight_diffs) {
1552 scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]->
1553 getMeshBuffer(daynight_diff.first.second);
1554 video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
1555 for (const auto &j : daynight_diff.second)
1556 final_color_blend(&(vertices[j.first].Color), j.second,
1559 m_last_daynight_ratio = daynight_ratio;
1565 void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
1567 // nothing to do if the entire block is opaque
1568 if (m_transparent_triangles.empty())
1571 v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
1572 v3f rel_camera_pos = camera_pos - block_posf;
1574 std::vector<s32> triangle_refs;
1575 m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
1577 // arrange index sequences into partial buffers
1578 m_transparent_buffers.clear();
1580 scene::SMeshBuffer *current_buffer = nullptr;
1581 std::vector<u16> current_strain;
1582 for (auto i : triangle_refs) {
1583 const auto &t = m_transparent_triangles[i];
1584 if (current_buffer != t.buffer) {
1585 if (current_buffer) {
1586 m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1587 current_strain.clear();
1589 current_buffer = t.buffer;
1591 current_strain.push_back(t.p1);
1592 current_strain.push_back(t.p2);
1593 current_strain.push_back(t.p3);
1596 if (!current_strain.empty())
1597 m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1600 void MapBlockMesh::consolidateTransparentBuffers()
1602 m_transparent_buffers.clear();
1604 scene::SMeshBuffer *current_buffer = nullptr;
1605 std::vector<u16> current_strain;
1607 // use the fact that m_transparent_triangles is already arranged by buffer
1608 for (const auto &t : m_transparent_triangles) {
1609 if (current_buffer != t.buffer) {
1610 if (current_buffer != nullptr) {
1611 this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1612 current_strain.clear();
1614 current_buffer = t.buffer;
1616 current_strain.push_back(t.p1);
1617 current_strain.push_back(t.p2);
1618 current_strain.push_back(t.p3);
1621 if (!current_strain.empty()) {
1622 this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain));
1626 video::SColor encode_light(u16 light, u8 emissive_light)
1629 u32 day = (light & 0xff);
1630 u32 night = (light >> 8);
1631 // Add emissive light
1632 night += emissive_light * 2.5f;
1635 // Since we don't know if the day light is sunlight or
1636 // artificial light, assume it is artificial when the night
1637 // light bank is also lit.
1642 u32 sum = day + night;
1643 // Ratio of sunlight:
1646 r = day * 255 / sum;
1650 float b = (day + night) / 2;
1651 return video::SColor(r, b, b, b);