int ServerMap::getSurface(v3s16 basepos, int searchup, bool walkable_only) { s16 max = MYMIN(searchup + basepos.Y, 0x7FFF); MapNode last_node = getNodeNoEx(basepos); MapNode node = last_node; v3s16 runpos = basepos; INodeDefManager *nodemgr = m_gamedef->ndef(); bool last_was_walkable = nodemgr->get(node).walkable; while ((runpos.Y < max) && (node.param0 != CONTENT_AIR)) { runpos.Y += 1; last_node = node; node = getNodeNoEx(runpos); if (!walkable_only) { if ((last_node.param0 != CONTENT_AIR) && (last_node.param0 != CONTENT_IGNORE) && (node.param0 == CONTENT_AIR)) { return runpos.Y; } } else { bool is_walkable = nodemgr->get(node).walkable; if (last_was_walkable && (!is_walkable)) { return runpos.Y; } last_was_walkable = is_walkable; } } return basepos.Y - 1; }
void ClientMap::renderPostFx() { INodeDefManager *nodemgr = m_gamedef->ndef(); // Sadly ISceneManager has no "post effects" render pass, in that case we // could just register for that and handle it in renderMap(). m_camera_mutex.Lock(); v3f camera_position = m_camera_position; m_camera_mutex.Unlock(); MapNode n = getNodeNoEx(floatToInt(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. const ContentFeatures& features = nodemgr->get(n); video::SColor post_effect_color = features.post_effect_color; if(features.solidness == 2 && !(g_settings->getBool("noclip") && m_gamedef->checkLocalPrivilege("noclip"))) { 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::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); } }
int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor, int oldvalue, bool *sunlight_seen_result) { const bool debugprint = false; INodeDefManager *ndef = m_gamedef->ndef(); 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++){ z_directions[i] = v3f( 0.01 * myrand_range(-100, 100), 1.0, 0.01 * myrand_range(-100, 100) ); z_offsets[i] = 0.01 * myrand_range(0,100); } } if(debugprint) std::cerr<<"In goes "<<PP(m_camera_direction)<<", out comes "; 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]; z_dir.normalize(); 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, ndef, 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; if(debugprint){ for(u32 i=0; i < first_value_i; i++) std::cerr<<values[i]<<" "; std::cerr<<"["; } for(u32 i=first_value_i; i < first_value_i+num_values_to_use; i++){ if(debugprint) std::cerr<<values[i]<<" "; brightness_sum += values[i]; brightness_count++; } if(debugprint){ std::cerr<<"]"; for(u32 i=first_value_i+num_values_to_use; i < values.size(); i++) std::cerr<<values[i]<<" "; } int ret = 0; if(brightness_count == 0){ MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS)); if(ndef->get(n).param_type == CPT_LIGHT){ ret = decode_light(n.getLightBlend(daylight_factor, ndef)); } else { ret = oldvalue; //ret = blend_light(255, 0, daylight_factor); } } else { /*float pre = (float)brightness_sum / (float)brightness_count; float tmp = pre; const float d = 0.2; pre *= 1.0 + d*2; pre -= tmp * d; int preint = pre; ret = MYMAX(0, MYMIN(255, preint));*/ ret = brightness_sum / brightness_count; } if(debugprint) std::cerr<<"Result: "<<ret<<" sunlight_seen_count=" <<sunlight_seen_count<<std::endl; *sunlight_seen_result = (sunlight_seen_count > 0); return ret; }
void ClientMap::updateDrawList(video::IVideoDriver* driver) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); g_profiler->add("CM::updateDrawList() count", 1); INodeDefManager *nodemgr = m_gamedef->ndef(); for(std::map<v3s16, MapBlock*>::iterator i = m_drawlist.begin(); i != m_drawlist.end(); ++i) { MapBlock *block = i->second; block->refDrop(); } m_drawlist.clear(); m_camera_mutex.Lock(); v3f camera_position = m_camera_position; v3f camera_direction = m_camera_direction; f32 camera_fov = m_camera_fov; m_camera_mutex.Unlock(); // 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 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; // Take a fair amount as we will be dropping more out later // Umm... these additions are a bit strange but they are needed. v3s16 p_blocks_min( p_nodes_min.X / MAP_BLOCKSIZE - 3, p_nodes_min.Y / MAP_BLOCKSIZE - 3, p_nodes_min.Z / MAP_BLOCKSIZE - 3); v3s16 p_blocks_max( p_nodes_max.X / MAP_BLOCKSIZE + 1, p_nodes_max.Y / MAP_BLOCKSIZE + 1, p_nodes_max.Z / MAP_BLOCKSIZE + 1); // 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; for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); si != m_sectors.end(); ++si) { MapSector *sector = si->second; v2s16 sp = sector->getPos(); if(m_control.range_all == false) { 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; } std::list< MapBlock * > sectorblocks; sector->getBlocks(sectorblocks); /* Loop through blocks in sector */ u32 sector_blocks_drawn = 0; std::list< MapBlock * >::iterator i; for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) { MapBlock *block = *i; /* Compare block position to camera position, skip if not seen on display */ float range = 100000 * BS; if(m_control.range_all == false) range = m_control.wanted_range * BS; float d = 0.0; if(isBlockInSight(block->getPos(), camera_position, camera_direction, camera_fov, range, &d) == false) { continue; } // This is ugly (spherical distance limit?) /*if(m_control.range_all == false && d - 0.5*BS*MAP_BLOCKSIZE > range) continue;*/ blocks_in_range++; /* Ignore if mesh doesn't exist */ { //JMutexAutoLock lock(block->mesh_mutex); if(block->mesh == NULL){ blocks_in_range_without_mesh++; continue; } } /* Occlusion culling */ // 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 || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); float step = BS*1; float stepfac = 1.1; float startoff = BS*1; float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; v3s16 spn = cam_pos_nodes + v3s16(0,0,0); s16 bs2 = MAP_BLOCKSIZE/2 + 1; u32 needed_count = 1; if( occlusion_culling_enabled && isOccluded(this, spn, cpn + v3s16(0,0,0), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) ) { 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 == false && d > m_control.wanted_min_range * BS) continue; // Add to set block->refGrab(); m_drawlist[block->getPos()] = block; sector_blocks_drawn++; blocks_drawn++; } // foreach sectorblocks if(sector_blocks_drawn != 0) m_last_drawn_sectors.insert(sp); } m_control.blocks_would_have_drawn = blocks_would_have_drawn; m_control.blocks_drawn = blocks_drawn; 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: wanted max blocks", m_control.wanted_max_blocks); }
/* Propagates sunlight down through the block. Doesn't modify nodes that are not affected by sunlight. Returns false if sunlight at bottom block is invalid. Returns true if sunlight at bottom block is valid. Returns true if bottom block doesn't exist. If there is a block above, continues from it. If there is no block above, assumes there is sunlight, unless is_underground is set or highest node is water. All sunlighted nodes are added to light_sources. if remove_light==true, sets non-sunlighted nodes black. if black_air_left!=NULL, it is set to true if non-sunlighted air is left in block. */ bool MapBlock::propagateSunlight(std::set<v3s16> & light_sources, bool remove_light, bool *black_air_left) { auto lock = lock_unique_rec(); INodeDefManager *nodemgr = m_gamedef->ndef(); // Whether the sunlight at the top of the bottom block is valid bool block_below_is_valid = true; v3s16 pos_relative = getPosRelative(); for(s16 x=0; x<MAP_BLOCKSIZE; x++) { for(s16 z=0; z<MAP_BLOCKSIZE; z++) { #if 1 bool no_sunlight = false; //bool no_top_block = false; // Check if node above block has sunlight bool is_valid_position; MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z), &is_valid_position); if (n) { if(n.getLight(LIGHTBANK_DAY, m_gamedef->ndef()) != LIGHT_SUN) { no_sunlight = true; } } else { //no_top_block = true; // NOTE: This makes over-ground roofed places sunlighted // Assume sunlight, unless is_underground==true if(is_underground) { no_sunlight = true; } else { MapNode n = getNodeNoEx(v3s16(x, MAP_BLOCKSIZE-1, z)); if(n && m_gamedef->ndef()->get(n).sunlight_propagates == false) { no_sunlight = true; } } // NOTE: As of now, this just would make everything dark. // No sunlight here //no_sunlight = true; } #endif #if 0 // Doesn't work; nothing gets light. bool no_sunlight = true; bool no_top_block = false; // Check if node above block has sunlight try{ MapNode n = getNodeParent(v3s16(x, MAP_BLOCKSIZE, z)); if(n.getLight(LIGHTBANK_DAY) == LIGHT_SUN) { no_sunlight = false; } } catch(InvalidPositionException &e) { no_top_block = true; } #endif /*std::cout<<"("<<x<<","<<z<<"): " <<"no_top_block="<<no_top_block <<", is_underground="<<is_underground <<", no_sunlight="<<no_sunlight <<std::endl;*/ s16 y = MAP_BLOCKSIZE-1; // This makes difference to diminishing in water. bool stopped_to_solid_object = false; u8 current_light = no_sunlight ? 0 : LIGHT_SUN; for(; y >= 0; y--) { v3s16 pos(x, y, z); MapNode &n = getNodeRef(pos); if(current_light == 0) { // Do nothing } else if(current_light == LIGHT_SUN && nodemgr->get(n).sunlight_propagates) { // Do nothing: Sunlight is continued } else if(nodemgr->get(n).light_propagates == false) { // A solid object is on the way. stopped_to_solid_object = true; // Light stops. current_light = 0; } else { // Diminish light current_light = diminish_light(current_light); } u8 old_light = n.getLight(LIGHTBANK_DAY, nodemgr); if(current_light > old_light || remove_light) { n.setLight(LIGHTBANK_DAY, current_light, nodemgr); } if(diminish_light(current_light) != 0) { light_sources.insert(pos_relative + pos); } if(current_light == 0 && stopped_to_solid_object) { if(black_air_left) { *black_air_left = true; } } } // Whether or not the block below should see LIGHT_SUN bool sunlight_should_go_down = (current_light == LIGHT_SUN); /* If the block below hasn't already been marked invalid: Check if the node below the block has proper sunlight at top. If not, the block below is invalid. Ignore non-transparent nodes as they always have no light */ if(block_below_is_valid) { MapNode n = getNodeParent(v3s16(x, -1, z), &is_valid_position); if (n) { if(nodemgr->get(n).light_propagates) { if(n.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN && sunlight_should_go_down == false) block_below_is_valid = false; else if(n.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN && sunlight_should_go_down == true) block_below_is_valid = false; } } else { /*std::cout<<"InvalidBlockException for bottom block node" <<std::endl;*/ // Just no block below, no need to panic. } } } } return block_below_is_valid; }
void ClientMap::updateDrawList(video::IVideoDriver* driver, float dtime, unsigned int max_cycle_ms) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); //g_profiler->add("CM::updateDrawList() count", 1); TimeTaker timer_step("ClientMap::updateDrawList"); INodeDefManager *nodemgr = m_gamedef->ndef(); if (!m_drawlist_last) m_drawlist_current = !m_drawlist_current; auto & drawlist = m_drawlist_current ? m_drawlist_1 : m_drawlist_0; if (!max_cycle_ms) max_cycle_ms = 300/getControl().fps_wanted; m_camera_mutex.Lock(); v3f camera_position = m_camera_position; f32 camera_fov = m_camera_fov; //v3s16 camera_offset = m_camera_offset; m_camera_mutex.Unlock(); // 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 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; // Take a fair amount as we will be dropping more out later // Umm... these additions are a bit strange but they are needed. v3s16 p_blocks_min( p_nodes_min.X / MAP_BLOCKSIZE - 3, p_nodes_min.Y / MAP_BLOCKSIZE - 3, p_nodes_min.Z / MAP_BLOCKSIZE - 3); v3s16 p_blocks_max( p_nodes_max.X / MAP_BLOCKSIZE + 1, p_nodes_max.Y / MAP_BLOCKSIZE + 1, p_nodes_max.Z / MAP_BLOCKSIZE + 1); // 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; int m_mesh_queued = 0; bool free_move = g_settings->getBool("free_move"); float range_max = 100000 * BS; if(m_control.range_all == false) range_max = m_control.wanted_range * BS; if (draw_nearest.empty()) { //ScopeProfiler sp(g_profiler, "CM::updateDrawList() make list", SPT_AVG); TimeTaker timer_step("ClientMap::updateDrawList make list"); auto lock = m_blocks.try_lock_shared_rec(); if (!lock->owns_lock()) return; draw_nearest.clear(); for(auto & ir : m_blocks) { auto bp = ir.first; if(m_control.range_all == false) { if(bp.X < p_blocks_min.X || bp.X > p_blocks_max.X || bp.Z > p_blocks_max.Z || bp.Z < p_blocks_min.Z || bp.Y < p_blocks_min.Y || bp.Y > p_blocks_max.Y) continue; } v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; // Block center position v3f blockpos( ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS, ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS, ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS ); f32 d = radius_box(blockpos, camera_position); //blockpos_relative.getLength(); if (d> range_max) continue; int range = d / (MAP_BLOCKSIZE * BS); draw_nearest.emplace_back(std::make_pair(bp, range)); } } const int maxq = 1000; // No occlusion culling when free_move is on and camera is // inside ground bool occlusion_culling_enabled = true; if(free_move) { MapNode n = getNodeNoEx(cam_pos_nodes); if(n.getContent() == CONTENT_IGNORE || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } u32 calls = 0, end_ms = porting::getTimeMs() + u32(max_cycle_ms); std::unordered_map<v3POS, bool, v3POSHash, v3POSEqual> occlude_cache; while (!draw_nearest.empty()) { auto ir = draw_nearest.back(); auto bp = ir.first; int range = ir.second; draw_nearest.pop_back(); ++calls; //auto block = getBlockNoCreateNoEx(bp); auto block = m_blocks.get(bp); if (!block) continue; int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes), bp); /* Compare block position to camera position, skip if not seen on display */ auto mesh = block->getMesh(mesh_step); if (mesh) mesh->updateCameraOffset(m_camera_offset); blocks_in_range++; auto smesh_size = block->mesh_size; /* Ignore if mesh doesn't exist */ { if(!mesh) { blocks_in_range_without_mesh++; if (m_mesh_queued < maxq || range <= 2) { m_client->addUpdateMeshTask(bp, false); ++m_mesh_queued; } continue; } if(mesh_step == mesh->step && block->getTimestamp() <= mesh->timestamp && !smesh_size) { blocks_in_range_without_mesh++; continue; } } /* Occlusion culling */ v3s16 cpn = bp * MAP_BLOCKSIZE; cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); float step = BS*1; float stepfac = 1.2; float startoff = BS*1; float endoff = -BS*MAP_BLOCKSIZE; //*1.42; //*1.42; v3s16 spn = cam_pos_nodes + v3s16(0,0,0); s16 bs2 = MAP_BLOCKSIZE/2 + 1; u32 needed_count = 1; if( range > 1 && smesh_size && occlusion_culling_enabled && isOccluded(this, spn, cpn + v3s16(0,0,0), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) ) { 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 == false && d > m_control.wanted_min_range * BS) continue; */ if (mesh_step != mesh->step && (m_mesh_queued < maxq*1.2 || range <= 2)) { m_client->addUpdateMeshTask(bp); ++m_mesh_queued; } if (block->getTimestamp() > mesh->timestamp + (smesh_size ? 0 : range >= 1 ? 60 : 5) && (m_mesh_queued < maxq*1.5 || range <= 2)) { m_client->addUpdateMeshTaskWithEdge(bp); ++m_mesh_queued; } if(!smesh_size) continue; mesh->incrementUsageTimer(dtime); // Add to set //block->refGrab(); block->resetUsageTimer(); drawlist.set(bp, block); blocks_drawn++; if(range * MAP_BLOCKSIZE > farthest_drawn) farthest_drawn = range * MAP_BLOCKSIZE; if (farthest_drawn > m_control.farthest_drawn) m_control.farthest_drawn = farthest_drawn; if (porting::getTimeMs() > end_ms) { break; } } m_drawlist_last = draw_nearest.size(); //if (m_drawlist_last) infostream<<"breaked UDL "<<m_drawlist_last<<" collected="<<drawlist.size()<<" calls="<<calls<<" s="<<m_blocks.size()<<" maxms="<<max_cycle_ms<<" fw="<<getControl().fps_wanted<<" morems="<<porting::getTimeMs() - end_ms<< " meshq="<<m_mesh_queued<<" occache="<<occlude_cache.size()<<std::endl; if (m_drawlist_last) return; //for (auto & ir : *m_drawlist) // ir.second->refDrop(); auto m_drawlist_old = !m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0; m_drawlist = m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0; m_drawlist_old->clear(); m_control.blocks_would_have_drawn = blocks_would_have_drawn; m_control.blocks_drawn = blocks_drawn; m_control.farthest_drawn = farthest_drawn; g_profiler->avg("CM: blocks total", m_blocks.size()); 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); }
void ClientMap::updateDrawList(video::IVideoDriver* driver) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); g_profiler->add("CM::updateDrawList() count", 1); INodeDefManager *nodemgr = m_gamedef->ndef(); for (std::map<v3s16, MapBlock*>::iterator i = m_drawlist.begin(); i != m_drawlist.end(); ++i) { 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; for (std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); si != m_sectors.end(); ++si) { MapSector *sector = si->second; v2s16 sp = sector->getPos(); if (m_control.range_all == false) { 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 (MapBlockVect::iterator i = sectorblocks.begin(); i != sectorblocks.end(); ++i) { MapBlock *block = *i; /* Compare block position to camera position, skip if not seen on display */ if (block->mesh != NULL) block->mesh->updateCameraOffset(m_camera_offset); float range = 100000 * BS; if (m_control.range_all == false) range = m_control.wanted_range * BS; float d = 0.0; if (!isBlockInSight(block->getPos(), camera_position, camera_direction, camera_fov, range, &d)) continue; // This is ugly (spherical distance limit?) /*if(m_control.range_all == false && d - 0.5*BS*MAP_BLOCKSIZE > range) continue;*/ blocks_in_range++; /* Ignore if mesh doesn't exist */ { //MutexAutoLock lock(block->mesh_mutex); if (block->mesh == NULL) { blocks_in_range_without_mesh++; continue; } } /* Occlusion culling */ // 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 || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; cpn += v3s16(MAP_BLOCKSIZE / 2, MAP_BLOCKSIZE / 2, MAP_BLOCKSIZE / 2); float step = BS * 1; float stepfac = 1.1; float startoff = BS * 1; // The occlusion search of 'isOccluded()' must stop short of the target // point by distance 'endoff' (end offset) to not enter the target mapblock. // For the 8 mapblock corners 'endoff' must therefore be the maximum diagonal // of a mapblock, because we must consider all view angles. // sqrt(1^2 + 1^2 + 1^2) = 1.732 float endoff = -BS * MAP_BLOCKSIZE * 1.732050807569; v3s16 spn = cam_pos_nodes; s16 bs2 = MAP_BLOCKSIZE / 2 + 1; // to reduce the likelihood of falsely occluded blocks // require at least two solid blocks // this is a HACK, we should think of a more precise algorithm u32 needed_count = 2; if (occlusion_culling_enabled && // For the central point of the mapblock 'endoff' can be halved isOccluded(this, spn, cpn, step, stepfac, startoff, endoff / 2.0f, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr)) { 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); } m_control.blocks_would_have_drawn = blocks_would_have_drawn; m_control.blocks_drawn = blocks_drawn; m_control.farthest_drawn = farthest_drawn; 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); }
void ClientMap::updateDrawList(video::IVideoDriver* driver) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); g_profiler->add("CM::updateDrawList() count", 1); for (std::map<v3s16, MapBlock*>::iterator i = m_drawlist.begin(); i != m_drawlist.end(); ++i) { 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 (std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); si != m_sectors.end(); ++si) { MapSector *sector = si->second; v2s16 sp = sector->getPos(); if (m_control.range_all == false) { 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 (MapBlockVect::iterator i = sectorblocks.begin(); i != sectorblocks.end(); ++i) { MapBlock *block = *i; /* 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); } m_control.blocks_would_have_drawn = blocks_would_have_drawn; m_control.blocks_drawn = blocks_drawn; m_control.farthest_drawn = farthest_drawn; 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); }
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) { INodeDefManager *nodemgr = m_gamedef->ndef(); //m_dout<<DTIME<<"Rendering map..."<<std::endl; DSTACK(__FUNCTION_NAME); 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. */ int time1 = time(0); /* Get animation parameters */ float animation_time = m_client->getAnimationTime(); int crack = m_client->getCrackLevel(); u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); m_camera_mutex.Lock(); v3f camera_position = m_camera_position; v3f camera_direction = m_camera_direction; f32 camera_fov = m_camera_fov; m_camera_mutex.Unlock(); /* Get all blocks and draw all visible ones */ v3s16 cam_pos_nodes = floatToInt(camera_position, BS); v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; // Take a fair amount as we will be dropping more out later // Umm... these additions are a bit strange but they are needed. v3s16 p_blocks_min( p_nodes_min.X / MAP_BLOCKSIZE - 3, p_nodes_min.Y / MAP_BLOCKSIZE - 3, p_nodes_min.Z / MAP_BLOCKSIZE - 3); v3s16 p_blocks_max( p_nodes_max.X / MAP_BLOCKSIZE + 1, p_nodes_max.Y / MAP_BLOCKSIZE + 1, p_nodes_max.Z / MAP_BLOCKSIZE + 1); 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; // 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; /* Collect a set of blocks for drawing */ core::map<v3s16, MapBlock*> drawset; { ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG); for(core::map<v2s16, MapSector*>::Iterator si = m_sectors.getIterator(); si.atEnd() == false; si++) { MapSector *sector = si.getNode()->getValue(); v2s16 sp = sector->getPos(); if(m_control.range_all == false) { 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; } core::list< MapBlock * > sectorblocks; sector->getBlocks(sectorblocks); /* Loop through blocks in sector */ u32 sector_blocks_drawn = 0; core::list< MapBlock * >::Iterator i; for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) { MapBlock *block = *i; /* Compare block position to camera position, skip if not seen on display */ float range = 100000 * BS; if(m_control.range_all == false) range = m_control.wanted_range * BS; float d = 0.0; if(isBlockInSight(block->getPos(), camera_position, camera_direction, camera_fov, range, &d) == false) { continue; } // This is ugly (spherical distance limit?) /*if(m_control.range_all == false && d - 0.5*BS*MAP_BLOCKSIZE > range) continue;*/ blocks_in_range++; /* Ignore if mesh doesn't exist */ { //JMutexAutoLock lock(block->mesh_mutex); if(block->mesh == NULL){ blocks_in_range_without_mesh++; continue; } } /* Occlusion culling */ // 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 || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); float step = BS*1; float stepfac = 1.1; float startoff = BS*1; float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; v3s16 spn = cam_pos_nodes + v3s16(0,0,0); s16 bs2 = MAP_BLOCKSIZE/2 + 1; u32 needed_count = 1; if( occlusion_culling_enabled && isOccluded(this, spn, cpn + v3s16(0,0,0), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) ) { 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 == false && d > m_control.wanted_min_range * BS) continue; // Mesh animation { //JMutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; // 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(); } } // Add to set drawset[block->getPos()] = block; sector_blocks_drawn++; blocks_drawn++; } // foreach sectorblocks if(sector_blocks_drawn != 0) m_last_drawn_sectors[sp] = true; } } // ScopeProfiler /* Draw the selected MapBlocks */ { ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); int timecheck_counter = 0; for(core::map<v3s16, MapBlock*>::Iterator i = drawset.getIterator(); i.atEnd() == false; i++) { { timecheck_counter++; if(timecheck_counter > 50) { timecheck_counter = 0; int time2 = time(0); if(time2 > time1 + 4) { infostream<<"ClientMap::renderMap(): " "Rendering takes ages, returning." <<std::endl; return; } } } MapBlock *block = i.getNode()->getValue(); /* Draw the faces of the block */ { //JMutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); scene::SMesh *mesh = mapBlockMesh->getMesh(); assert(mesh); u32 c = mesh->getMeshBufferCount(); bool stuff_actually_drawn = false; for(u32 i=0; i<c; i++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); const video::SMaterial& material = buf->getMaterial(); video::IMaterialRenderer* rnd = driver->getMaterialRenderer(material.MaterialType); bool transparent = (rnd && rnd->isTransparent()); // Render transparent on transparent pass and likewise. if(transparent == is_transparent_pass) { if(buf->getVertexCount() == 0) errorstream<<"Block ["<<analyze_block(block) <<"] contains an empty meshbuf"<<std::endl; /* This *shouldn't* hurt too much because Irrlicht doesn't change opengl textures if the old material has the same texture. */ driver->setMaterial(buf->getMaterial()); driver->drawMeshBuffer(buf); vertex_count += buf->getVertexCount(); meshbuffer_count++; stuff_actually_drawn = true; } } if(stuff_actually_drawn) blocks_had_pass_meshbuf++; else blocks_without_stuff++; } } } // ScopeProfiler // Log only on solid pass because values are the same if(pass == scene::ESNRP_SOLID){ 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: 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); m_control.blocks_drawn = blocks_drawn; m_control.blocks_would_have_drawn = blocks_would_have_drawn; /*infostream<<"renderMap(): is_transparent_pass="******", rendered "<<vertex_count<<" vertices."<<std::endl;*/ }
void ClientMap::updateDrawList(video::IVideoDriver* driver, float dtime) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); //g_profiler->add("CM::updateDrawList() count", 1); INodeDefManager *nodemgr = m_gamedef->ndef(); if (!m_drawlist_last) m_drawlist_current = !m_drawlist_current; auto & drawlist = m_drawlist_current ? m_drawlist_1 : m_drawlist_0; float max_cycle_ms = 0.1/getControl().fps_wanted; u32 n = 0, calls = 0, end_ms = porting::getTimeMs() + max_cycle_ms; m_camera_mutex.Lock(); v3f camera_position = m_camera_position; v3f camera_direction = m_camera_direction; f32 camera_fov = m_camera_fov; v3s16 camera_offset = m_camera_offset; m_camera_mutex.Unlock(); // 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 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; // Take a fair amount as we will be dropping more out later // Umm... these additions are a bit strange but they are needed. v3s16 p_blocks_min( p_nodes_min.X / MAP_BLOCKSIZE - 3, p_nodes_min.Y / MAP_BLOCKSIZE - 3, p_nodes_min.Z / MAP_BLOCKSIZE - 3); v3s16 p_blocks_max( p_nodes_max.X / MAP_BLOCKSIZE + 1, p_nodes_max.Y / MAP_BLOCKSIZE + 1, p_nodes_max.Z / MAP_BLOCKSIZE + 1); // 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; { auto lock = m_blocks.lock_shared_rec(); for(auto & ir : m_blocks) { if (n++ < m_drawlist_last) continue; else m_drawlist_last = 0; ++calls; MapBlock *block = ir.second; auto bp = block->getPos(); if(m_control.range_all == false) { if(bp.X < p_blocks_min.X || bp.X > p_blocks_max.X || bp.Z > p_blocks_max.Z || bp.Z < p_blocks_min.Z || bp.Y < p_blocks_min.Y || bp.Y > p_blocks_max.Y) continue; } int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes).getDistanceFrom(block->getPos())); /* Compare block position to camera position, skip if not seen on display */ if (block->getMesh(mesh_step) != NULL) block->getMesh(mesh_step)->updateCameraOffset(m_camera_offset); float range = 100000 * BS; if(m_control.range_all == false) range = m_control.wanted_range * BS; float d = 0.0; if(isBlockInSight(block->getPos(), camera_position, camera_direction, camera_fov, range, &d) == false) { continue; } // This is ugly (spherical distance limit?) /*if(m_control.range_all == false && d - 0.5*BS*MAP_BLOCKSIZE > range) continue;*/ blocks_in_range++; /* Ignore if mesh doesn't exist */ { //JMutexAutoLock lock(block->mesh_mutex); if(block->getMesh(mesh_step) == NULL){ blocks_in_range_without_mesh++; continue; } } /* Occlusion culling */ // 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 || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); float step = BS*1; float stepfac = 1.1; float startoff = BS*1; float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; v3s16 spn = cam_pos_nodes + v3s16(0,0,0); s16 bs2 = MAP_BLOCKSIZE/2 + 1; u32 needed_count = 1; if( occlusion_culling_enabled && isOccluded(this, spn, cpn + v3s16(0,0,0), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) ) { 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 == false && d > m_control.wanted_min_range * BS) continue; if (m_control.farmesh && mesh_step != block->getMesh(mesh_step)->step) { //&& !block->mesh->transparent m_client->addUpdateMeshTask(block->getPos(), false, mesh_step == 1); } block->getMesh(mesh_step)->incrementUsageTimer(dtime); // Add to set block->refGrab(); drawlist[block->getPos()] = block; blocks_drawn++; if(d/BS > farthest_drawn) farthest_drawn = d/BS; if (porting::getTimeMs() > end_ms) { m_drawlist_last = n; break; } } } if (!calls) m_drawlist_last = 0; if (m_drawlist_last) return; for (auto & ir : *m_drawlist) ir.second->refDrop(); m_drawlist->clear(); m_drawlist = m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0; m_control.blocks_would_have_drawn = blocks_would_have_drawn; m_control.blocks_drawn = blocks_drawn; m_control.farthest_drawn = farthest_drawn; 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); }
u32 Map::transformLiquidsReal(Server *m_server, unsigned int max_cycle_ms) { INodeDefManager *nodemgr = m_gamedef->ndef(); DSTACK(__FUNCTION_NAME); //TimeTaker timer("transformLiquidsReal()"); u32 loopcount = 0; u32 initial_size = transforming_liquid_size(); s32 regenerated = 0; #if LIQUID_DEBUG bool debug = 1; #endif u8 relax = g_settings->getS16("liquid_relax"); bool fast_flood = g_settings->getS16("liquid_fast_flood"); int water_level = g_settings->getS16("water_level"); s16 liquid_pressure = m_server->m_emerge->params.liquid_pressure; //g_settings->getS16NoEx("liquid_pressure", liquid_pressure); // list of nodes that due to viscosity have not reached their max level height //std::unordered_map<v3POS, bool, v3POSHash, v3POSEqual> must_reflow, must_reflow_second, must_reflow_third; std::list<v3POS> must_reflow, must_reflow_second, must_reflow_third; // List of MapBlocks that will require a lighting update (due to lava) u16 loop_rand = myrand(); u32 end_ms = porting::getTimeMs() + max_cycle_ms; NEXT_LIQUID: ; while (transforming_liquid_size() > 0) { // This should be done here so that it is done when continue is used if (loopcount >= initial_size * 2 || porting::getTimeMs() > end_ms) break; ++loopcount; /* Get a queued transforming liquid node */ v3POS p0; { //JMutexAutoLock lock(m_transforming_liquid_mutex); p0 = transforming_liquid_pop(); } s16 total_level = 0; //u16 level_max = 0; // surrounding flowing liquid nodes NodeNeighbor neighbors[7] = { { } }; // current level of every block s8 liquid_levels[7] = { -1, -1, -1, -1, -1, -1, -1}; // target levels s8 liquid_levels_want[7] = { -1, -1, -1, -1, -1, -1, -1}; s8 can_liquid_same_level = 0; s8 can_liquid = 0; // warning! when MINETEST_PROTO enabled - CONTENT_IGNORE != 0 content_t liquid_kind = CONTENT_IGNORE; content_t liquid_kind_flowing = CONTENT_IGNORE; content_t melt_kind = CONTENT_IGNORE; content_t melt_kind_flowing = CONTENT_IGNORE; //s8 viscosity = 0; /* Collect information about the environment, start from self */ for (u8 e = 0; e < 7; e++) { u8 i = liquid_explore_map[e]; NodeNeighbor & nb = neighbors[i]; nb.pos = p0 + liquid_flow_dirs[i]; nb.node = getNodeNoEx(neighbors[i].pos); nb.content = nb.node.getContent(); NeighborType nt = NEIGHBOR_SAME_LEVEL; switch (i) { case D_TOP: nt = NEIGHBOR_UPPER; break; case D_BOTTOM: nt = NEIGHBOR_LOWER; break; } nb.type = nt; nb.liquid = 0; nb.infinity = 0; nb.weight = 0; nb.drop = 0; if (!nb.node) { //if (i == D_SELF && (loopcount % 2) && initial_size < m_liquid_step_flow * 3) // must_reflow_third[nb.pos] = 1; // must_reflow_third.push_back(nb.pos); continue; } auto f = nodemgr->get(nb.content); switch (f.liquid_type) { case LIQUID_NONE: if (nb.content == CONTENT_AIR) { liquid_levels[i] = 0; nb.liquid = 1; } //TODO: if (nb.content == CONTENT_AIR || nodemgr->get(nb.node).buildable_to && !nodemgr->get(nb.node).walkable) { // need lua drop api for drop torches else if ( melt_kind_flowing != CONTENT_IGNORE && nb.content == melt_kind_flowing && nb.type != NEIGHBOR_UPPER && !(loopcount % 2)) { u8 melt_max_level = nb.node.getMaxLevel(nodemgr); u8 my_max_level = MapNode(liquid_kind_flowing).getMaxLevel(nodemgr); liquid_levels[i] = (float)my_max_level / melt_max_level * nb.node.getLevel(nodemgr); if (liquid_levels[i]) nb.liquid = 1; } else if ( melt_kind != CONTENT_IGNORE && nb.content == melt_kind && nb.type != NEIGHBOR_UPPER && !(loopcount % 8)) { liquid_levels[i] = nodemgr->get(liquid_kind_flowing).getMaxLevel(); if (liquid_levels[i]) nb.liquid = 1; } else { int drop = ((ItemGroupList) f.groups)["drop_by_liquid"]; if (drop && !(loopcount % drop) ) { liquid_levels[i] = 0; nb.liquid = 1; nb.drop = 1; } } // todo: for erosion add something here.. break; case LIQUID_SOURCE: // if this node is not (yet) of a liquid type, // choose the first liquid type we encounter if (liquid_kind_flowing == CONTENT_IGNORE) liquid_kind_flowing = nodemgr->getId( f.liquid_alternative_flowing); if (liquid_kind == CONTENT_IGNORE) liquid_kind = nb.content; if (liquid_kind_flowing == CONTENT_IGNORE) liquid_kind_flowing = liquid_kind; if (melt_kind == CONTENT_IGNORE) melt_kind = nodemgr->getId(f.melt); if (melt_kind_flowing == CONTENT_IGNORE) melt_kind_flowing = nodemgr->getId( nodemgr->get(nodemgr->getId(f.melt) ).liquid_alternative_flowing); if (melt_kind_flowing == CONTENT_IGNORE) melt_kind_flowing = melt_kind; if (nb.content == liquid_kind) { if (nb.node.param2 & LIQUID_STABLE_MASK) continue; liquid_levels[i] = nb.node.getLevel(nodemgr); //LIQUID_LEVEL_SOURCE; nb.liquid = 1; nb.infinity = (nb.node.param2 & LIQUID_INFINITY_MASK); } break; case LIQUID_FLOWING: // if this node is not (yet) of a liquid type, // choose the first liquid type we encounter if (liquid_kind_flowing == CONTENT_IGNORE) liquid_kind_flowing = nb.content; if (liquid_kind == CONTENT_IGNORE) liquid_kind = nodemgr->getId( f.liquid_alternative_source); if (liquid_kind == CONTENT_IGNORE) liquid_kind = liquid_kind_flowing; if (melt_kind_flowing == CONTENT_IGNORE) melt_kind_flowing = nodemgr->getId(f.melt); if (melt_kind == CONTENT_IGNORE) melt_kind = nodemgr->getId(nodemgr->get(nodemgr->getId( f.melt)).liquid_alternative_source); if (melt_kind == CONTENT_IGNORE) melt_kind = melt_kind_flowing; if (nb.content == liquid_kind_flowing) { if (nb.node.param2 & LIQUID_STABLE_MASK) continue; liquid_levels[i] = nb.node.getLevel(nodemgr); nb.liquid = 1; nb.infinity = (nb.node.param2 & LIQUID_INFINITY_MASK); } break; } // only self, top, bottom swap if (f.liquid_type && e <= 2) { try { nb.weight = ((ItemGroupList) f.groups)["weight"]; if (e == 1 && neighbors[D_BOTTOM].weight && neighbors[D_SELF].weight > neighbors[D_BOTTOM].weight) { setNode(neighbors[D_SELF].pos, neighbors[D_BOTTOM].node); setNode(neighbors[D_BOTTOM].pos, neighbors[D_SELF].node); //must_reflow_second[neighbors[D_SELF].pos] = 1; //must_reflow_second[neighbors[D_BOTTOM].pos] = 1; must_reflow_second.push_back(neighbors[D_SELF].pos); must_reflow_second.push_back(neighbors[D_BOTTOM].pos); #if LIQUID_DEBUG infostream << "Liquid swap1" << neighbors[D_SELF].pos << nodemgr->get(neighbors[D_SELF].node).name << neighbors[D_SELF].node << " w=" << neighbors[D_SELF].weight << " VS " << neighbors[D_BOTTOM].pos << nodemgr->get(neighbors[D_BOTTOM].node).name << neighbors[D_BOTTOM].node << " w=" << neighbors[D_BOTTOM].weight << std::endl; #endif goto NEXT_LIQUID; } if (e == 2 && neighbors[D_SELF].weight && neighbors[D_TOP].weight > neighbors[D_SELF].weight) { setNode(neighbors[D_SELF].pos, neighbors[D_TOP].node); setNode(neighbors[D_TOP].pos, neighbors[D_SELF].node); //must_reflow_second[neighbors[D_SELF].pos] = 1; //must_reflow_second[neighbors[D_TOP].pos] = 1; must_reflow_second.push_back(neighbors[D_SELF].pos); must_reflow_second.push_back(neighbors[D_TOP].pos); #if LIQUID_DEBUG infostream << "Liquid swap2" << neighbors[D_TOP].pos << nodemgr->get(neighbors[D_TOP].node).name << neighbors[D_TOP].node << " w=" << neighbors[D_TOP].weight << " VS " << neighbors[D_SELF].pos << nodemgr->get(neighbors[D_SELF].node).name << neighbors[D_SELF].node << " w=" << neighbors[D_SELF].weight << std::endl; #endif goto NEXT_LIQUID; } } catch(InvalidPositionException &e) { verbosestream << "transformLiquidsReal: weight: setNode() failed:" << nb.pos << ":" << e.what() << std::endl; //goto NEXT_LIQUID; } } if (nb.liquid) { liquid_levels_want[i] = 0; ++can_liquid; if(nb.type == NEIGHBOR_SAME_LEVEL) ++can_liquid_same_level; } if (liquid_levels[i] > 0) total_level += liquid_levels[i]; #if LIQUID_DEBUG infostream << "get node i=" << (int)i << " " << PP(nb.pos) << " c=" << nb.content << " p0=" << (int)nb.node.param0 << " p1=" << (int)nb.node.param1 << " p2=" << (int)nb.node.param2 << " lt=" << f.liquid_type //<< " lk=" << liquid_kind << " lkf=" << liquid_kind_flowing << " l=" << nb.liquid << " inf=" << nb.infinity << " nlevel=" << (int)liquid_levels[i] << " totallevel=" << (int)total_level << " cansame=" << (int)can_liquid_same_level << " Lmax=" << (int)nodemgr->get(liquid_kind_flowing).getMaxLevel() << std::endl; #endif } if (liquid_kind == CONTENT_IGNORE || !neighbors[D_SELF].liquid || total_level <= 0) continue; s16 level_max = nodemgr->get(liquid_kind_flowing).getMaxLevel(); s16 level_max_compressed = nodemgr->get(liquid_kind_flowing).getMaxLevel(1); s16 pressure = liquid_pressure ? ((ItemGroupList) nodemgr->get(liquid_kind).groups)["pressure"] : 0; auto liquid_renewable = nodemgr->get(liquid_kind).liquid_renewable; #if LIQUID_DEBUG s16 total_was = total_level; //debug #endif //viscosity = nodemgr->get(liquid_kind).viscosity; s16 level_avg = total_level / can_liquid; if (!pressure && level_avg) { level_avg = level_max; } #if LIQUID_DEBUG if (debug) infostream << " go: " << nodemgr->get(liquid_kind).name << " total_level=" << (int)total_level //<<" total_was="<<(int)total_was << " level_max=" << (int)level_max << " level_max_compressed=" << (int)level_max_compressed << " level_avg=" << (int)level_avg << " pressure=" << (int)pressure << " can_liquid=" << (int)can_liquid << " can_liquid_same_level=" << (int)can_liquid_same_level << std::endl; ; #endif // fill bottom block if (neighbors[D_BOTTOM].liquid) { liquid_levels_want[D_BOTTOM] = level_avg > level_max ? level_avg : total_level > level_max ? level_max : total_level; total_level -= liquid_levels_want[D_BOTTOM]; //if (pressure && total_level && liquid_levels_want[D_BOTTOM] < level_max_compressed) { // ++liquid_levels_want[D_BOTTOM]; // --total_level; //} } //relax up u16 relax_want = level_max * can_liquid_same_level; if ( liquid_renewable && relax && ((p0.Y == water_level) || (fast_flood && p0.Y <= water_level)) && level_max > 1 && liquid_levels[D_TOP] == 0 && liquid_levels[D_BOTTOM] >= level_max && total_level >= relax_want - (can_liquid_same_level - relax) && total_level < relax_want && can_liquid_same_level >= relax + 1) { regenerated += relax_want - total_level; #if LIQUID_DEBUG infostream << " relax_up: " << " total_level=" << (int)total_level << " to=> " << int(relax_want) << std::endl; #endif total_level = relax_want; } // prevent lakes in air above unloaded blocks if ( liquid_levels[D_TOP] == 0 && p0.Y > water_level && level_max > 1 && !neighbors[D_BOTTOM].node && !(loopcount % 3)) { --total_level; #if LIQUID_DEBUG infostream << " above unloaded fix: " << " total_level=" << (int)total_level << std::endl; #endif } // calculate self level 5 blocks u16 want_level = level_avg > level_max ? level_avg : total_level >= level_max * can_liquid_same_level ? level_max : total_level / can_liquid_same_level; total_level -= want_level * can_liquid_same_level; /* if (pressure && total_level > 0 && neighbors[D_BOTTOM].liquid) { // bottom pressure +1 ++liquid_levels_want[D_BOTTOM]; --total_level; #if LIQUID_DEBUG infostream << " bottom1 pressure+1: " << " bottom=" << (int)liquid_levels_want[D_BOTTOM] << " total_level=" << (int)total_level << std::endl; #endif } */ //relax down if ( liquid_renewable && relax && p0.Y >= water_level - 1 && p0.Y <= water_level + 1 && liquid_levels[D_TOP] == 0 && (total_level <= 1 || !(loopcount % 2)) && level_max > 1 && liquid_levels[D_BOTTOM] >= level_max && want_level <= 0 && total_level <= (can_liquid_same_level - relax) && can_liquid_same_level >= relax + 1) { #if LIQUID_DEBUG infostream << " relax_down: " << " total_level WAS=" << (int)total_level << " to => 0" << std::endl; #endif regenerated -= total_level; total_level = 0; } for (u16 ir = D_SELF; ir < D_TOP; ++ir) { // fill only same level u16 ii = liquid_random_map[(loopcount + loop_rand + 1) % 4][ir]; if (!neighbors[ii].liquid) continue; liquid_levels_want[ii] = want_level; //if (viscosity > 1 && (liquid_levels_want[ii]-liquid_levels[ii]>8-viscosity)) // randomly place rest of divide if (liquid_levels_want[ii] < level_max && total_level > 0) { if (level_max > LIQUID_LEVEL_SOURCE || loopcount % 3 || liquid_levels[ii] <= 0) { if (liquid_levels[ii] > liquid_levels_want[ii]) { ++liquid_levels_want[ii]; --total_level; } } else { ++liquid_levels_want[ii]; --total_level; } } } for (u16 ir = D_SELF; ir < D_TOP; ++ir) { if (total_level < 1) break; u16 ii = liquid_random_map[(loopcount + loop_rand + 2) % 4][ir]; if (liquid_levels_want[ii] >= 0 && liquid_levels_want[ii] < level_max) { ++liquid_levels_want[ii]; --total_level; } } // fill top block if can if (neighbors[D_TOP].liquid && total_level > 0) { //infostream<<"compressing to top was="<<liquid_levels_want[D_TOP]<<" add="<<total_level<<std::endl; //liquid_levels_want[D_TOP] = total_level>level_max_compressed?level_max_compressed:total_level; liquid_levels_want[D_TOP] = total_level > level_max ? level_max : total_level; total_level -= liquid_levels_want[D_TOP]; //if (liquid_levels_want[D_TOP] && total_level && pressure) { if (total_level > 0 && pressure) { /* if (total_level > 0 && neighbors[D_BOTTOM].liquid) { // bottom pressure +2 ++liquid_levels_want[D_BOTTOM]; --total_level; } */ //compressing self level while can //for (u16 ir = D_SELF; ir < D_TOP; ++ir) { for (u16 ir = D_BOTTOM; ir <= D_TOP; ++ir) { if (total_level < 1) break; u16 ii = liquid_random_map[(loopcount + loop_rand + 3) % 4][ir]; if (neighbors[ii].liquid && liquid_levels_want[ii] < level_max_compressed) { ++liquid_levels_want[ii]; --total_level; } } /* if (total_level > 0 && neighbors[D_BOTTOM].liquid) { // bottom pressure +2 ++liquid_levels_want[D_BOTTOM]; --total_level; #if LIQUID_DEBUG infostream << " bottom2 pressure+1: " << " bottom=" << (int)liquid_levels_want[D_BOTTOM] << " total_level=" << (int)total_level << std::endl; #endif } */ } } if (pressure) { if (neighbors[D_BOTTOM].liquid && liquid_levels_want[D_BOTTOM] < level_max_compressed && liquid_levels_want[D_TOP] > 0 ) { //if (liquid_levels_want[D_BOTTOM] <= liquid_levels_want[D_TOP]) { --liquid_levels_want[D_TOP]; ++liquid_levels_want[D_BOTTOM]; #if LIQUID_DEBUG infostream << " bottom1 pressure+: " << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl; #endif //} } else if ( neighbors[D_BOTTOM].liquid && liquid_levels_want[D_BOTTOM] < level_max_compressed && liquid_levels_want[D_SELF] > level_max ) { if (liquid_levels_want[D_BOTTOM] <= liquid_levels_want[D_SELF]) { --liquid_levels_want[D_SELF]; ++liquid_levels_want[D_BOTTOM]; #if LIQUID_DEBUG infostream << " bottom2 pressure+: " << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl; #endif } } else if ( neighbors[D_TOP].liquid && liquid_levels_want[D_SELF] < level_max_compressed && liquid_levels_want[D_TOP] > level_max ) { if (liquid_levels_want[D_SELF] <= liquid_levels_want[D_TOP]) { --liquid_levels_want[D_TOP]; ++liquid_levels_want[D_SELF]; #if LIQUID_DEBUG infostream << " bottom3 pressure+: " << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl; #endif } } if (liquid_levels_want[D_TOP] > level_max && relax && total_level <= 0 && level_avg > level_max && liquid_levels_want[D_TOP] < level_avg) { #if LIQUID_DEBUG infostream << " top pressure relax: " << " top=" << (int)liquid_levels_want[D_TOP] << " to=>" << level_avg << std::endl; #endif //regenerated += level_avg - liquid_levels_want[D_TOP]; //liquid_levels_want[D_TOP] = level_avg; regenerated += 1 ; liquid_levels_want[D_TOP] += 1; } } #if LIQUID_DEBUG if (total_level > 0) infostream << " rest 1: " << " wtop=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl; #endif if (total_level > 0 && neighbors[D_TOP].liquid && liquid_levels_want[D_TOP] < level_max_compressed) { s16 add = (total_level > level_max_compressed - liquid_levels_want[D_TOP]) ? level_max_compressed - liquid_levels_want[D_TOP] : total_level; liquid_levels_want[D_TOP] += add; total_level -= add; } if (total_level > 0 && neighbors[D_SELF].liquid && liquid_levels_want[D_SELF] < level_max_compressed) { // very rare, compressed only s16 add = (total_level > level_max_compressed - liquid_levels_want[D_SELF]) ? level_max_compressed - liquid_levels_want[D_SELF] : total_level; #if LIQUID_DEBUG if (total_level > 0) infostream << " rest 2: " << " wself=" << (int)liquid_levels_want[D_SELF] << " total_level=" << (int)total_level << " add=" << (int)add << std::endl; #endif liquid_levels_want[D_SELF] += add; total_level -= add; } #if LIQUID_DEBUG if (total_level > 0) infostream << " rest 3: " << " total_level=" << (int)total_level << std::endl; #endif for (u16 ii = 0; ii < 7; ii++) { // infinity and cave flood optimization if (neighbors[ii].infinity && liquid_levels_want[ii] < liquid_levels[ii]) { #if LIQUID_DEBUG infostream << " infinity: was=" << (int)ii << " = " << (int)liquid_levels_want[ii] << " to=" << (int)liquid_levels[ii] << std::endl; #endif regenerated += liquid_levels[ii] - liquid_levels_want[ii]; liquid_levels_want[ii] = liquid_levels[ii]; } else if ( liquid_levels_want[ii] >= 0 && liquid_levels_want[ii] < level_max && level_max > 1 && fast_flood && p0.Y < water_level && initial_size >= 1000 && ii != D_TOP && want_level >= level_max / 4 && can_liquid_same_level >= 5 && liquid_levels[D_TOP] >= level_max) { #if LIQUID_DEBUG infostream << " flood_fast: was=" << (int)ii << " = " << (int)liquid_levels_want[ii] << " to=" << (int)level_max << std::endl; #endif regenerated += level_max - liquid_levels_want[ii]; liquid_levels_want[ii] = level_max; } } #if LIQUID_DEBUG if (total_level != 0) //|| flowed != volume) infostream << " AFTER err level=" << (int)total_level //<< " flowed="<<flowed<< " volume=" << volume << " max=" << (int)level_max << " wantsame=" << (int)want_level << " top=" << (int)liquid_levels_want[D_TOP] << " topwas=" << (int)liquid_levels[D_TOP] << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " botwas=" << (int)liquid_levels[D_BOTTOM] << std::endl; s16 flowed = 0; // for debug #endif #if LIQUID_DEBUG if (debug) infostream << " dpress=" << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << std::endl; #endif for (u16 r = 0; r < 7; r++) { u16 i = liquid_random_map[(loopcount + loop_rand + 4) % 4][r]; if (liquid_levels_want[i] < 0 || !neighbors[i].liquid) continue; #if LIQUID_DEBUG if (debug) infostream << " set=" << i << " " << neighbors[i].pos << " want=" << (int)liquid_levels_want[i] << " was=" << (int) liquid_levels[i] << std::endl; #endif /* disabled because brokes constant volume of lava u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity; if (viscosity > 1 && liquid_levels_want[i] != liquid_levels[i]) { // amount to gain, limited by viscosity // must be at least 1 in absolute value s8 level_inc = liquid_levels_want[i] - liquid_levels[i]; if (level_inc < -viscosity || level_inc > viscosity) new_node_level = liquid_levels[i] + level_inc/viscosity; else if (level_inc < 0) new_node_level = liquid_levels[i] - 1; else if (level_inc > 0) new_node_level = liquid_levels[i] + 1; } else { */ // last level must flow down on stairs if (liquid_levels_want[i] != liquid_levels[i] && liquid_levels[D_TOP] <= 0 && (!neighbors[D_BOTTOM].liquid || level_max == 1) && liquid_levels_want[i] >= 1 && liquid_levels_want[i] <= 2) { for (u16 ir = D_SELF + 1; ir < D_TOP; ++ir) { // only same level u16 ii = liquid_random_map[(loopcount + loop_rand + 5) % 4][ir]; if (neighbors[ii].liquid) must_reflow_second.push_back(neighbors[i].pos + liquid_flow_dirs[ii]); //must_reflow_second[neighbors[i].pos + liquid_flow_dirs[ii]] = 1; } } #if LIQUID_DEBUG if (liquid_levels_want[i] > 0) flowed += liquid_levels_want[i]; #endif if (liquid_levels[i] == liquid_levels_want[i]) { continue; } if (neighbors[i].drop) {// && level_max > 1 && total_level >= level_max - 1 m_server->getEnv().getScriptIface()->node_drop(neighbors[i].pos, 2); } neighbors[i].node.setContent(liquid_kind_flowing); neighbors[i].node.setLevel(nodemgr, liquid_levels_want[i], 1); try { setNode(neighbors[i].pos, neighbors[i].node); } catch(InvalidPositionException &e) { verbosestream << "transformLiquidsReal: setNode() failed:" << neighbors[i].pos << ":" << e.what() << std::endl; } // If node emits light, MapBlock requires lighting update // or if node removed v3POS blockpos = getNodeBlockPos(neighbors[i].pos); MapBlock *block = getBlockNoCreateNoEx(blockpos, true); // remove true if light bugs if(block != NULL) { //modified_blocks[blockpos] = block; if(!nodemgr->get(neighbors[i].node).light_propagates || nodemgr->get(neighbors[i].node).light_source) // better to update always lighting_modified_blocks.set_try(block->getPos(), block); } // fmtodo: make here random %2 or.. if (total_level < level_max * can_liquid) must_reflow.push_back(neighbors[i].pos); } #if LIQUID_DEBUG //if (total_was != flowed) { if (total_was > flowed) { infostream << " volume changed! flowed=" << flowed << " total_was=" << total_was << " want_level=" << want_level; for (u16 rr = 0; rr <= 6; rr++) { infostream << " i=" << rr << ",b" << (int)liquid_levels[rr] << ",a" << (int)liquid_levels_want[rr]; } infostream << std::endl; } #endif /* //for better relax only same level if (changed) for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) { if (!neighbors[ii].l) continue; must_reflow.push_back(p0 + dirs[ii]); }*/ //g_profiler->graphAdd("liquids", 1); } u32 ret = loopcount >= initial_size ? 0 : transforming_liquid_size(); if (ret || loopcount > m_liquid_step_flow) m_liquid_step_flow += (m_liquid_step_flow > loopcount ? -1 : 1) * (int)loopcount / 10; /* if (loopcount) infostream<<"Map::transformLiquidsReal(): loopcount="<<loopcount<<" initial_size="<<initial_size <<" avgflow="<<m_liquid_step_flow <<" reflow="<<must_reflow.size() <<" reflow_second="<<must_reflow_second.size() <<" reflow_third="<<must_reflow_third.size() <<" queue="<< transforming_liquid_size() <<" per="<< porting::getTimeMs() - (end_ms - max_cycle_ms) <<" ret="<<ret<<std::endl; */ { //TimeTaker timer13("transformLiquidsReal() reflow"); //auto lock = m_transforming_liquid.lock_unique_rec(); std::lock_guard<std::mutex> lock(m_transforming_liquid_mutex); //m_transforming_liquid.insert(must_reflow.begin(), must_reflow.end()); for (const auto & p : must_reflow) m_transforming_liquid.push_back(p); must_reflow.clear(); //m_transforming_liquid.insert(must_reflow_second.begin(), must_reflow_second.end()); for (const auto & p : must_reflow_second) m_transforming_liquid.push_back(p); must_reflow_second.clear(); //m_transforming_liquid.insert(must_reflow_third.begin(), must_reflow_third.end()); for (const auto & p : must_reflow_third) m_transforming_liquid.push_back(p); must_reflow_third.clear(); } g_profiler->add("Server: liquids real processed", loopcount); if (regenerated) g_profiler->add("Server: liquids regenerated", regenerated); if (loopcount < initial_size) g_profiler->add("Server: liquids queue", initial_size); return loopcount; }