From 3f8261830e0503cd59d8713d5c9aab12fc1491db Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Wed, 4 Jan 2017 19:18:40 +0100 Subject: Improve getPointedThing() (#4346) * Improved getPointedThing() The new algorithm checks every node exactly once. Now the point and normal vector of the collision is also returned in the PointedThing (currently they are not used outside of the function). Now the CNodeDefManager keeps the union of all possible nodeboxes, so the raycast won't miss any nodes. Also if there are only small nodeboxes, getPointedThing() is exceptionally fast. Also adds unit test for VoxelLineIterator. * Cleanup, code move This commit moves getPointedThing() and Client::getSelectedActiveObject() to ClientEnvironment. The map nodes now can decide which neighbors they are connecting to (MapNode::getNeighbors()). --- src/voxelalgorithms.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'src/voxelalgorithms.cpp') diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index 93cc33acc..c20917164 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -747,5 +747,73 @@ void update_lighting_nodes(Map *map, INodeDefManager *ndef, } } +VoxelLineIterator::VoxelLineIterator( + const v3f &start_position, + const v3f &line_vector) : + m_start_position(start_position), + m_line_vector(line_vector), + m_next_intersection_multi(10000.0f, 10000.0f, 10000.0f), + m_intersection_multi_inc(10000.0f, 10000.0f, 10000.0f), + m_step_directions(1.0f, 1.0f, 1.0f) +{ + m_current_node_pos = floatToInt(m_start_position, 1); + + if (m_line_vector.X > 0) { + m_next_intersection_multi.X = (floorf(m_start_position.X - 0.5) + 1.5 + - m_start_position.X) / m_line_vector.X; + m_intersection_multi_inc.X = 1 / m_line_vector.X; + } else if (m_line_vector.X < 0) { + m_next_intersection_multi.X = (floorf(m_start_position.X - 0.5) + - m_start_position.X + 0.5) / m_line_vector.X; + m_intersection_multi_inc.X = -1 / m_line_vector.X; + m_step_directions.X = -1; + } + + if (m_line_vector.Y > 0) { + m_next_intersection_multi.Y = (floorf(m_start_position.Y - 0.5) + 1.5 + - m_start_position.Y) / m_line_vector.Y; + m_intersection_multi_inc.Y = 1 / m_line_vector.Y; + } else if (m_line_vector.Y < 0) { + m_next_intersection_multi.Y = (floorf(m_start_position.Y - 0.5) + - m_start_position.Y + 0.5) / m_line_vector.Y; + m_intersection_multi_inc.Y = -1 / m_line_vector.Y; + m_step_directions.Y = -1; + } + + if (m_line_vector.Z > 0) { + m_next_intersection_multi.Z = (floorf(m_start_position.Z - 0.5) + 1.5 + - m_start_position.Z) / m_line_vector.Z; + m_intersection_multi_inc.Z = 1 / m_line_vector.Z; + } else if (m_line_vector.Z < 0) { + m_next_intersection_multi.Z = (floorf(m_start_position.Z - 0.5) + - m_start_position.Z + 0.5) / m_line_vector.Z; + m_intersection_multi_inc.Z = -1 / m_line_vector.Z; + m_step_directions.Z = -1; + } + + m_has_next = (m_next_intersection_multi.X <= 1) + || (m_next_intersection_multi.Y <= 1) + || (m_next_intersection_multi.Z <= 1); +} + +void VoxelLineIterator::next() +{ + if ((m_next_intersection_multi.X < m_next_intersection_multi.Y) + && (m_next_intersection_multi.X < m_next_intersection_multi.Z)) { + m_next_intersection_multi.X += m_intersection_multi_inc.X; + m_current_node_pos.X += m_step_directions.X; + } else if ((m_next_intersection_multi.Y < m_next_intersection_multi.Z)) { + m_next_intersection_multi.Y += m_intersection_multi_inc.Y; + m_current_node_pos.Y += m_step_directions.Y; + } else { + m_next_intersection_multi.Z += m_intersection_multi_inc.Z; + m_current_node_pos.Z += m_step_directions.Z; + } + + m_has_next = (m_next_intersection_multi.X <= 1) + || (m_next_intersection_multi.Y <= 1) + || (m_next_intersection_multi.Z <= 1); +} + } // namespace voxalgo -- cgit v1.2.3 From f17c9c45dc30a388675d46418d278a4a029206e2 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Thu, 27 Oct 2016 23:25:44 +0200 Subject: Lighting: Update lighting at block loading This commit updates mapblocks' light if necessary when they are loaded. This removes ghost lighting. --- src/clientiface.cpp | 6 -- src/map.cpp | 131 +++++++++++++++++++---------------------- src/mapblock.cpp | 21 +++---- src/mapblock.h | 62 ++++++++++++-------- src/serialization.h | 5 +- src/voxelalgorithms.cpp | 152 +++++++++++++++++++++++++++++++++++++++++++++--- src/voxelalgorithms.h | 17 +++++- 7 files changed, 273 insertions(+), 121 deletions(-) (limited to 'src/voxelalgorithms.cpp') diff --git a/src/clientiface.cpp b/src/clientiface.cpp index 47730343c..0eb68c9c1 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -283,12 +283,6 @@ void RemoteClient::GetNextBlocks ( surely_not_found_on_disk = true; } - // Block is valid if lighting is up-to-date and data exists - if(block->isValid() == false) - { - block_is_invalid = true; - } - if(block->isGenerated() == false) block_is_invalid = true; diff --git a/src/map.cpp b/src/map.cpp index f2a4b7ffe..ffba77262 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -824,7 +824,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, // Update lighting std::vector > oldnodes; oldnodes.push_back(std::pair(p, oldnode)); - voxalgo::update_lighting_nodes(this, m_nodedef, oldnodes, modified_blocks); + voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); for(std::map::iterator i = modified_blocks.begin(); @@ -1523,7 +1523,7 @@ void Map::transformLiquids(std::map &modified_blocks) for (std::deque::iterator iter = must_reflow.begin(); iter != must_reflow.end(); ++iter) m_transforming_liquid.push_back(*iter); - voxalgo::update_lighting_nodes(this, m_nodedef, changed_nodes, modified_blocks); + voxalgo::update_lighting_nodes(this, changed_nodes, modified_blocks); /* ---------------------------------------------------------------------- @@ -1955,27 +1955,10 @@ void ServerMap::finishBlockMake(BlockMakeData *data, v3s16 bpmax = data->blockpos_max; v3s16 extra_borders(1, 1, 1); - v3s16 full_bpmin = bpmin - extra_borders; - v3s16 full_bpmax = bpmax + extra_borders; bool enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; EMERGE_DBG_OUT("finishBlockMake(): " PP(bpmin) " - " PP(bpmax)); - /* - Set lighting to non-expired state in all of them. - This is cheating, but it is not fast enough if all of them - would actually be updated. - */ - for (s16 x = full_bpmin.X; x <= full_bpmax.X; x++) - for (s16 z = full_bpmin.Z; z <= full_bpmax.Z; z++) - for (s16 y = full_bpmin.Y; y <= full_bpmax.Y; y++) { - MapBlock *block = emergeBlock(v3s16(x, y, z), false); - if (!block) - continue; - - block->setLightingExpired(false); - } - /* Blit generated stuff to map NOTE: blitBackAll adds nearly everything to changed_blocks @@ -2991,7 +2974,6 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool // We just loaded it from, so it's up-to-date. block->resetModified(); - } catch(SerializationError &e) { @@ -3015,71 +2997,80 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos) { DSTACK(FUNCTION_NAME); + bool created_new = (getBlockNoCreateNoEx(blockpos) == NULL); + v2s16 p2d(blockpos.X, blockpos.Z); std::string ret; dbase->loadBlock(blockpos, &ret); if (ret != "") { loadBlock(&ret, blockpos, createSector(p2d), false); - return getBlockNoCreateNoEx(blockpos); - } - // Not found in database, try the files - - // The directory layout we're going to load from. - // 1 - original sectors/xxxxzzzz/ - // 2 - new sectors2/xxx/zzz/ - // If we load from anything but the latest structure, we will - // immediately save to the new one, and remove the old. - int loadlayout = 1; - std::string sectordir1 = getSectorDir(p2d, 1); - std::string sectordir; - if(fs::PathExists(sectordir1)) - { - sectordir = sectordir1; - } - else - { - loadlayout = 2; - sectordir = getSectorDir(p2d, 2); - } + } else { + // Not found in database, try the files + + // The directory layout we're going to load from. + // 1 - original sectors/xxxxzzzz/ + // 2 - new sectors2/xxx/zzz/ + // If we load from anything but the latest structure, we will + // immediately save to the new one, and remove the old. + int loadlayout = 1; + std::string sectordir1 = getSectorDir(p2d, 1); + std::string sectordir; + if (fs::PathExists(sectordir1)) { + sectordir = sectordir1; + } else { + loadlayout = 2; + sectordir = getSectorDir(p2d, 2); + } - /* + /* Make sure sector is loaded - */ + */ - MapSector *sector = getSectorNoGenerateNoEx(p2d); - if(sector == NULL) - { - try{ - sector = loadSectorMeta(sectordir, loadlayout != 2); - } - catch(InvalidFilenameException &e) - { - return NULL; - } - catch(FileNotGoodException &e) - { - return NULL; - } - catch(std::exception &e) - { - return NULL; + MapSector *sector = getSectorNoGenerateNoEx(p2d); + if (sector == NULL) { + try { + sector = loadSectorMeta(sectordir, loadlayout != 2); + } catch(InvalidFilenameException &e) { + return NULL; + } catch(FileNotGoodException &e) { + return NULL; + } catch(std::exception &e) { + return NULL; + } } - } - /* + + /* Make sure file exists - */ + */ - std::string blockfilename = getBlockFilename(blockpos); - if(fs::PathExists(sectordir + DIR_DELIM + blockfilename) == false) - return NULL; + std::string blockfilename = getBlockFilename(blockpos); + if (fs::PathExists(sectordir + DIR_DELIM + blockfilename) == false) + return NULL; - /* + /* Load block and save it to the database - */ - loadBlock(sectordir, blockfilename, sector, true); - return getBlockNoCreateNoEx(blockpos); + */ + loadBlock(sectordir, blockfilename, sector, true); + } + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if (created_new && (block != NULL)) { + std::map modified_blocks; + // Fix lighting if necessary + voxalgo::update_block_border_lighting(this, block, modified_blocks); + if (!modified_blocks.empty()) { + //Modified lighting, send event + MapEditEvent event; + event.type = MEET_OTHER; + std::map::iterator it; + for (it = modified_blocks.begin(); + it != modified_blocks.end(); ++it) + event.modified_blocks.insert(it->first); + dispatchEvent(&event); + } + } + return block; } bool ServerMap::deleteBlock(v3s16 blockpos) diff --git a/src/mapblock.cpp b/src/mapblock.cpp index f8c3197bc..840cb9b39 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -73,7 +73,7 @@ MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy): m_modified(MOD_STATE_WRITE_NEEDED), m_modified_reason(MOD_REASON_INITIAL), is_underground(false), - m_lighting_expired(true), + m_lighting_complete(0xFFFF), m_day_night_differs(false), m_day_night_differs_expired(true), m_generated(false), @@ -571,11 +571,12 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) flags |= 0x01; if(getDayNightDiff()) flags |= 0x02; - if(m_lighting_expired) - flags |= 0x04; if(m_generated == false) flags |= 0x08; writeU8(os, flags); + if (version >= 27) { + writeU16(os, m_lighting_complete); + } /* Bulk node data @@ -672,7 +673,11 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) u8 flags = readU8(is); is_underground = (flags & 0x01) ? true : false; m_day_night_differs = (flags & 0x02) ? true : false; - m_lighting_expired = (flags & 0x04) ? true : false; + if (version < 27) { + m_lighting_complete = 0xFFFF; + } else { + m_lighting_complete = readU16(is); + } m_generated = (flags & 0x08) ? false : true; /* @@ -783,7 +788,7 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) // Initialize default flags is_underground = false; m_day_night_differs = false; - m_lighting_expired = false; + m_lighting_complete = 0xFFFF; m_generated = true; // Make a temporary buffer @@ -849,7 +854,6 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) is.read((char*)&flags, 1); is_underground = (flags & 0x01) ? true : false; m_day_night_differs = (flags & 0x02) ? true : false; - m_lighting_expired = (flags & 0x04) ? true : false; if(version >= 18) m_generated = (flags & 0x08) ? false : true; @@ -1027,10 +1031,7 @@ std::string analyze_block(MapBlock *block) else desc<<"is_ug [ ], "; - if(block->getLightingExpired()) - desc<<"lighting_exp [X], "; - else - desc<<"lighting_exp [ ], "; + desc<<"lighting_complete: "<getLightingComplete()<<", "; if(block->isDummy()) { diff --git a/src/mapblock.h b/src/mapblock.h index f80800109..5a0ec937a 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -105,7 +105,7 @@ public: #define MOD_REASON_INITIAL (1 << 0) #define MOD_REASON_REALLOCATE (1 << 1) #define MOD_REASON_SET_IS_UNDERGROUND (1 << 2) -#define MOD_REASON_SET_LIGHTING_EXPIRED (1 << 3) +#define MOD_REASON_SET_LIGHTING_COMPLETE (1 << 3) #define MOD_REASON_SET_GENERATED (1 << 4) #define MOD_REASON_SET_NODE (1 << 5) #define MOD_REASON_SET_NODE_NO_CHECK (1 << 6) @@ -213,17 +213,42 @@ public: raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_IS_UNDERGROUND); } - inline void setLightingExpired(bool expired) + inline void setLightingComplete(u16 newflags) { - if (expired != m_lighting_expired){ - m_lighting_expired = expired; - raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_LIGHTING_EXPIRED); + if (newflags != m_lighting_complete) { + m_lighting_complete = newflags; + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_LIGHTING_COMPLETE); } } - inline bool getLightingExpired() + inline u16 getLightingComplete() { - return m_lighting_expired; + return m_lighting_complete; + } + + inline void setLightingComplete(LightBank bank, u8 direction, + bool is_complete) + { + assert(direction >= 0 && direction <= 5); + if (bank == LIGHTBANK_NIGHT) { + direction += 6; + } + u16 newflags = m_lighting_complete; + if (is_complete) { + newflags |= 1 << direction; + } else { + newflags &= ~(1 << direction); + } + setLightingComplete(newflags); + } + + inline bool isLightingComplete(LightBank bank, u8 direction) + { + assert(direction >= 0 && direction <= 5); + if (bank == LIGHTBANK_NIGHT) { + direction += 6; + } + return (m_lighting_complete & (1 << direction)) != 0; } inline bool isGenerated() @@ -239,15 +264,6 @@ public: } } - inline bool isValid() - { - if (m_lighting_expired) - return false; - if (data == NULL) - return false; - return true; - } - //// //// Position stuff //// @@ -613,14 +629,14 @@ private: */ bool is_underground; - /* - Set to true if changes has been made that make the old lighting - values wrong but the lighting hasn't been actually updated. - - If this is false, lighting is exactly right. - If this is true, lighting might be wrong or right. + /*! + * Each bit indicates if light spreading was finished + * in a direction. (Because the neighbor could also be unloaded.) + * Bits: day X+, day Y+, day Z+, day Z-, day Y-, day X-, + * night X+, night Y+, night Z+, night Z-, night Y-, night X-, + * nothing, nothing, nothing, nothing. */ - bool m_lighting_expired; + u16 m_lighting_complete; // Whether day and night lighting differs bool m_day_night_differs; diff --git a/src/serialization.h b/src/serialization.h index 01d37d363..52c63098e 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -62,13 +62,14 @@ with this program; if not, write to the Free Software Foundation, Inc., 24: 16-bit node ids and node timers (never released as stable) 25: Improved node timer format 26: Never written; read the same as 25 + 27: Added light spreading flags to blocks */ // This represents an uninitialized or invalid format #define SER_FMT_VER_INVALID 255 // Highest supported serialization version -#define SER_FMT_VER_HIGHEST_READ 26 +#define SER_FMT_VER_HIGHEST_READ 27 // Saved on disk version -#define SER_FMT_VER_HIGHEST_WRITE 25 +#define SER_FMT_VER_HIGHEST_WRITE 27 // Lowest supported serialization version #define SER_FMT_VER_LOWEST_READ 0 // Lowest serialization version for writing diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index c20917164..3c32bc125 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -423,6 +423,7 @@ void unspread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, if (step_rel_block_pos(i, neighbor_rel_pos, neighbor_block_pos)) { neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos); if (neighbor_block == NULL) { + current.block->setLightingComplete(bank, i, false); continue; } } else { @@ -486,7 +487,8 @@ void unspread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, * \param modified_blocks output, all modified map blocks are added to this */ void spread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, - LightQueue &light_sources, std::map &modified_blocks) + LightQueue &light_sources, + std::map &modified_blocks) { // The light the current node can provide to its neighbors. u8 spreading_light; @@ -511,6 +513,7 @@ void spread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, if (step_rel_block_pos(i, neighbor_rel_pos, neighbor_block_pos)) { neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos); if (neighbor_block == NULL) { + current.block->setLightingComplete(bank, i, false); continue; } } else { @@ -584,10 +587,11 @@ bool is_sunlight_above(Map *map, v3s16 pos, INodeDefManager *ndef) static const LightBank banks[] = { LIGHTBANK_DAY, LIGHTBANK_NIGHT }; -void update_lighting_nodes(Map *map, INodeDefManager *ndef, +void update_lighting_nodes(Map *map, std::vector > &oldnodes, std::map &modified_blocks) { + INodeDefManager *ndef = map->getNodeDefManager(); // For node getter functions bool is_valid_position; @@ -596,6 +600,22 @@ void update_lighting_nodes(Map *map, INodeDefManager *ndef, LightBank bank = banks[i]; UnlightQueue disappearing_lights(256); ReLightQueue light_sources(256); + // Nodes that are brighter than the brightest modified node was + // won't change, since they didn't get their light from a + // modified node. + u8 min_safe_light = 0; + for (std::vector >::iterator it = + oldnodes.begin(); it < oldnodes.end(); ++it) { + u8 old_light = it->second.getLight(bank, ndef); + if (old_light > min_safe_light) { + min_safe_light = old_light; + } + } + // If only one node changed, even nodes with the same brightness + // didn't get their light from the changed node. + if (oldnodes.size() > 1) { + min_safe_light++; + } // For each changed node process sunlight and initialize for (std::vector >::iterator it = oldnodes.begin(); it < oldnodes.end(); ++it) { @@ -634,11 +654,9 @@ void update_lighting_nodes(Map *map, INodeDefManager *ndef, MapNode n2 = map->getNodeNoEx(p2, &is_valid); if (is_valid) { u8 spread = n2.getLight(bank, ndef); - // If the neighbor is at least as bright as - // this node then its light is not from - // this node. - // Its light can spread to this node. - if (spread > new_light && spread >= old_light) { + // If it is sure that the neighbor won't be + // unlighted, its light can spread to this node. + if (spread > new_light && spread >= min_safe_light) { new_light = spread - 1; } } @@ -747,6 +765,126 @@ void update_lighting_nodes(Map *map, INodeDefManager *ndef, } } +/*! + * Borders of a map block in relative node coordinates. + * Compatible with type 'direction'. + */ +const VoxelArea block_borders[] = { + VoxelArea(v3s16(15, 0, 0), v3s16(15, 15, 15)), //X+ + VoxelArea(v3s16(0, 15, 0), v3s16(15, 15, 15)), //Y+ + VoxelArea(v3s16(0, 0, 15), v3s16(15, 15, 15)), //Z+ + VoxelArea(v3s16(0, 0, 0), v3s16(15, 15, 0)), //Z- + VoxelArea(v3s16(0, 0, 0), v3s16(15, 0, 15)), //Y- + VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X- +}; + +/*! + * Returns true if: + * -the node has unloaded neighbors + * -the node doesn't have light + * -the node's light is the same as the maximum of + * its light source and its brightest neighbor minus one. + * . + */ +bool is_light_locally_correct(Map *map, INodeDefManager *ndef, LightBank bank, + v3s16 pos) +{ + bool is_valid_position; + MapNode n = map->getNodeNoEx(pos, &is_valid_position); + const ContentFeatures &f = ndef->get(n); + if (f.param_type != CPT_LIGHT) { + return true; + } + u8 light = n.getLightNoChecks(bank, &f); + assert(f.light_source <= LIGHT_MAX); + u8 brightest_neighbor = f.light_source + 1; + for (direction d = 0; d < 6; ++d) { + MapNode n2 = map->getNodeNoEx(pos + neighbor_dirs[d], + &is_valid_position); + u8 light2 = n2.getLight(bank, ndef); + if (brightest_neighbor < light2) { + brightest_neighbor = light2; + } + } + assert(light <= LIGHT_SUN); + return brightest_neighbor == light + 1; +} + +void update_block_border_lighting(Map *map, MapBlock *block, + std::map &modified_blocks) +{ + INodeDefManager *ndef = map->getNodeDefManager(); + bool is_valid_position; + for (s32 i = 0; i < 2; i++) { + LightBank bank = banks[i]; + UnlightQueue disappearing_lights(256); + ReLightQueue light_sources(256); + // Get incorrect lights + for (direction d = 0; d < 6; d++) { + // For each direction + // Get neighbor block + v3s16 otherpos = block->getPos() + neighbor_dirs[d]; + MapBlock *other = map->getBlockNoCreateNoEx(otherpos); + if (other == NULL) { + continue; + } + // Only update if lighting was not completed. + if (block->isLightingComplete(bank, d) && + other->isLightingComplete(bank, 5 - d)) + continue; + // Reset flags + block->setLightingComplete(bank, d, true); + other->setLightingComplete(bank, 5 - d, true); + // The two blocks and their connecting surfaces + MapBlock *blocks[] = {block, other}; + VoxelArea areas[] = {block_borders[d], block_borders[5 - d]}; + // For both blocks + for (u8 blocknum = 0; blocknum < 2; blocknum++) { + MapBlock *b = blocks[blocknum]; + VoxelArea a = areas[blocknum]; + // For all nodes + for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++) + for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) + for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + MapNode n = b->getNodeNoCheck(x, y, z, + &is_valid_position); + u8 light = n.getLight(bank, ndef); + // Sunlight is fixed + if (light < LIGHT_SUN) { + // Unlight if not correct + if (!is_light_locally_correct(map, ndef, bank, + v3s16(x, y, z) + b->getPosRelative())) { + // Initialize for unlighting + n.setLight(bank, 0, ndef); + b->setNodeNoCheck(x, y, z, n); + modified_blocks[b->getPos()]=b; + disappearing_lights.push(light, + relative_v3(x, y, z), b->getPos(), b, + 6); + } + } + } + } + } + // Remove lights + unspread_light(map, ndef, bank, disappearing_lights, light_sources, + modified_blocks); + // Initialize light values for light spreading. + for (u8 i = 0; i <= LIGHT_SUN; i++) { + const std::vector &lights = light_sources.lights[i]; + for (std::vector::const_iterator it = lights.begin(); + it < lights.end(); it++) { + MapNode n = it->block->getNodeNoCheck(it->rel_position, + &is_valid_position); + n.setLight(bank, i, ndef); + it->block->setNodeNoCheck(it->rel_position, n); + } + } + // Spread lights. + spread_light(map, ndef, bank, light_sources, modified_blocks); + } +} + VoxelLineIterator::VoxelLineIterator( const v3f &start_position, const v3f &line_vector) : diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index 5eff8f7ac..bf1638fa3 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -22,8 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "voxel.h" #include "mapnode.h" -#include -#include +#include "util/container.h" +#include "util/cpp11_container.h" class Map; class MapBlock; @@ -69,10 +69,21 @@ SunlightPropagateResult propagateSunlight(VoxelManipulator &v, VoxelArea a, */ void update_lighting_nodes( Map *map, - INodeDefManager *ndef, std::vector > &oldnodes, std::map &modified_blocks); +/*! + * Updates borders of the given mapblock. + * Only updates if the block was marked with incomplete + * lighting and the neighbor is also loaded. + * + * \param block the block to update + * \param modified_blocks output, contains all map blocks that + * the function modified + */ +void update_block_border_lighting(Map *map, MapBlock *block, + std::map &modified_blocks); + /*! * This class iterates trough voxels that intersect with * a line. The collision detection does not see nodeboxes, -- cgit v1.2.3 From ab371cc93491baf0973ecc94b96c3a1fdb4abfd5 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Sat, 10 Dec 2016 19:02:44 +0100 Subject: Light calculation: New bulk node lighting code This commit introduces a new bulk node lighting algorithm to minimize lighting bugs during l-system tree generation, schematic placement and non-mapgen-object lua voxelmanip light calculation. If the block above the changed area is not loaded, it gets loaded to avoid lighting bugs. Light is updated as soon as write_to_map is called on a voxel manipulator, therefore update_map does nothing. --- doc/lua_api.txt | 9 +- src/map.cpp | 565 ---------------------------------------- src/map.h | 16 -- src/mg_schematic.cpp | 10 +- src/mg_schematic.h | 3 +- src/script/lua_api/l_mapgen.cpp | 4 +- src/script/lua_api/l_vmanip.cpp | 46 ++-- src/treegen.cpp | 7 +- src/voxelalgorithms.cpp | 371 +++++++++++++++++++++++++- src/voxelalgorithms.h | 13 + 10 files changed, 408 insertions(+), 636 deletions(-) (limited to 'src/voxelalgorithms.cpp') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 23aac90d9..484a5848c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -3282,9 +3282,6 @@ format as produced by get_data() et al. and is *not required* to be a table retr Once the internal VoxelManip state has been modified to your liking, the changes can be committed back to the map by calling `VoxelManip:write_to_map()`. -Finally, a call to `VoxelManip:update_map()` is required to re-calculate lighting and set the blocks -as being modified so that connected clients are sent the updated parts of map. - ##### Flat array format Let @@ -3349,8 +3346,6 @@ but with a few differences: will also update the Mapgen VoxelManip object's internal state active on the current thread. * After modifying the Mapgen VoxelManip object's internal buffer, it may be necessary to update lighting information using either: `VoxelManip:calc_lighting()` or `VoxelManip:set_lighting()`. -* `VoxelManip:update_map()` does not need to be called after `write_to_map()`. The map update is performed - automatically after all on_generated callbacks have been run for that generated block. ##### Other API functions operating on a VoxelManip If any VoxelManip contents were set to a liquid node, `VoxelManip:update_liquids()` must be called @@ -3393,9 +3388,7 @@ will place the schematic inside of the VoxelManip. * returns raw node data in the form of an array of node content IDs * if the param `buffer` is present, this table will be used to store the result instead * `set_data(data)`: Sets the data contents of the `VoxelManip` object -* `update_map()`: Update map after writing chunk back to map. - * To be used only by `VoxelManip` objects created by the mod itself; - not a `VoxelManip` that was retrieved from `minetest.get_mapgen_object` +* `update_map()`: Does nothing, kept for compatibility. * `set_lighting(light, [p1, p2])`: Set the lighting within the `VoxelManip` to a uniform value * `light` is a table, `{day=<0...15>, night=<0...15>}` * To be used only by a `VoxelManip` object from `minetest.get_mapgen_object` diff --git a/src/map.cpp b/src/map.cpp index a415bda96..a1502befa 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -235,571 +235,6 @@ void Map::setNode(v3s16 p, MapNode & n) block->setNodeNoCheck(relpos, n); } -/* - Goes recursively through the neighbours of the node. - - Alters only transparent nodes. - - If the lighting of the neighbour is lower than the lighting of - the node was (before changing it to 0 at the step before), the - lighting of the neighbour is set to 0 and then the same stuff - repeats for the neighbour. - - The ending nodes of the routine are stored in light_sources. - This is useful when a light is removed. In such case, this - routine can be called for the light node and then again for - light_sources to re-light the area without the removed light. - - values of from_nodes are lighting values. -*/ -void Map::unspreadLight(enum LightBank bank, - std::map & from_nodes, - std::set & light_sources, - std::map & modified_blocks) -{ - v3s16 dirs[6] = { - v3s16(0,0,1), // back - v3s16(0,1,0), // top - v3s16(1,0,0), // right - v3s16(0,0,-1), // front - v3s16(0,-1,0), // bottom - v3s16(-1,0,0), // left - }; - - if(from_nodes.empty()) - return; - - u32 blockchangecount = 0; - - std::map unlighted_nodes; - - /* - Initialize block cache - */ - v3s16 blockpos_last; - MapBlock *block = NULL; - // Cache this a bit, too - bool block_checked_in_modified = false; - - for(std::map::iterator j = from_nodes.begin(); - j != from_nodes.end(); ++j) - { - v3s16 pos = j->first; - v3s16 blockpos = getNodeBlockPos(pos); - - // Only fetch a new block if the block position has changed - try{ - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) - { - continue; - } - - if(block->isDummy()) - continue; - - // Calculate relative position in block - //v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE; - - // Get node straight from the block - //MapNode n = block->getNode(relpos); - - u8 oldlight = j->second; - - // Loop through 6 neighbors - for(u16 i=0; i<6; i++) - { - // Get the position of the neighbor node - v3s16 n2pos = pos + dirs[i]; - - // Get the block where the node is located - v3s16 blockpos, relpos; - getNodeBlockPosWithOffset(n2pos, blockpos, relpos); - - // Only fetch a new block if the block position has changed - try { - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) { - continue; - } - - // Get node straight from the block - bool is_valid_position; - MapNode n2 = block->getNode(relpos, &is_valid_position); - if (!is_valid_position) - continue; - - bool changed = false; - - //TODO: Optimize output by optimizing light_sources? - - /* - If the neighbor is dimmer than what was specified - as oldlight (the light of the previous node) - */ - if(n2.getLight(bank, m_nodedef) < oldlight) - { - /* - And the neighbor is transparent and it has some light - */ - if(m_nodedef->get(n2).light_propagates - && n2.getLight(bank, m_nodedef) != 0) - { - /* - Set light to 0 and add to queue - */ - - u8 current_light = n2.getLight(bank, m_nodedef); - n2.setLight(bank, 0, m_nodedef); - block->setNode(relpos, n2); - - unlighted_nodes[n2pos] = current_light; - changed = true; - - /* - Remove from light_sources if it is there - NOTE: This doesn't happen nearly at all - */ - /*if(light_sources.find(n2pos)) - { - infostream<<"Removed from light_sources"< & from_nodes, - std::map & modified_blocks) -{ - const v3s16 dirs[6] = { - v3s16(0,0,1), // back - v3s16(0,1,0), // top - v3s16(1,0,0), // right - v3s16(0,0,-1), // front - v3s16(0,-1,0), // bottom - v3s16(-1,0,0), // left - }; - - if(from_nodes.empty()) - return; - - u32 blockchangecount = 0; - - std::set lighted_nodes; - - /* - Initialize block cache - */ - v3s16 blockpos_last; - MapBlock *block = NULL; - // Cache this a bit, too - bool block_checked_in_modified = false; - - for(std::set::iterator j = from_nodes.begin(); - j != from_nodes.end(); ++j) - { - v3s16 pos = *j; - v3s16 blockpos, relpos; - - getNodeBlockPosWithOffset(pos, blockpos, relpos); - - // Only fetch a new block if the block position has changed - try { - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) { - continue; - } - - if(block->isDummy()) - continue; - - // Get node straight from the block - bool is_valid_position; - MapNode n = block->getNode(relpos, &is_valid_position); - - u8 oldlight = is_valid_position ? n.getLight(bank, m_nodedef) : 0; - u8 newlight = diminish_light(oldlight); - - // Loop through 6 neighbors - for(u16 i=0; i<6; i++){ - // Get the position of the neighbor node - v3s16 n2pos = pos + dirs[i]; - - // Get the block where the node is located - v3s16 blockpos, relpos; - getNodeBlockPosWithOffset(n2pos, blockpos, relpos); - - // Only fetch a new block if the block position has changed - try { - if(block == NULL || blockpos != blockpos_last){ - block = getBlockNoCreate(blockpos); - blockpos_last = blockpos; - - block_checked_in_modified = false; - blockchangecount++; - } - } - catch(InvalidPositionException &e) { - continue; - } - - // Get node straight from the block - MapNode n2 = block->getNode(relpos, &is_valid_position); - if (!is_valid_position) - continue; - - bool changed = false; - /* - If the neighbor is brighter than the current node, - add to list (it will light up this node on its turn) - */ - if(n2.getLight(bank, m_nodedef) > undiminish_light(oldlight)) - { - lighted_nodes.insert(n2pos); - changed = true; - } - /* - If the neighbor is dimmer than how much light this node - would spread on it, add to list - */ - if(n2.getLight(bank, m_nodedef) < newlight) - { - if(m_nodedef->get(n2).light_propagates) - { - n2.setLight(bank, newlight, m_nodedef); - block->setNode(relpos, n2); - lighted_nodes.insert(n2pos); - changed = true; - } - } - - // Add to modified_blocks - if(changed == true && block_checked_in_modified == false) - { - // If the block is not found in modified_blocks, add. - if(modified_blocks.find(blockpos) == modified_blocks.end()) - { - modified_blocks[blockpos] = block; - } - block_checked_in_modified = true; - } - } - } - - /*infostream<<"spreadLight(): Changed block " - < & a_blocks, - std::map & modified_blocks) -{ - /*m_dout<<"Map::updateLighting(): " - < blocks_to_update; - - std::set light_sources; - - std::map unlight_from; - - int num_bottom_invalid = 0; - - { - //TimeTaker t("first stuff"); - - for(std::map::iterator i = a_blocks.begin(); - i != a_blocks.end(); ++i) - { - MapBlock *block = i->second; - - for(;;) - { - // Don't bother with dummy blocks. - if(block->isDummy()) - break; - - v3s16 pos = block->getPos(); - v3s16 posnodes = block->getPosRelative(); - modified_blocks[pos] = block; - //blocks_to_update[pos] = block; - - /* - Clear all light from block - */ - for(s16 z=0; zgetNode(p, &is_valid_position); - if (!is_valid_position) { - /* This would happen when dealing with a - dummy block. - */ - infostream<<"updateLighting(): InvalidPositionException" - <setNode(p, n); - - // If node sources light, add to list - u8 source = m_nodedef->get(n).light_source; - if(source != 0) - light_sources.insert(p + posnodes); - - // Collect borders for unlighting - if((x==0 || x == MAP_BLOCKSIZE-1 - || y==0 || y == MAP_BLOCKSIZE-1 - || z==0 || z == MAP_BLOCKSIZE-1) - && oldlight != 0) - { - v3s16 p_map = p + posnodes; - unlight_from[p_map] = oldlight; - } - - - } - - if(bank == LIGHTBANK_DAY) - { - bool bottom_valid = block->propagateSunlight(light_sources); - - if(!bottom_valid) - num_bottom_invalid++; - - // If bottom is valid, we're done. - if(bottom_valid) - break; - } - else if(bank == LIGHTBANK_NIGHT) - { - // For night lighting, sunlight is not propagated - break; - } - else - { - assert("Invalid lighting bank" == NULL); - } - - /*infostream<<"Bottom for sunlight-propagated block (" - <get("")) - { - core::map::Iterator i; - i = blocks_to_update.getIterator(); - for(; i.atEnd() == false; i++) - { - MapBlock *block = i.getNode()->getValue(); - v3s16 p = block->getPos(); - block->setLightingExpired(false); - } - return; - } -#endif - -#if 1 - { - //TimeTaker timer("unspreadLight"); - unspreadLight(bank, unlight_from, light_sources, modified_blocks); - } - - /*if(debug) - { - u32 diff = modified_blocks.size() - count_was; - count_was = modified_blocks.size(); - infostream<<"unspreadLight modified "<::Iterator i; - i = blocks_to_update.getIterator(); - for(; i.atEnd() == false; i++) - { - MapBlock *block = i.getNode()->getValue(); - v3s16 p = block->getPos(); - - // Add all surrounding blocks - vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1)); - - /* - Add all surrounding blocks that have up-to-date lighting - NOTE: This doesn't quite do the job (not everything - appropriate is lighted) - */ - /*for(s16 z=-1; z<=1; z++) - for(s16 y=-1; y<=1; y++) - for(s16 x=-1; x<=1; x++) - { - v3s16 p2 = p + v3s16(x,y,z); - MapBlock *block = getBlockNoCreateNoEx(p2); - if(block == NULL) - continue; - if(block->isDummy()) - continue; - if(block->getLightingExpired()) - continue; - vmanip.initialEmerge(p2, p2); - }*/ - - // Lighting of block will be updated completely - block->setLightingExpired(false); - } - } - - { - //TimeTaker timer("unSpreadLight"); - vmanip.unspreadLight(bank, unlight_from, light_sources, nodemgr); - } - { - //TimeTaker timer("spreadLight"); - vmanip.spreadLight(bank, light_sources, nodemgr); - } - { - //TimeTaker timer("blitBack"); - vmanip.blitBack(modified_blocks); - } - /*infostream<<"emerge_time="< & a_blocks, - std::map & modified_blocks) -{ - updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks); - updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks); - - /* - Update information about whether day and night light differ - */ - for(std::map::iterator - i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) - { - MapBlock *block = i->second; - block->expireDayNightDiff(); - } -} - void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::map &modified_blocks, bool remove_metadata) diff --git a/src/map.h b/src/map.h index 19c94ee80..c4181a49f 100644 --- a/src/map.h +++ b/src/map.h @@ -208,22 +208,6 @@ public: // position is valid, otherwise false MapNode getNodeNoEx(v3s16 p, bool *is_valid_position = NULL); - void unspreadLight(enum LightBank bank, - std::map & from_nodes, - std::set & light_sources, - std::map & modified_blocks); - - void spreadLight(enum LightBank bank, - std::set & from_nodes, - std::map & modified_blocks); - - void updateLighting(enum LightBank bank, - std::map & a_blocks, - std::map & modified_blocks); - - void updateLighting(std::map & a_blocks, - std::map & modified_blocks); - /* These handle lighting but not faces. */ diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp index 3d08d86fa..92e138df4 100644 --- a/src/mg_schematic.cpp +++ b/src/mg_schematic.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "serialization.h" #include "filesys.h" +#include "voxelalgorithms.h" /////////////////////////////////////////////////////////////////////////////// @@ -202,7 +203,7 @@ bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1))); } -void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags, +void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place) { std::map lighting_modified_blocks; @@ -238,15 +239,10 @@ void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags, blitToVManip(&vm, p, rot, force_place); - vm.blitBackAll(&modified_blocks); + voxalgo::blit_back_with_light(map, &vm, &modified_blocks); //// Carry out post-map-modification actions - //// Update lighting - // TODO: Optimize this by using Mapgen::calcLighting() instead - lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end()); - map->updateLighting(lighting_modified_blocks, modified_blocks); - //// Create & dispatch map modification events to observers MapEditEvent event; event.type = MEET_OTHER; diff --git a/src/mg_schematic.h b/src/mg_schematic.h index 1d46e6ac4..2f60c843b 100644 --- a/src/mg_schematic.h +++ b/src/mg_schematic.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" class Map; +class ServerMap; class Mapgen; class MMVManip; class PseudoRandom; @@ -108,7 +109,7 @@ public: void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place); bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); - void placeOnMap(Map *map, v3s16 p, u32 flags, Rotation rot, bool force_place); + void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place); void applyProbabilities(v3s16 p0, std::vector > *plist, diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index bc1c32f03..0bc9e25f7 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -1357,7 +1357,9 @@ int ModApiMapgen::l_place_schematic(lua_State *L) { MAP_LOCK_REQUIRED; - Map *map = &(getEnv(L)->getMap()); + GET_ENV_PTR; + + ServerMap *map = &(env->getServerMap()); SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; //// Read position diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index bdf720f0a..5f129d2af 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "server.h" #include "mapgen.h" +#include "voxelalgorithms.h" // garbage collector int LuaVoxelManip::gc_object(lua_State *L) @@ -109,10 +110,24 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); - MMVManip *vm = o->vm; + GET_ENV_PTR; + ServerMap *map = &(env->getServerMap()); + if (o->is_mapgen_vm) { + o->vm->blitBackAll(&(o->modified_blocks)); + } else { + voxalgo::blit_back_with_light(map, o->vm, + &(o->modified_blocks)); + } - vm->blitBackAll(&o->modified_blocks); + MapEditEvent event; + event.type = MEET_OTHER; + for (std::map::iterator it = o->modified_blocks.begin(); + it != o->modified_blocks.end(); ++it) + event.modified_blocks.insert(it->first); + map->dispatchEvent(&event); + + o->modified_blocks.clear(); return 0; } @@ -322,33 +337,6 @@ int LuaVoxelManip::l_set_param2_data(lua_State *L) int LuaVoxelManip::l_update_map(lua_State *L) { - GET_ENV_PTR; - - LuaVoxelManip *o = checkobject(L, 1); - if (o->is_mapgen_vm) - return 0; - - Map *map = &(env->getMap()); - - // TODO: Optimize this by using Mapgen::calcLighting() instead - std::map lighting_mblocks; - std::map *mblocks = &o->modified_blocks; - - lighting_mblocks.insert(mblocks->begin(), mblocks->end()); - - map->updateLighting(lighting_mblocks, *mblocks); - - MapEditEvent event; - event.type = MEET_OTHER; - for (std::map::iterator - it = mblocks->begin(); - it != mblocks->end(); ++it) - event.modified_blocks.insert(it->first); - - map->dispatchEvent(&event); - - mblocks->clear(); - return 0; } diff --git a/src/treegen.cpp b/src/treegen.cpp index 4df574f34..505954e8e 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverenvironment.h" #include "nodedef.h" #include "treegen.h" +#include "voxelalgorithms.h" namespace treegen { @@ -125,12 +126,8 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, if (e != SUCCESS) return e; - vmanip.blitBackAll(&modified_blocks); + voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks); - // update lighting - std::map lighting_modified_blocks; - lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end()); - map->updateLighting(lighting_modified_blocks, modified_blocks); // Send a MEET_OTHER event MapEditEvent event; event.type = MEET_OTHER; diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index 3c32bc125..411369bd4 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -542,6 +542,21 @@ void spread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, } } +struct SunlightPropagationUnit{ + v2s16 relative_pos; + bool is_sunlit; + + SunlightPropagationUnit(v2s16 relpos, bool sunlit): + relative_pos(relpos), + is_sunlit(sunlit) + {} +}; + +struct SunlightPropagationData{ + std::vector data; + v3s16 target_block; +}; + /*! * Returns true if the node gets sunlight from the * node above it. @@ -753,7 +768,7 @@ void update_lighting_nodes(Map *map, for (u8 i = 0; i <= LIGHT_SUN; i++) { const std::vector &lights = light_sources.lights[i]; for (std::vector::const_iterator it = lights.begin(); - it < lights.end(); it++) { + it < lights.end(); ++it) { MapNode n = it->block->getNodeNoCheck(it->rel_position, &is_valid_position); n.setLight(bank, i, ndef); @@ -817,8 +832,10 @@ void update_block_border_lighting(Map *map, MapBlock *block, bool is_valid_position; for (s32 i = 0; i < 2; i++) { LightBank bank = banks[i]; - UnlightQueue disappearing_lights(256); - ReLightQueue light_sources(256); + // Since invalid light is not common, do not allocate + // memory if not needed. + UnlightQueue disappearing_lights(0); + ReLightQueue light_sources(0); // Get incorrect lights for (direction d = 0; d < 6; d++) { // For each direction @@ -873,7 +890,7 @@ void update_block_border_lighting(Map *map, MapBlock *block, for (u8 i = 0; i <= LIGHT_SUN; i++) { const std::vector &lights = light_sources.lights[i]; for (std::vector::const_iterator it = lights.begin(); - it < lights.end(); it++) { + it < lights.end(); ++it) { MapNode n = it->block->getNodeNoCheck(it->rel_position, &is_valid_position); n.setLight(bank, i, ndef); @@ -885,6 +902,352 @@ void update_block_border_lighting(Map *map, MapBlock *block, } } +/*! + * Resets the lighting of the given VoxelManipulator to + * complete darkness and full sunlight. + * Operates in one map sector. + * + * \param offset contains the least x and z node coordinates + * of the map sector. + * \param light incoming sunlight, light[x][z] is true if there + * is sunlight above the voxel manipulator at the given x-z coordinates. + * The array's indices are relative node coordinates in the sector. + * After the procedure returns, this contains outgoing light at + * the bottom of the voxel manipulator. + */ +void fill_with_sunlight(MMVManip *vm, INodeDefManager *ndef, v2s16 offset, + bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) +{ + // Distance in array between two nodes on top of each other. + s16 ystride = vm->m_area.getExtent().X; + // Cache the ignore node. + MapNode ignore = MapNode(CONTENT_IGNORE); + // For each column of nodes: + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + // Position of the column on the map. + v2s16 realpos = offset + v2s16(x, z); + // Array indices in the voxel manipulator + s32 maxindex = vm->m_area.index(realpos.X, vm->m_area.MaxEdge.Y, + realpos.Y); + s32 minindex = vm->m_area.index(realpos.X, vm->m_area.MinEdge.Y, + realpos.Y); + // True if the current node has sunlight. + bool lig = light[z][x]; + // For each node, downwards: + for (s32 i = maxindex; i >= minindex; i -= ystride) { + MapNode *n; + if (vm->m_flags[i] & VOXELFLAG_NO_DATA) + n = &ignore; + else + n = &vm->m_data[i]; + // Ignore IGNORE nodes, these are not generated yet. + if(n->getContent() == CONTENT_IGNORE) + continue; + const ContentFeatures &f = ndef->get(n->getContent()); + if (lig && !f.sunlight_propagates) + // Sunlight is stopped. + lig = false; + // Reset light + n->setLight(LIGHTBANK_DAY, lig ? 15 : 0, f); + n->setLight(LIGHTBANK_NIGHT, 0, f); + } + // Output outgoing light. + light[z][x] = lig; + } +} + +/*! + * Returns incoming sunlight for one map block. + * If block above is not found, it is loaded. + * + * \param pos position of the map block that gets the sunlight. + * \param light incoming sunlight, light[z][x] is true if there + * is sunlight above the block at the given z-x relative + * node coordinates. + */ +void is_sunlight_above_block(ServerMap *map, mapblock_v3 pos, + INodeDefManager *ndef, bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) +{ + mapblock_v3 source_block_pos = pos + v3s16(0, 1, 0); + // Get or load source block. + // It might take a while to load, but correcting incorrect + // sunlight may be even slower. + MapBlock *source_block = map->emergeBlock(source_block_pos, false); + // Trust only generated blocks. + if (source_block == NULL || source_block->isDummy() + || !source_block->isGenerated()) { + // But if there is no block above, then use heuristics + bool sunlight = true; + MapBlock *node_block = map->getBlockNoCreateNoEx(pos); + if (node_block == NULL) + // This should not happen. + sunlight = false; + else + sunlight = !node_block->getIsUnderground(); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + light[z][x] = sunlight; + } else { + // Dummy boolean, the position is valid. + bool is_valid_position; + // For each column: + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + // Get the bottom block. + MapNode above = source_block->getNodeNoCheck(x, 0, z, + &is_valid_position); + light[z][x] = above.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN; + } + } +} + +/*! + * Propagates sunlight down in a given map block. + * + * \param data contains incoming sunlight and shadow and + * the coordinates of the target block. + * \param unlight propagated shadow is inserted here + * \param relight propagated sunlight is inserted here + * + * \returns true if the block was modified, false otherwise. + */ +bool propagate_block_sunlight(Map *map, INodeDefManager *ndef, + SunlightPropagationData *data, UnlightQueue *unlight, ReLightQueue *relight) +{ + bool modified = false; + // Get the block. + MapBlock *block = map->getBlockNoCreateNoEx(data->target_block); + if (block == NULL || block->isDummy()) { + // The work is done if the block does not contain data. + data->data.clear(); + return false; + } + // Dummy boolean + bool is_valid; + // For each changing column of nodes: + size_t index; + for (index = 0; index < data->data.size(); index++) { + SunlightPropagationUnit it = data->data[index]; + // Relative position of the currently inspected node. + relative_v3 current_pos(it.relative_pos.X, MAP_BLOCKSIZE - 1, + it.relative_pos.Y); + if (it.is_sunlit) { + // Propagate sunlight. + // For each node downwards: + for (; current_pos.Y >= 0; current_pos.Y--) { + MapNode n = block->getNodeNoCheck(current_pos, &is_valid); + const ContentFeatures &f = ndef->get(n); + if (n.getLightRaw(LIGHTBANK_DAY, f) < LIGHT_SUN + && f.sunlight_propagates) { + // This node gets sunlight. + n.setLight(LIGHTBANK_DAY, LIGHT_SUN, f); + block->setNodeNoCheck(current_pos, n); + modified = true; + relight->push(LIGHT_SUN, current_pos, data->target_block, + block, 4); + } else { + // Light already valid, propagation stopped. + break; + } + } + } else { + // Propagate shadow. + // For each node downwards: + for (; current_pos.Y >= 0; current_pos.Y--) { + MapNode n = block->getNodeNoCheck(current_pos, &is_valid); + const ContentFeatures &f = ndef->get(n); + if (n.getLightRaw(LIGHTBANK_DAY, f) == LIGHT_SUN) { + // The sunlight is no longer valid. + n.setLight(LIGHTBANK_DAY, 0, f); + block->setNodeNoCheck(current_pos, n); + modified = true; + unlight->push(LIGHT_SUN, current_pos, data->target_block, + block, 4); + } else { + // Reached shadow, propagation stopped. + break; + } + } + } + if (current_pos.Y >= 0) { + // Propagation stopped, remove from data. + data->data[index] = data->data.back(); + data->data.pop_back(); + index--; + } + } + return modified; +} + +/*! + * Borders of a map block in relative node coordinates. + * The areas do not overlap. + * Compatible with type 'direction'. + */ +const VoxelArea block_pad[] = { + VoxelArea(v3s16(15, 0, 0), v3s16(15, 15, 15)), //X+ + VoxelArea(v3s16(1, 15, 0), v3s16(14, 15, 15)), //Y+ + VoxelArea(v3s16(1, 1, 15), v3s16(14, 14, 15)), //Z+ + VoxelArea(v3s16(1, 1, 0), v3s16(14, 14, 0)), //Z- + VoxelArea(v3s16(1, 0, 0), v3s16(14, 0, 15)), //Y- + VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X- +}; + +void blit_back_with_light(ServerMap *map, MMVManip *vm, + std::map *modified_blocks) +{ + INodeDefManager *ndef = map->getNodeDefManager(); + mapblock_v3 minblock = getNodeBlockPos(vm->m_area.MinEdge); + mapblock_v3 maxblock = getNodeBlockPos(vm->m_area.MaxEdge); + // First queue is for day light, second is for night light. + UnlightQueue unlight[] = { UnlightQueue(256), UnlightQueue(256) }; + ReLightQueue relight[] = { ReLightQueue(256), ReLightQueue(256) }; + // Will hold sunlight data. + bool lights[MAP_BLOCKSIZE][MAP_BLOCKSIZE]; + SunlightPropagationData data; + // Dummy boolean. + bool is_valid; + + // --- STEP 1: reset everything to sunlight + + // For each map block: + for (s16 x = minblock.X; x <= maxblock.X; x++) + for (s16 z = minblock.Z; z <= maxblock.Z; z++) { + // Extract sunlight above. + is_sunlight_above_block(map, v3s16(x, maxblock.Y, z), ndef, lights); + v2s16 offset(x, z); + offset *= MAP_BLOCKSIZE; + // Reset the voxel manipulator. + fill_with_sunlight(vm, ndef, offset, lights); + // Copy sunlight data + data.target_block = v3s16(x, minblock.Y - 1, z); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + data.data.push_back( + SunlightPropagationUnit(v2s16(x, z), lights[z][x])); + // Propagate sunlight and shadow below the voxel manipulator. + while (!data.data.empty()) { + if (propagate_block_sunlight(map, ndef, &data, &unlight[0], + &relight[0])) + (*modified_blocks)[data.target_block] = + map->getBlockNoCreateNoEx(data.target_block); + // Step downwards. + data.target_block.Y--; + } + } + + // --- STEP 2: Get nodes from borders to unlight + + // In case there are unloaded holes in the voxel manipulator + // unlight each block. + // For each block: + for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) + for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) + for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { + v3s16 blockpos(b_x, b_y, b_z); + MapBlock *block = map->getBlockNoCreateNoEx(blockpos); + if (!block || block->isDummy()) + // Skip not existing blocks. + continue; + v3s16 offset = block->getPosRelative(); + // For each border of the block: + for (direction d = 0; d < 6; d++) { + VoxelArea a = block_pad[d]; + // For each node of the border: + for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++) + for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) + for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + v3s16 relpos(x, y, z); + // Get old and new node + MapNode oldnode = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &oldf = ndef->get(oldnode); + MapNode newnode = vm->getNodeNoExNoEmerge(relpos + offset); + const ContentFeatures &newf = ndef->get(newnode); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 oldlight = oldf.param_type == CPT_LIGHT ? + oldnode.getLightNoChecks(bank, &oldf): + LIGHT_SUN; // no light information, force unlighting + u8 newlight = newf.param_type == CPT_LIGHT ? + newnode.getLightNoChecks(bank, &newf): + newf.light_source; + // If the new node is dimmer, unlight. + if (oldlight > newlight) { + unlight[b].push( + oldlight, relpos, blockpos, block, 6); + } + } // end of banks + } // end of nodes + } // end of borders + } // end of blocks + + // --- STEP 3: All information extracted, overwrite + + vm->blitBackAll(modified_blocks, true); + + // --- STEP 4: Do unlighting + + for (size_t bank = 0; bank < 2; bank++) { + LightBank b = banks[bank]; + unspread_light(map, ndef, b, unlight[bank], relight[bank], + *modified_blocks); + } + + // --- STEP 5: Get all newly inserted light sources + + // For each block: + for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) + for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) + for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { + v3s16 blockpos(b_x, b_y, b_z); + MapBlock *block = map->getBlockNoCreateNoEx(blockpos); + if (!block || block->isDummy()) + // Skip not existing blocks + continue; + // For each node in the block: + for (s32 x = 0; x < MAP_BLOCKSIZE; x++) + for (s32 z = 0; z < MAP_BLOCKSIZE; z++) + for (s32 y = 0; y < MAP_BLOCKSIZE; y++) { + v3s16 relpos(x, y, z); + MapNode node = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &f = ndef->get(node); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 light = f.param_type == CPT_LIGHT ? + node.getLightNoChecks(bank, &f): + f.light_source; + if (light > 1) + relight[b].push(light, relpos, blockpos, block, 6); + } // end of banks + } // end of nodes + } // end of blocks + + // --- STEP 6: do light spreading + + // For each light bank: + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + // Sunlight is already initialized. + u8 maxlight = (b == 0) ? LIGHT_MAX : LIGHT_SUN; + // Initialize light values for light spreading. + for (u8 i = 0; i <= maxlight; i++) { + const std::vector &lights = relight[b].lights[i]; + for (std::vector::const_iterator it = lights.begin(); + it < lights.end(); ++it) { + MapNode n = it->block->getNodeNoCheck(it->rel_position, + &is_valid); + n.setLight(bank, i, ndef); + it->block->setNodeNoCheck(it->rel_position, n); + } + } + // Spread lights. + spread_light(map, ndef, bank, relight[b], *modified_blocks); + } +} + VoxelLineIterator::VoxelLineIterator( const v3f &start_position, const v3f &line_vector) : diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index bf1638fa3..cdffe86c8 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -26,7 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/cpp11_container.h" class Map; +class ServerMap; class MapBlock; +class MMVManip; namespace voxalgo { @@ -84,6 +86,17 @@ void update_lighting_nodes( void update_block_border_lighting(Map *map, MapBlock *block, std::map &modified_blocks); +/*! + * Copies back nodes from a voxel manipulator + * to the map and updates lighting. + * For server use only. + * + * \param modified_blocks output, contains all map blocks that + * the function modified + */ +void blit_back_with_light(ServerMap *map, MMVManip *vm, + std::map *modified_blocks); + /*! * This class iterates trough voxels that intersect with * a line. The collision detection does not see nodeboxes, -- cgit v1.2.3 From 6d1e6f889826a5802e17f53f99000a51b2e00066 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Sat, 11 Mar 2017 17:03:15 +0100 Subject: Split light update into two parts The common part can be reused. --- src/voxelalgorithms.cpp | 150 +++++++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 58 deletions(-) (limited to 'src/voxelalgorithms.cpp') diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index 411369bd4..f2142717f 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -1094,6 +1094,95 @@ const VoxelArea block_pad[] = { VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X- }; +/*! + * The common part of bulk light updates - it is always executed. + * The procedure takes the nodes that should be unlit, and the + * full modified area. + * + * The procedure handles the correction of all lighting except + * direct sunlight spreading. + * + * \param minblock least coordinates of the changed area in block + * coordinates + * \param maxblock greatest coordinates of the changed area in block + * coordinates + * \param unlight the first queue is for day light, the second is for + * night light. Contains all nodes on the borders that need to be unlit. + * \param relight the first queue is for day light, the second is for + * night light. Contains nodes that were not modified, but got sunlight + * because the changes. + * \param modified_blocks the procedure adds all modified blocks to + * this map + */ +void finish_bulk_light_update(Map *map, mapblock_v3 minblock, + mapblock_v3 maxblock, UnlightQueue unlight[2], ReLightQueue relight[2], + std::map *modified_blocks) +{ + INodeDefManager *ndef = map->getNodeDefManager(); + // dummy boolean + bool is_valid; + + // --- STEP 1: Do unlighting + + for (size_t bank = 0; bank < 2; bank++) { + LightBank b = banks[bank]; + unspread_light(map, ndef, b, unlight[bank], relight[bank], + *modified_blocks); + } + + // --- STEP 2: Get all newly inserted light sources + + // For each block: + for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) + for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) + for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { + v3s16 blockpos(b_x, b_y, b_z); + MapBlock *block = map->getBlockNoCreateNoEx(blockpos); + if (!block || block->isDummy()) + // Skip not existing blocks + continue; + // For each node in the block: + for (s32 x = 0; x < MAP_BLOCKSIZE; x++) + for (s32 z = 0; z < MAP_BLOCKSIZE; z++) + for (s32 y = 0; y < MAP_BLOCKSIZE; y++) { + v3s16 relpos(x, y, z); + MapNode node = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &f = ndef->get(node); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 light = f.param_type == CPT_LIGHT ? + node.getLightNoChecks(bank, &f): + f.light_source; + if (light > 1) + relight[b].push(light, relpos, blockpos, block, 6); + } // end of banks + } // end of nodes + } // end of blocks + + // --- STEP 3: do light spreading + + // For each light bank: + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + // Sunlight is already initialized. + u8 maxlight = (b == 0) ? LIGHT_MAX : LIGHT_SUN; + // Initialize light values for light spreading. + for (u8 i = 0; i <= maxlight; i++) { + const std::vector &lights = relight[b].lights[i]; + for (std::vector::const_iterator it = lights.begin(); + it < lights.end(); ++it) { + MapNode n = it->block->getNodeNoCheck(it->rel_position, + &is_valid); + n.setLight(bank, i, ndef); + it->block->setNodeNoCheck(it->rel_position, n); + } + } + // Spread lights. + spread_light(map, ndef, bank, relight[b], *modified_blocks); + } +} + void blit_back_with_light(ServerMap *map, MMVManip *vm, std::map *modified_blocks) { @@ -1187,65 +1276,10 @@ void blit_back_with_light(ServerMap *map, MMVManip *vm, vm->blitBackAll(modified_blocks, true); - // --- STEP 4: Do unlighting - - for (size_t bank = 0; bank < 2; bank++) { - LightBank b = banks[bank]; - unspread_light(map, ndef, b, unlight[bank], relight[bank], - *modified_blocks); - } - - // --- STEP 5: Get all newly inserted light sources + // --- STEP 4: Finish light update - // For each block: - for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) - for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) - for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { - v3s16 blockpos(b_x, b_y, b_z); - MapBlock *block = map->getBlockNoCreateNoEx(blockpos); - if (!block || block->isDummy()) - // Skip not existing blocks - continue; - // For each node in the block: - for (s32 x = 0; x < MAP_BLOCKSIZE; x++) - for (s32 z = 0; z < MAP_BLOCKSIZE; z++) - for (s32 y = 0; y < MAP_BLOCKSIZE; y++) { - v3s16 relpos(x, y, z); - MapNode node = block->getNodeNoCheck(x, y, z, &is_valid); - const ContentFeatures &f = ndef->get(node); - // For each light bank - for (size_t b = 0; b < 2; b++) { - LightBank bank = banks[b]; - u8 light = f.param_type == CPT_LIGHT ? - node.getLightNoChecks(bank, &f): - f.light_source; - if (light > 1) - relight[b].push(light, relpos, blockpos, block, 6); - } // end of banks - } // end of nodes - } // end of blocks - - // --- STEP 6: do light spreading - - // For each light bank: - for (size_t b = 0; b < 2; b++) { - LightBank bank = banks[b]; - // Sunlight is already initialized. - u8 maxlight = (b == 0) ? LIGHT_MAX : LIGHT_SUN; - // Initialize light values for light spreading. - for (u8 i = 0; i <= maxlight; i++) { - const std::vector &lights = relight[b].lights[i]; - for (std::vector::const_iterator it = lights.begin(); - it < lights.end(); ++it) { - MapNode n = it->block->getNodeNoCheck(it->rel_position, - &is_valid); - n.setLight(bank, i, ndef); - it->block->setNodeNoCheck(it->rel_position, n); - } - } - // Spread lights. - spread_light(map, ndef, bank, relight[b], *modified_blocks); - } + finish_bulk_light_update(map, minblock, maxblock, unlight, relight, + modified_blocks); } VoxelLineIterator::VoxelLineIterator( -- cgit v1.2.3 From 57e5aa662851485902575c3c747437e365bf72c8 Mon Sep 17 00:00:00 2001 From: Dániel Juhász Date: Sat, 11 Mar 2017 17:07:04 +0100 Subject: Light update for map blocks This is not really different from the light update of a voxel manipulator. This update does not assume that the lighting was correct before, therefore it is useful for correction. Also expose this function to the Lua API for light correction, and allow voxel manipulators not to update the light. --- doc/lua_api.txt | 24 +++++++- src/map.cpp | 10 ++++ src/map.h | 10 ++++ src/script/lua_api/l_env.cpp | 31 ++++++++++ src/script/lua_api/l_env.h | 3 + src/script/lua_api/l_vmanip.cpp | 3 +- src/voxelalgorithms.cpp | 122 +++++++++++++++++++++++++++++++++++++++- src/voxelalgorithms.h | 9 +++ 8 files changed, 209 insertions(+), 3 deletions(-) (limited to 'src/voxelalgorithms.cpp') diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 6e7a1de68..16e662e0c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2398,6 +2398,22 @@ and `minetest.auth_reload` call the authetification handler. * increase level of leveled node by level, default `level` equals `1` * if `totallevel > maxlevel`, returns rest (`total-max`) * can be negative for decreasing +* `minetest.fix_light(pos1, pos2)`: returns `true`/`false` + * resets the light in a cuboid-shaped part of + the map and removes lighting bugs. + * Loads the area if it is not loaded. + * `pos1` is the corner of the cuboid with the least coordinates + (in node coordinates), inclusive. + * `pos2` is the opposite corner of the cuboid, inclusive. + * The actual updated cuboid might be larger than the specified one, + because only whole map blocks can be updated. + The actual updated area consists of those map blocks that intersect + with the given cuboid. + * However, the neighborhood of the updated area might change + as well, as light can spread out of the cuboid, also light + might be removed. + * returns `false` if the area is not fully generated, + `true` otherwise * `core.check_single_for_falling(pos)` * causes an unsupported `group:falling_node` node to fall and causes an unattached `group:attached_node` node to fall. @@ -3421,8 +3437,14 @@ will place the schematic inside of the VoxelManip. * `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object containing the region formed by `p1` and `p2`. * returns actual emerged `pmin`, actual emerged `pmax` -* `write_to_map()`: Writes the data loaded from the `VoxelManip` back to the map. +* `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to the map. * **important**: data must be set using `VoxelManip:set_data()` before calling this + * if `light` is true, then lighting is automatically recalculated. + The default value is true. + If `light` is false, no light calculations happen, and you should correct + all modified blocks with `minetest.fix_light()` as soon as possible. + Keep in mind that modifying the map where light is incorrect can cause + more lighting bugs. * `get_node_at(pos)`: Returns a `MapNode` table of the node currently loaded in the `VoxelManip` at that position * `set_node_at(pos, node)`: Sets a specific `MapNode` in the `VoxelManip` at that position diff --git a/src/map.cpp b/src/map.cpp index f8bbee180..8754813dd 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2591,6 +2591,16 @@ void ServerMap::PrintInfo(std::ostream &out) out<<"ServerMap: "; } +bool ServerMap::repairBlockLight(v3s16 blockpos, + std::map *modified_blocks) +{ + MapBlock *block = emergeBlock(blockpos, false); + if (!block || !block->isGenerated()) + return false; + voxalgo::repair_block_light(this, block, modified_blocks); + return true; +} + MMVManip::MMVManip(Map *map): VoxelManipulator(), m_is_dirty(false), diff --git a/src/map.h b/src/map.h index 744a4d1e2..739cdb59b 100644 --- a/src/map.h +++ b/src/map.h @@ -477,6 +477,16 @@ public: u64 getSeed(); s16 getWaterLevel(); + /*! + * Fixes lighting in one map block. + * May modify other blocks as well, as light can spread + * out of the specified block. + * Returns false if the block is not generated (so nothing + * changed), true otherwise. + */ + bool repairBlockLight(v3s16 blockpos, + std::map *modified_blocks); + MapSettingsManager settings_mgr; private: diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 4fad7b37c..1fa7845b5 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -847,6 +847,36 @@ int ModApiEnvMod::l_line_of_sight(lua_State *L) return 1; } +// fix_light(p1, p2) +int ModApiEnvMod::l_fix_light(lua_State *L) +{ + GET_ENV_PTR; + + v3s16 blockpos1 = getContainerPos(read_v3s16(L, 1), MAP_BLOCKSIZE); + v3s16 blockpos2 = getContainerPos(read_v3s16(L, 2), MAP_BLOCKSIZE); + ServerMap &map = env->getServerMap(); + std::map modified_blocks; + bool success = true; + v3s16 blockpos; + for (blockpos.X = blockpos1.X; blockpos.X <= blockpos2.X; blockpos.X++) + for (blockpos.Y = blockpos1.Y; blockpos.Y <= blockpos2.Y; blockpos.Y++) + for (blockpos.Z = blockpos1.Z; blockpos.Z <= blockpos2.Z; blockpos.Z++) { + success = success & map.repairBlockLight(blockpos, &modified_blocks); + } + if (modified_blocks.size() > 0) { + MapEditEvent event; + event.type = MEET_OTHER; + for (std::map::iterator it = modified_blocks.begin(); + it != modified_blocks.end(); ++it) + event.modified_blocks.insert(it->first); + + map.dispatchEvent(&event); + } + lua_pushboolean(L, success); + + return 1; +} + // emerge_area(p1, p2, [callback, context]) // emerge mapblocks in area p1..p2, calls callback with context upon completion int ModApiEnvMod::l_emerge_area(lua_State *L) @@ -1089,6 +1119,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(find_node_near); API_FCT(find_nodes_in_area); API_FCT(find_nodes_in_area_under_air); + API_FCT(fix_light); API_FCT(emerge_area); API_FCT(delete_area); API_FCT(get_perlin); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 38b2282d7..3f688b398 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -128,6 +128,9 @@ private: // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_nodes_in_area_under_air(lua_State *L); + // fix_light(p1, p2) -> true/false + static int l_fix_light(lua_State *L); + // emerge_area(p1, p2) static int l_emerge_area(lua_State *L); diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 7316fb200..254a7e5a6 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -110,9 +110,10 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); + bool update_light = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true; GET_ENV_PTR; ServerMap *map = &(env->getServerMap()); - if (o->is_mapgen_vm) { + if (o->is_mapgen_vm || !update_light) { o->vm->blitBackAll(&(o->modified_blocks)); } else { voxalgo::blit_back_with_light(map, o->vm, diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index f2142717f..40f8595a7 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -1136,7 +1136,7 @@ void finish_bulk_light_update(Map *map, mapblock_v3 minblock, for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++) for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++) for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) { - v3s16 blockpos(b_x, b_y, b_z); + const v3s16 blockpos(b_x, b_y, b_z); MapBlock *block = map->getBlockNoCreateNoEx(blockpos); if (!block || block->isDummy()) // Skip not existing blocks @@ -1282,6 +1282,126 @@ void blit_back_with_light(ServerMap *map, MMVManip *vm, modified_blocks); } +/*! + * Resets the lighting of the given map block to + * complete darkness and full sunlight. + * + * \param light incoming sunlight, light[x][z] is true if there + * is sunlight above the map block at the given x-z coordinates. + * The array's indices are relative node coordinates in the block. + * After the procedure returns, this contains outgoing light at + * the bottom of the map block. + */ +void fill_with_sunlight(MapBlock *block, INodeDefManager *ndef, + bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) +{ + if (block->isDummy()) + return; + // dummy boolean + bool is_valid; + // For each column of nodes: + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + // True if the current node has sunlight. + bool lig = light[z][x]; + // For each node, downwards: + for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) { + MapNode n = block->getNodeNoCheck(x, y, z, &is_valid); + // Ignore IGNORE nodes, these are not generated yet. + if (n.getContent() == CONTENT_IGNORE) + continue; + const ContentFeatures &f = ndef->get(n.getContent()); + if (lig && !f.sunlight_propagates) { + // Sunlight is stopped. + lig = false; + } + // Reset light + n.setLight(LIGHTBANK_DAY, lig ? 15 : 0, f); + n.setLight(LIGHTBANK_NIGHT, 0, f); + block->setNodeNoCheck(x, y, z, n); + } + // Output outgoing light. + light[z][x] = lig; + } +} + +void repair_block_light(ServerMap *map, MapBlock *block, + std::map *modified_blocks) +{ + if (!block || block->isDummy()) + return; + INodeDefManager *ndef = map->getNodeDefManager(); + // First queue is for day light, second is for night light. + UnlightQueue unlight[] = { UnlightQueue(256), UnlightQueue(256) }; + ReLightQueue relight[] = { ReLightQueue(256), ReLightQueue(256) }; + // Will hold sunlight data. + bool lights[MAP_BLOCKSIZE][MAP_BLOCKSIZE]; + SunlightPropagationData data; + // Dummy boolean. + bool is_valid; + + // --- STEP 1: reset everything to sunlight + + mapblock_v3 blockpos = block->getPos(); + (*modified_blocks)[blockpos] = block; + // For each map block: + // Extract sunlight above. + is_sunlight_above_block(map, blockpos, ndef, lights); + // Reset the voxel manipulator. + fill_with_sunlight(block, ndef, lights); + // Copy sunlight data + data.target_block = v3s16(blockpos.X, blockpos.Y - 1, blockpos.Z); + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + data.data.push_back( + SunlightPropagationUnit(v2s16(x, z), lights[z][x])); + } + // Propagate sunlight and shadow below the voxel manipulator. + while (!data.data.empty()) { + if (propagate_block_sunlight(map, ndef, &data, &unlight[0], + &relight[0])) + (*modified_blocks)[data.target_block] = + map->getBlockNoCreateNoEx(data.target_block); + // Step downwards. + data.target_block.Y--; + } + + // --- STEP 2: Get nodes from borders to unlight + + // For each border of the block: + for (direction d = 0; d < 6; d++) { + VoxelArea a = block_pad[d]; + // For each node of the border: + for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++) + for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) + for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + v3s16 relpos(x, y, z); + // Get node + MapNode node = block->getNodeNoCheck(x, y, z, &is_valid); + const ContentFeatures &f = ndef->get(node); + // For each light bank + for (size_t b = 0; b < 2; b++) { + LightBank bank = banks[b]; + u8 light = f.param_type == CPT_LIGHT ? + node.getLightNoChecks(bank, &f): + f.light_source; + // If the new node is dimmer than sunlight, unlight. + // (if it has maximal light, it is pointless to remove + // surrounding light, as it can only become brighter) + if (LIGHT_SUN > light) { + unlight[b].push( + LIGHT_SUN, relpos, blockpos, block, 6); + } + } // end of banks + } // end of nodes + } // end of borders + + // STEP 3: Remove and spread light + + finish_bulk_light_update(map, blockpos, blockpos, unlight, relight, + modified_blocks); +} + VoxelLineIterator::VoxelLineIterator( const v3f &start_position, const v3f &line_vector) : diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index cdffe86c8..b518979d7 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -97,6 +97,15 @@ void update_block_border_lighting(Map *map, MapBlock *block, void blit_back_with_light(ServerMap *map, MMVManip *vm, std::map *modified_blocks); +/*! + * Corrects the light in a map block. + * For server use only. + * + * \param block the block to update + */ +void repair_block_light(ServerMap *map, MapBlock *block, + std::map *modified_blocks); + /*! * This class iterates trough voxels that intersect with * a line. The collision detection does not see nodeboxes, -- cgit v1.2.3