aboutsummaryrefslogtreecommitdiff
path: root/src/script/lua_api/l_mainmenu.cpp
Commit message (Expand)AuthorAge
* Remove remaining modstore code (#6120)Elijah Duffy2017-07-14
* Main Menu: Allow copying directories from non-Minetest locations (#6095)Elijah Duffy2017-07-04
* Isolate irrlicht references and use a singleton (#6041)Loïc Blot2017-06-26
* Improve the path select GUI (#5852)red-0012017-06-11
* Main Menu: Add get_clientmodpath API (#5912)Elijah Duffy2017-06-06
* Use a settings object for the main settingsShadowNinja2017-05-06
* Fix various points reported by cppcheck (#5656)Loïc Blot2017-04-25
* [CSM] sound_play & sound_stop support + client_lua_api doc (#5096)Loïc Blot2017-03-13
* Serverlist: Add ping indicators (#5164)kilbith2017-02-03
* Add version APIShadowNinja2016-10-31
* Mapgen: Refactor mapgen creation and managementkwolekr2016-07-03
* Add option to not send pre v25 init packetest312016-03-15
* Remove wstrgettextest312015-10-18
* Hide mapgens from main menu not intended for end userskwolekr2015-10-04
* Add emerge completion callback mechanismkwolekr2015-10-04
* Optional reconnect functionalityest312015-07-23
* Use UTF-8 instead of narrowest312015-07-08
* Use utf-8 in formspecsIlya Zhuravlev2015-06-13
* Add core.get_dir_listShadowNinja2015-05-16
* Move globals from main.cpp to more sane locationsCraig Robbins2015-04-01
* Fix typoCraig Robbins2015-03-29
* Add Lua function get_video_modes() for main menuCraig Robbins2015-03-28
* Clean up and tweak build systemShadowNinja2015-03-27
* Fix game minetest.conf default settingsest312015-03-18
* For usages of assert() that are meant to persist in Release builds (when NDEB...Craig Robbins2015-03-07
* Server: announce MIN/MAX protocol version supported to serverlist. Client: ch...est312015-02-18
* Fix .zip extraction (mod store)ngosang2015-02-12
* Reduce gettext wide/narrow and string/char* conversionsShadowNinja2015-02-05
* Fix all warnings and remove -Wno-unused-but-set cflagkwolekr2015-01-18
* Reorganize supported video driver query mechanismskwolekr2015-01-18
* Add core.get_mapgen_names() to Main Menu API (and use it)kwolekr2014-12-29
* Add video driver selection to settings menu (based uppon idea from webdesigne...sapier2014-08-23
* Fix the *CDP displaySmallJoker2014-08-15
* Remove a lot of superfluous ifndef USE_CURL checkssapier2014-06-19
* Fix regression dirt texture not beeing default in non cloud menusapier2014-06-14
* Organize builtin into subdirectoriesShadowNinja2014-05-07
* Fix code style of async APIShadowNinja2014-04-27
* Remove dependency on marshal and many other async changesShadowNinja2014-04-27
* Add support for dpi based HUD scalingsapier2014-04-27
* Minor fixes for file/modlist download in mainmenusapier2014-04-09
* Fix invalid check for fread error on extracting zipsapier2014-02-07
* Add formspec tableKahrl2014-01-13
* Fix absence of images when compiled with RUN_IN_PLACE=0.Ilya Zhuravlev2014-01-05
* Implement search tab and version pickersapier2013-12-11
* Fix modstore/favourites hang by adding asynchronous lua job supportsapier2013-11-29
* Fix invalid usage of temporary object in mainmenu json conversionsapier2013-11-11
* Show git hash in version string at top left corner of windowKahrl2013-09-28
* Fix umlauts/special character issue in lua gettextBlockMen2013-08-19
* Allow SIGINT to kill mainmenu againKahrl2013-08-19
* Add translation for main menusapier2013-08-17
n477'>477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "clientmap.h"
#include "client.h"
#include "mapblock_mesh.h"
#include <IMaterialRenderer.h>
#include <matrix4.h>
#include "mapsector.h"
#include "mapblock.h"
#include "profiler.h"
#include "settings.h"
#include "camera.h"               // CameraModes
#include "util/basic_macros.h"
#include <algorithm>
#include "client/renderingengine.h"

ClientMap::ClientMap(
		Client *client,
		MapDrawControl &control,
		s32 id
):
	Map(dout_client, client),
	scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
		RenderingEngine::get_scene_manager(), id),
	m_client(client),
	m_control(control)
{
	m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000,
			BS*1000000,BS*1000000,BS*1000000);

	/* TODO: Add a callback function so these can be updated when a setting
	 *       changes.  At this point in time it doesn't matter (e.g. /set
	 *       is documented to change server settings only)
	 *
	 * TODO: Local caching of settings is not optimal and should at some stage
	 *       be updated to use a global settings object for getting thse values
	 *       (as opposed to the this local caching). This can be addressed in
	 *       a later release.
	 */
	m_cache_trilinear_filter  = g_settings->getBool("trilinear_filter");
	m_cache_bilinear_filter   = g_settings->getBool("bilinear_filter");
	m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");

}

MapSector * ClientMap::emergeSector(v2s16 p2d)
{
	// Check that it doesn't exist already
	try {
		return getSectorNoGenerate(p2d);
	} catch(InvalidPositionException &e) {
	}

	// Create a sector
	MapSector *sector = new MapSector(this, p2d, m_gamedef);
	m_sectors[p2d] = sector;

	return sector;
}

void ClientMap::OnRegisterSceneNode()
{
	if(IsVisible)
	{
		SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
		SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
	}

	ISceneNode::OnRegisterSceneNode();
}

void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
		v3s16 *p_blocks_min, v3s16 *p_blocks_max)
{
	v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1);
	// Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d'
	// can exceed the range of v3s16 when a large view range is used near the
	// world edges.
	v3s32 p_nodes_min(
		cam_pos_nodes.X - box_nodes_d.X,
		cam_pos_nodes.Y - box_nodes_d.Y,
		cam_pos_nodes.Z - box_nodes_d.Z);
	v3s32 p_nodes_max(
		cam_pos_nodes.X + box_nodes_d.X,
		cam_pos_nodes.Y + box_nodes_d.Y,
		cam_pos_nodes.Z + box_nodes_d.Z);
	// Take a fair amount as we will be dropping more out later
	// Umm... these additions are a bit strange but they are needed.
	*p_blocks_min = v3s16(
			p_nodes_min.X / MAP_BLOCKSIZE - 3,
			p_nodes_min.Y / MAP_BLOCKSIZE - 3,
			p_nodes_min.Z / MAP_BLOCKSIZE - 3);
	*p_blocks_max = v3s16(
			p_nodes_max.X / MAP_BLOCKSIZE + 1,
			p_nodes_max.Y / MAP_BLOCKSIZE + 1,
			p_nodes_max.Z / MAP_BLOCKSIZE + 1);
}

void ClientMap::updateDrawList()
{
	ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
	g_profiler->add("CM::updateDrawList() count", 1);

	for (auto &i : m_drawlist) {
		MapBlock *block = i.second;
		block->refDrop();
	}
	m_drawlist.clear();

	v3f camera_position = m_camera_position;
	v3f camera_direction = m_camera_direction;
	f32 camera_fov = m_camera_fov;

	// Use a higher fov to accomodate faster camera movements.
	// Blocks are cropped better when they are drawn.
	// Or maybe they aren't? Well whatever.
	camera_fov *= 1.2;

	v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
	v3s16 p_blocks_min;
	v3s16 p_blocks_max;
	getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);

	// Number of blocks in rendering range
	u32 blocks_in_range = 0;
	// Number of blocks occlusion culled
	u32 blocks_occlusion_culled = 0;
	// Number of blocks in rendering range but don't have a mesh
	u32 blocks_in_range_without_mesh = 0;
	// Blocks that had mesh that would have been drawn according to
	// rendering range (if max blocks limit didn't kick in)
	u32 blocks_would_have_drawn = 0;
	// Blocks that were drawn and had a mesh
	u32 blocks_drawn = 0;
	// Blocks which had a corresponding meshbuffer for this pass
	//u32 blocks_had_pass_meshbuf = 0;
	// Blocks from which stuff was actually drawn
	//u32 blocks_without_stuff = 0;
	// Distance to farthest drawn block
	float farthest_drawn = 0;

	// No occlusion culling when free_move is on and camera is
	// inside ground
	bool occlusion_culling_enabled = true;
	if (g_settings->getBool("free_move")) {
		MapNode n = getNodeNoEx(cam_pos_nodes);
		if (n.getContent() == CONTENT_IGNORE ||
				m_nodedef->get(n).solidness == 2)
			occlusion_culling_enabled = false;
	}

	for (const auto &sector_it : m_sectors) {
		MapSector *sector = sector_it.second;
		v2s16 sp = sector->getPos();

		if (!m_control.range_all) {
			if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X ||
					sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z)
				continue;
		}

		MapBlockVect sectorblocks;
		sector->getBlocks(sectorblocks);

		/*
			Loop through blocks in sector
		*/

		u32 sector_blocks_drawn = 0;

		for (auto block : sectorblocks) {
			/*
			Compare block position to camera position, skip
			if not seen on display
		*/

			if (block->mesh)
				block->mesh->updateCameraOffset(m_camera_offset);

			float range = 100000 * BS;
			if (!m_control.range_all)
				range = m_control.wanted_range * BS;

			float d = 0.0;
			if (!isBlockInSight(block->getPos(), camera_position,
					camera_direction, camera_fov, range, &d))
				continue;

			blocks_in_range++;

			/*
				Ignore if mesh doesn't exist
			*/
			if (!block->mesh) {
				blocks_in_range_without_mesh++;
				continue;
			}

			/*
				Occlusion culling
			*/
			if (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes)) {
				blocks_occlusion_culled++;
				continue;
			}

			// This block is in range. Reset usage timer.
			block->resetUsageTimer();

			// Limit block count in case of a sudden increase
			blocks_would_have_drawn++;
			if (blocks_drawn >= m_control.wanted_max_blocks &&
					!m_control.range_all &&
					d > m_control.wanted_range * BS)
				continue;

			// Add to set
			block->refGrab();
			m_drawlist[block->getPos()] = block;

			sector_blocks_drawn++;
			blocks_drawn++;
			if (d / BS > farthest_drawn)
				farthest_drawn = d / BS;

		} // foreach sectorblocks

		if (sector_blocks_drawn != 0)
			m_last_drawn_sectors.insert(sp);
	}

	g_profiler->avg("CM: blocks in range", blocks_in_range);
	g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
	if (blocks_in_range != 0)
		g_profiler->avg("CM: blocks in range without mesh (frac)",
				(float)blocks_in_range_without_mesh / blocks_in_range);
	g_profiler->avg("CM: blocks drawn", blocks_drawn);
	g_profiler->avg("CM: farthest drawn", farthest_drawn);
	g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks);
}

struct MeshBufList
{
	video::SMaterial m;
	std::vector<scene::IMeshBuffer*> bufs;
};

struct MeshBufListList
{
	/*!
	 * Stores the mesh buffers of the world.
	 * The array index is the material's layer.
	 * The vector part groups vertices by material.
	 */
	std::vector<MeshBufList> lists[MAX_TILE_LAYERS];

	void clear()
	{
		for (auto &list : lists)
			list.clear();
	}

	void add(scene::IMeshBuffer *buf, u8 layer)
	{
		// Append to the correct layer
		std::vector<MeshBufList> &list = lists[layer];
		const video::SMaterial &m = buf->getMaterial();
		for (MeshBufList &l : list) {
			// comparing a full material is quite expensive so we don't do it if
			// not even first texture is equal
			if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture)
				continue;

			if (l.m == m) {
				l.bufs.push_back(buf);
				return;
			}
		}
		MeshBufList l;
		l.m = m;
		l.bufs.push_back(buf);
		list.push_back(l);
	}
};

void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
{
	bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;

	std::string prefix;
	if (pass == scene::ESNRP_SOLID)
		prefix = "CM: solid: ";
	else
		prefix = "CM: transparent: ";

	/*
		This is called two times per frame, reset on the non-transparent one
	*/
	if (pass == scene::ESNRP_SOLID)
		m_last_drawn_sectors.clear();

	/*
		Get time for measuring timeout.

		Measuring time is very useful for long delays when the
		machine is swapping a lot.
	*/
	std::time_t time1 = time(0);

	/*
		Get animation parameters
	*/
	float animation_time = m_client->getAnimationTime();
	int crack = m_client->getCrackLevel();
	u32 daynight_ratio = m_client->getEnv().getDayNightRatio();

	v3f camera_position = m_camera_position;
	v3f camera_direction = m_camera_direction;
	f32 camera_fov = m_camera_fov;

	/*
		Get all blocks and draw all visible ones
	*/

	u32 vertex_count = 0;
	u32 meshbuffer_count = 0;

	// For limiting number of mesh animations per frame
	u32 mesh_animate_count = 0;
	u32 mesh_animate_count_far = 0;

	// Blocks that were drawn and had a mesh
	u32 blocks_drawn = 0;
	// Blocks which had a corresponding meshbuffer for this pass
	u32 blocks_had_pass_meshbuf = 0;
	// Blocks from which stuff was actually drawn
	u32 blocks_without_stuff = 0;

	/*
		Draw the selected MapBlocks
	*/

	{
	ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG);

	MeshBufListList drawbufs;

	for (auto &i : m_drawlist) {
		MapBlock *block = i.second;

		// If the mesh of the block happened to get deleted, ignore it
		if (!block->mesh)
			continue;

		float d = 0.0;
		if (!isBlockInSight(block->getPos(), camera_position,
				camera_direction, camera_fov, 100000 * BS, &d))
			continue;

		// Mesh animation
		if (pass == scene::ESNRP_SOLID) {
			//MutexAutoLock lock(block->mesh_mutex);
			MapBlockMesh *mapBlockMesh = block->mesh;
			assert(mapBlockMesh);
			// Pretty random but this should work somewhat nicely
			bool faraway = d >= BS * 50;
			//bool faraway = d >= m_control.wanted_range * BS;
			if (mapBlockMesh->isAnimationForced() || !faraway ||
					mesh_animate_count_far < (m_control.range_all ? 200 : 50)) {
				bool animated = mapBlockMesh->animate(faraway, animation_time,
					crack, daynight_ratio);
				if (animated)
					mesh_animate_count++;
				if (animated && faraway)
					mesh_animate_count_far++;
			} else {
				mapBlockMesh->decreaseAnimationForceTimer();
			}
		}

		/*
			Get the meshbuffers of the block
		*/
		{
			//MutexAutoLock lock(block->mesh_mutex);

			MapBlockMesh *mapBlockMesh = block->mesh;
			assert(mapBlockMesh);

			for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
				scene::IMesh *mesh = mapBlockMesh->getMesh(layer);
				assert(mesh);

				u32 c = mesh->getMeshBufferCount();
				for (u32 i = 0; i < c; i++) {
					scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);

					video::SMaterial& material = buf->getMaterial();
					video::IMaterialRenderer* rnd =
						driver->getMaterialRenderer(material.MaterialType);
					bool transparent = (rnd && rnd->isTransparent());
					if (transparent == is_transparent_pass) {
						if (buf->getVertexCount() == 0)
							errorstream << "Block [" << analyze_block(block)
								<< "] contains an empty meshbuf" << std::endl;

						material.setFlag(video::EMF_TRILINEAR_FILTER,
							m_cache_trilinear_filter);
						material.setFlag(video::EMF_BILINEAR_FILTER,
							m_cache_bilinear_filter);
						material.setFlag(video::EMF_ANISOTROPIC_FILTER,
							m_cache_anistropic_filter);
						material.setFlag(video::EMF_WIREFRAME,
							m_control.show_wireframe);

						drawbufs.add(buf, layer);
					}
				}
			}
		}
	}

	// Render all layers in order
	for (auto &lists : drawbufs.lists) {
		int timecheck_counter = 0;
		for (MeshBufList &list : lists) {
			timecheck_counter++;
			if (timecheck_counter > 50) {
				timecheck_counter = 0;
				std::time_t time2 = time(0);
				if (time2 > time1 + 4) {
					infostream << "ClientMap::renderMap(): "
						"Rendering takes ages, returning."
						<< std::endl;
					return;
				}
			}

			driver->setMaterial(list.m);

			for (scene::IMeshBuffer *buf : list.bufs) {
				driver->drawMeshBuffer(buf);
				vertex_count += buf->getVertexCount();
				meshbuffer_count++;
			}
		}
	}
	} // ScopeProfiler

	// Log only on solid pass because values are the same
	if (pass == scene::ESNRP_SOLID) {
		g_profiler->avg("CM: animated meshes", mesh_animate_count);
		g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far);
	}

	g_profiler->avg(prefix + "vertices drawn", vertex_count);
	if (blocks_had_pass_meshbuf != 0)
		g_profiler->avg(prefix + "meshbuffers per block",
			(float)meshbuffer_count / (float)blocks_had_pass_meshbuf);
	if (blocks_drawn != 0)
		g_profiler->avg(prefix + "empty blocks (frac)",
			(float)blocks_without_stuff / blocks_drawn);
}

static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
	float step_multiplier, float start_distance, float end_distance,
	const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d,
	int *result, bool *sunlight_seen)
{
	int brightness_sum = 0;
	int brightness_count = 0;
	float distance = start_distance;
	dir.normalize();
	v3f pf = p0;
	pf += dir * distance;
	int noncount = 0;
	bool nonlight_seen = false;
	bool allow_allowing_non_sunlight_propagates = false;
	bool allow_non_sunlight_propagates = false;
	// Check content nearly at camera position
	{
		v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS);
		MapNode n = map->getNodeNoEx(p);
		if(ndef->get(n).param_type == CPT_LIGHT &&
				!ndef->get(n).sunlight_propagates)
			allow_allowing_non_sunlight_propagates = true;
	}
	// If would start at CONTENT_IGNORE, start closer
	{
		v3s16 p = floatToInt(pf, BS);
		MapNode n = map->getNodeNoEx(p);
		if(n.getContent() == CONTENT_IGNORE){
			float newd = 2*BS;
			pf = p0 + dir * 2*newd;
			distance = newd;
			sunlight_min_d = 0;
		}
	}
	for (int i=0; distance < end_distance; i++) {
		pf += dir * step;
		distance += step;
		step *= step_multiplier;

		v3s16 p = floatToInt(pf, BS);
		MapNode n = map->getNodeNoEx(p);
		if (allow_allowing_non_sunlight_propagates && i == 0 &&
				ndef->get(n).param_type == CPT_LIGHT &&
				!ndef->get(n).sunlight_propagates) {
			allow_non_sunlight_propagates = true;
		}

		if (ndef->get(n).param_type != CPT_LIGHT ||
				(!ndef->get(n).sunlight_propagates &&
					!allow_non_sunlight_propagates)){
			nonlight_seen = true;
			noncount++;
			if(noncount >= 4)
				break;
			continue;
		}

		if (distance >= sunlight_min_d && !*sunlight_seen && !nonlight_seen)
			if (n.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
				*sunlight_seen = true;
		noncount = 0;
		brightness_sum += decode_light(n.getLightBlend(daylight_factor, ndef));
		brightness_count++;
	}
	*result = 0;
	if(brightness_count == 0)
		return false;
	*result = brightness_sum / brightness_count;
	/*std::cerr<<"Sampled "<<brightness_count<<" points; result="
			<<(*result)<<std::endl;*/
	return true;
}

int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
		int oldvalue, bool *sunlight_seen_result)
{
	static v3f z_directions[50] = {
		v3f(-100, 0, 0)
	};
	static f32 z_offsets[sizeof(z_directions)/sizeof(*z_directions)] = {
		-1000,
	};

	if(z_directions[0].X < -99){
		for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
			// Assumes FOV of 72 and 16/9 aspect ratio
			z_directions[i] = v3f(
				0.02 * myrand_range(-100, 100),
				1.0,
				0.01 * myrand_range(-100, 100)
			).normalize();
			z_offsets[i] = 0.01 * myrand_range(0,100);
		}
	}

	int sunlight_seen_count = 0;
	float sunlight_min_d = max_d*0.8;
	if(sunlight_min_d > 35*BS)
		sunlight_min_d = 35*BS;
	std::vector<int> values;
	for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
		v3f z_dir = z_directions[i];
		core::CMatrix4<f32> a;
		a.buildRotateFromTo(v3f(0,1,0), z_dir);
		v3f dir = m_camera_direction;
		a.rotateVect(dir);
		int br = 0;
		float step = BS*1.5;
		if(max_d > 35*BS)
			step = max_d / 35 * 1.5;
		float off = step * z_offsets[i];
		bool sunlight_seen_now = false;
		bool ok = getVisibleBrightness(this, m_camera_position, dir,
				step, 1.0, max_d*0.6+off, max_d, m_nodedef, daylight_factor,
				sunlight_min_d,
				&br, &sunlight_seen_now);
		if(sunlight_seen_now)
			sunlight_seen_count++;
		if(!ok)
			continue;
		values.push_back(br);
		// Don't try too much if being in the sun is clear
		if(sunlight_seen_count >= 20)
			break;
	}
	int brightness_sum = 0;
	int brightness_count = 0;
	std::sort(values.begin(), values.end());
	u32 num_values_to_use = values.size();
	if(num_values_to_use >= 10)
		num_values_to_use -= num_values_to_use/2;
	else if(num_values_to_use >= 7)
		num_values_to_use -= num_values_to_use/3;
	u32 first_value_i = (values.size() - num_values_to_use) / 2;

	for (u32 i=first_value_i; i < first_value_i + num_values_to_use; i++) {
		brightness_sum += values[i];
		brightness_count++;
	}

	int ret = 0;
	if(brightness_count == 0){
		MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
		if(m_nodedef->get(n).param_type == CPT_LIGHT){
			ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef));
		} else {
			ret = oldvalue;
		}
	} else {
		ret = brightness_sum / brightness_count;
	}

	*sunlight_seen_result = (sunlight_seen_count > 0);
	return ret;
}

void ClientMap::renderPostFx(CameraMode cam_mode)
{
	// Sadly ISceneManager has no "post effects" render pass, in that case we
	// could just register for that and handle it in renderMap().

	MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));

	// - If the player is in a solid node, make everything black.
	// - If the player is in liquid, draw a semi-transparent overlay.
	// - Do not if player is in third person mode
	const ContentFeatures& features = m_nodedef->get(n);
	video::SColor post_effect_color = features.post_effect_color;
	if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
			m_client->checkLocalPrivilege("noclip")) &&
			cam_mode == CAMERA_MODE_FIRST)
	{
		post_effect_color = video::SColor(255, 0, 0, 0);
	}
	if (post_effect_color.getAlpha() != 0)
	{
		// Draw a full-screen rectangle
		video::IVideoDriver* driver = SceneManager->getVideoDriver();
		v2u32 ss = driver->getScreenSize();
		core::rect<s32> rect(0,0, ss.X, ss.Y);
		driver->draw2DRectangle(post_effect_color, rect);
	}
}

void ClientMap::PrintInfo(std::ostream &out)
{
	out<<"ClientMap: ";
}