MapBlock * Map::createBlankBlock(v3POS & p) { auto lock = m_blocks.lock_unique_rec(); MapBlock *block = getBlockNoCreateNoEx(p, false, true); if (block != NULL) { infostream << "Block already created p=" << block->getPos() << std::endl; return block; } block = createBlankBlockNoInsert(p); m_blocks.set(p, block); return block; }
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) { 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(); } bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); /* 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; // 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(std::map<v3s16, MapBlock*>::iterator i = m_drawlist.begin(); i != m_drawlist.end(); ++i) { MapBlock *block = i->second; // If the mesh of the block happened to get deleted, ignore it if(block->mesh == NULL) continue; float d = 0.0; if(isBlockInSight(block->getPos(), camera_position, camera_direction, camera_fov, 100000*BS, &d) == false) { continue; } // Mesh animation { //JMutexAutoLock 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 */ { //JMutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); scene::SMesh *mesh = mapBlockMesh->getMesh(); assert(mesh); u32 c = mesh->getMeshBufferCount(); for(u32 i=0; i<c; i++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); const 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; drawbufs.add(buf); } } } } std::list<MeshBufList> &lists = drawbufs.lists; int timecheck_counter = 0; for(std::list<MeshBufList>::iterator i = lists.begin(); i != lists.end(); ++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; } } } MeshBufList &list = *i; driver->setMaterial(list.m); for(std::list<scene::IMeshBuffer*>::iterator j = list.bufs.begin(); j != list.bufs.end(); ++j) { scene::IMeshBuffer *buf = *j; driver->drawMeshBuffer(buf); vertex_count += buf->getVertexCount(); meshbuffer_count++; } #if 0 /* 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++; } #endif } } // 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); /*infostream<<"renderMap(): is_transparent_pass="******", rendered "<<vertex_count<<" vertices."<<std::endl;*/ }
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); }
/* Convert objects that are not in active blocks to static. If m_known_by_count != 0, active object is not deleted, but static data is still updated. If force_delete is set, active object is deleted nevertheless. It shall only be set so in the destructor of the environment. */ void ServerEnvironment::deactivateFarObjects(bool force_delete) { core::list<u16> objects_to_remove; for(core::map<u16, ServerActiveObject*>::Iterator i = m_active_objects.getIterator(); i.atEnd()==false; i++) { ServerActiveObject* obj = i.getNode()->getValue(); u16 id = i.getNode()->getKey(); v3f objectpos = obj->getBasePosition(); // This shouldn't happen but check it if(obj == NULL) { dstream<<"WARNING: NULL object found in ServerEnvironment" <<std::endl; assert(0); continue; } // The block in which the object resides in v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS)); // If block is active, don't remove if(m_active_blocks.contains(blockpos_o)) continue; /* Update the static data */ // Delete old static object MapBlock *oldblock = NULL; if(obj->m_static_exists) { MapBlock *block = m_map->getBlockNoCreateNoEx (obj->m_static_block); if(block) { block->m_static_objects.remove(id); oldblock = block; } } // Create new static object std::string staticdata = obj->getStaticData(); StaticObject s_obj(obj->getType(), objectpos, staticdata); // Add to the block where the object is located in v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); // Get or generate the block MapBlock *block = m_map->emergeBlock(blockpos); /*MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos); if(block == NULL) { // Block not found. Is the old block still ok? if(oldblock) block = oldblock; // Load from disk or generate else block = m_map->emergeBlock(blockpos); }*/ if(block) { block->m_static_objects.insert(0, s_obj); block->setChangedFlag(); obj->m_static_exists = true; obj->m_static_block = block->getPos(); } else{ dstream<<"WARNING: ServerEnv: Could not find or generate " <<"a block for storing static object"<<std::endl; obj->m_static_exists = false; continue; } /* Delete active object if not known by some client, else set pending deactivation */ // If known by some client, don't delete. if(obj->m_known_by_count > 0 && force_delete == false) { obj->m_pending_deactivation = true; continue; } /*dstream<<"INFO: Server: Stored static data. Deleting object." <<std::endl;*/ // Delete active object delete obj; // Id to be removed from m_active_objects objects_to_remove.push_back(id); } // Remove references from m_active_objects for(core::list<u16>::Iterator i = objects_to_remove.begin(); i != objects_to_remove.end(); i++) { m_active_objects.remove(*i); } }
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::renderMap(video::IVideoDriver* driver, s32 pass) { 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. */ 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 (std::map<v3s16, MapBlock*>::iterator i = m_drawlist.begin(); i != m_drawlist.end(); ++i) { 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 (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { std::vector<MeshBufList> &lists = drawbufs.lists[layer]; 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); /*infostream<<"renderMap(): is_transparent_pass="******", rendered "<<vertex_count<<" vertices."<<std::endl;*/ }
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; }