+ // If this is an opaque node, it still can emit light.
+ new_light = ndef->get(n).light_source;
+ }
+
+ if (new_light > 0) {
+ light_sources.push(new_light, rel_pos, block_pos, block, 6);
+ }
+
+ if (new_light < old_light) {
+ // The node became opaque or doesn't provide as much
+ // light as the previous one, so it must be unlighted.
+
+ // Add to unlight queue
+ n.setLight(bank, 0, ndef);
+ block->setNodeNoCheck(rel_pos, n);
+ disappearing_lights.push(old_light, rel_pos, block_pos, block,
+ 6);
+
+ // Remove sunlight, if there was any
+ if (bank == LIGHTBANK_DAY && old_light == LIGHT_SUN) {
+ for (s16 y = p.Y - 1;; y--) {
+ v3s16 n2pos(p.X, y, p.Z);
+
+ MapNode n2;
+
+ n2 = map->getNode(n2pos, &is_valid_position);
+ if (!is_valid_position)
+ break;
+
+ // If this node doesn't have sunlight, the nodes below
+ // it don't have too.
+ if (n2.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) {
+ break;
+ }
+ // Remove sunlight and add to unlight queue.
+ n2.setLight(LIGHTBANK_DAY, 0, ndef);
+ map->setNode(n2pos, n2);
+ relative_v3 rel_pos2;
+ mapblock_v3 block_pos2;
+ getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2);
+ MapBlock *block2 = map->getBlockNoCreateNoEx(
+ block_pos2);
+ disappearing_lights.push(LIGHT_SUN, rel_pos2,
+ block_pos2, block2,
+ 4 /* The node above caused the change */);
+ }
+ }
+ } else if (new_light > old_light) {
+ // It is sure that the node provides more light than the previous
+ // one, unlighting is not necessary.
+ // Propagate sunlight
+ if (bank == LIGHTBANK_DAY && new_light == LIGHT_SUN) {
+ for (s16 y = p.Y - 1;; y--) {
+ v3s16 n2pos(p.X, y, p.Z);
+
+ MapNode n2;
+
+ n2 = map->getNode(n2pos, &is_valid_position);
+ if (!is_valid_position)
+ break;
+
+ // This should not happen, but if the node has sunlight
+ // then the iteration should stop.
+ if (n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) {
+ break;
+ }
+ // If the node terminates sunlight, stop.
+ if (!ndef->get(n2).sunlight_propagates) {
+ break;
+ }
+ relative_v3 rel_pos2;
+ mapblock_v3 block_pos2;
+ getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2);
+ MapBlock *block2 = map->getBlockNoCreateNoEx(
+ block_pos2);
+ // Mark node for lighting.
+ light_sources.push(LIGHT_SUN, rel_pos2, block_pos2,
+ block2, 4);
+ }
+ }
+ }
+
+ }
+ // 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<ChangingLight> &lights = light_sources.lights[i];
+ for (std::vector<ChangingLight>::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);
+ }
+}
+
+/*!
+ * 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, const NodeDefManager *ndef,
+ LightBank bank, v3s16 pos)
+{
+ bool is_valid_position;
+ MapNode n = map->getNode(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 (const v3s16 &neighbor_dir : neighbor_dirs) {
+ MapNode n2 = map->getNode(pos + neighbor_dir,
+ &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<v3s16, MapBlock*> &modified_blocks)
+{
+ const NodeDefManager *ndef = map->getNodeDefManager();
+ bool is_valid_position;
+ for (LightBank bank : banks) {
+ // 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
+ // 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<ChangingLight> &lights = light_sources.lights[i];
+ for (std::vector<ChangingLight>::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);
+ }
+}
+
+/*!
+ * 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, const NodeDefManager *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,
+ const NodeDefManager *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, const NodeDefManager *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;