/* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "voxelalgorithms.h" #include "nodedef.h" #include "mapblock.h" #include "map.h" namespace voxalgo { /*! * A direction. * 0=X+ * 1=Y+ * 2=Z+ * 3=Z- * 4=Y- * 5=X- * 6=no direction * Two directions are opposite only if their sum is 5. */ typedef u8 direction; /*! * Relative node position. * This represents a node's position in its map block. * All coordinates must be between 0 and 15. */ typedef v3s16 relative_v3; /*! * Position of a map block (block coordinates). * One block_pos unit is as long as 16 node position units. */ typedef v3s16 mapblock_v3; //! Contains information about a node whose light is about to change. struct ChangingLight { //! Relative position of the node in its map block. relative_v3 rel_position; //! Position of the node's block. mapblock_v3 block_position; //! Pointer to the node's block. MapBlock *block = NULL; /*! * Direction from the node that caused this node's changing * to this node. */ direction source_direction = 6; ChangingLight() = default; ChangingLight(const relative_v3 &rel_pos, const mapblock_v3 &block_pos, MapBlock *b, direction source_dir) : rel_position(rel_pos), block_position(block_pos), block(b), source_direction(source_dir) {} }; /*! * A fast, priority queue-like container to contain ChangingLights. * The ChangingLights are ordered by the given light levels. * The brightest ChangingLight is returned first. */ struct LightQueue { //! For each light level there is a vector. std::vector lights[LIGHT_SUN + 1]; //! Light of the brightest ChangingLight in the queue. u8 max_light; /*! * Creates a LightQueue. * \param reserve for each light level that many slots are reserved. */ LightQueue(size_t reserve) { max_light = LIGHT_SUN; for (u8 i = 0; i <= LIGHT_SUN; i++) { lights[i].reserve(reserve); } } /*! * Returns the next brightest ChangingLight and * removes it from the queue. * If there were no elements in the queue, the given parameters * remain unmodified. * \param light light level of the popped ChangingLight * \param data the ChangingLight that was popped * \returns true if there was a ChangingLight in the queue. */ bool next(u8 &light, ChangingLight &data) { while (lights[max_light].empty()) { if (max_light == 0) { return false; } max_light--; } light = max_light; data = lights[max_light].back(); lights[max_light].pop_back(); return true; } /*! * Adds an element to the queue. * The parameters are the same as in ChangingLight's constructor. * \param light light level of the ChangingLight */ inline void push(u8 light, const relative_v3 &rel_pos, const mapblock_v3 &block_pos, MapBlock *block, direction source_dir) { assert(light <= LIGHT_SUN); lights[light].emplace_back(rel_pos, block_pos, block, source_dir); } }; /*! * This type of light queue is for unlighting. * A node can be pushed in it only if its raw light is zero. * This prevents pushing nodes twice into this queue. * The light of the pushed ChangingLight must be the * light of the node before unlighting it. */ typedef LightQueue UnlightQueue; /*! * This type of light queue is for spreading lights. * While spreading lights, all the nodes in it must * have the same light as the light level the ChangingLights * were pushed into this queue with. This prevents unnecessary * re-pushing of the nodes into the queue. * If a node doesn't let light trough but emits light, it can be added * too. */ typedef LightQueue ReLightQueue; /*! * neighbor_dirs[i] points towards * the direction i. * See the definition of the type "direction" */ const static v3s16 neighbor_dirs[6] = { v3s16(1, 0, 0), // right v3s16(0, 1, 0), // top v3s16(0, 0, 1), // back v3s16(0, 0, -1), // front v3s16(0, -1, 0), // bottom v3s16(-1, 0, 0), // left }; /*! * Transforms the given map block offset by one node towards * the specified direction. * \param dir the direction of the transformation * \param rel_pos the node's relative position in its map block * \param block_pos position of the node's block */ bool step_rel_block_pos(direction dir, relative_v3 &rel_pos, mapblock_v3 &block_pos) { switch (dir) { case 0: if (rel_pos.X < MAP_BLOCKSIZE - 1) { rel_pos.X++; } else { rel_pos.X = 0; block_pos.X++; return true; } break; case 1: if (rel_pos.Y < MAP_BLOCKSIZE - 1) { rel_pos.Y++; } else { rel_pos.Y = 0; block_pos.Y++; return true; } break; case 2: if (rel_pos.Z < MAP_BLOCKSIZE - 1) { rel_pos.Z++; } else { rel_pos.Z = 0; block_pos.Z++; return true; } break; case 3: if (rel_pos.Z > 0) { rel_pos.Z--; } else { rel_pos.Z = MAP_BLOCKSIZE - 1; block_pos.Z--; return true; } break; case 4: if (rel_pos.Y > 0) { rel_pos.Y--; } else { rel_pos.Y = MAP_BLOCKSIZE - 1; block_pos.Y--; return true; } break; case 5: if (rel_pos.X > 0) { rel_pos.X--; } else { rel_pos.X = MAP_BLOCKSIZE - 1; block_pos.X--; return true; } break; } return false; } /* * Removes all light that is potentially emitted by the specified * light sources. These nodes will have zero light. * Returns all nodes whose light became zero but should be re-lighted. * * \param bank the light bank in which the procedure operates * \param from_nodes nodes whose light is removed * \param light_sources nodes that should be re-lighted * \param modified_blocks output, all modified map blocks are added to this */ void unspread_light(Map *map, const NodeDefManager *nodemgr, LightBank bank, UnlightQueue &from_nodes, ReLightQueue &light_sources, std::map &modified_blocks) { // Stores data popped from from_nodes u8 current_light; ChangingLight current; // Data of the current neighbor mapblock_v3 neighbor_block_pos; relative_v3 neighbor_rel_pos; // A dummy boolean bool is_valid_position; // Direction of the brightest neighbor of the node direction source_dir; while (from_nodes.next(current_light, current)) { // For all nodes that need unlighting // There is no brightest neighbor source_dir = 6; // The current node const MapNode &node = current.block->getNodeNoCheck( current.rel_position, &is_valid_position); const ContentFeatures &f = nodemgr->get(node); // If the node emits light, it behaves like it had a // brighter neighbor. u8 brightest_neighbor_light = f.light_source + 1; for (direction i = 0; i < 6; i++) { //For each neighbor // The node that changed this node has already zero light // and it can't give light to this node if (current.source_direction + i == 5) { continue; } // Get the neighbor's position and block neighbor_rel_pos = current.rel_position; neig /* start css.sty */ .ecsx-1728{font-size:170%; font-weight: bold;} .ecsx-1728{ font-weight: bold;} .ecsx-1728{ font-weight: bold;} .ecsx-1728{ font-weight: bold;} .ecsx-1728{ font-weight: bold;} .ecsx-1728{ font-weight: bold;} .ecst-1440{font-size:140%;} .ecsx-1200{font-size:120%; font-weight: bold;} .ecsx-1200{ font-weight: bold;} .ecsx-1200{ font-weight: bold;} .ecsx-1200{ font-weight: bold;} .ecsx-1200{ font-weight: bold;} .ecsx-1200{ font-weight: bold;} .ecsx-1000{ font-weight: bold;} .ecsx-1000{ font-weight: bold;} .ecsx-1000{ font-weight: bold;} .ecsx-1000{ font-weight: bold;} .ecsx-1000{ font-weight: bold;} .ecsx-1000{ font-weight: bold;} .ecbx-1000{ font-weight: bold;} .ecbx-1000{ font-weight: bold;} .ecbx-1000{ font-weight: bold;} .ecbx-1000{ font-weight: bold;} .ecbx-1000{ font-weight: bold;} .ecbx-1000{ font-weight: bold;} p.noindent { text-indent: 0em } td p.noindent { text-indent: 0em; margin-top:0em; } p.nopar { text-indent: 0em; } p.indent{ text-indent: 1.5em } @media print {div.crosslinks {visibility:hidden;}} a img { border-top: 0; border-left: 0; border-right: 0; } center { margin-top:1em; margin-bottom:1em; } td center { margin-top:0em; margin-bottom:0em; } .Canvas { position:relative; } img.math{vertical-align:middle;} li p.indent { text-indent: 0em } li p:first-child{ margin-top:0em; } li p:last-child, li div:last-child { margin-bottom:0.5em; } li p~ul:last-child, li p~ol:last-child{ margin-bottom:0.5em; } .enumerate1 {list-style-type:decimal;} .enumerate2 {list-style-type:lower-alpha;} .enumerate3 {list-style-type:lower-roman;} .enumerate4 {list-style-type:upper-alpha;} div.newtheorem { margin-bottom: 2em; margin-top: 2em;} .obeylines-h,.obeylines-v {white-space: nowrap; } div.obeylines-v p { margin-top:0; margin-bottom:0; } .overline{ text-decoration:overline; } .overline img{ border-top: 1px solid black; } td.displaylines {text-align:center; white-space:nowrap;} .centerline {text-align:center;} .rightline {text-align:right;} div.verbatim {font-family: monospace; white-space: nowrap; text-align:left; clear:both; } .fbox {padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } div.fbox {display:table} div.center div.fbox {text-align:center; clear:both; padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } div.minipage{width:100%;} div.center, div.center div.center {text-align: center; margin-left:1em; margin-right:1em;} div.center div {text-align: left;} div.flushright, div.flushright div.flushright {text-align: right;} div.flushright div {text-align: left;} div.flushleft {text-align: left;} .underline{ text-decoration:underline; } .underline img{ border-bottom: 1px solid black; margin-bottom:1pt; } .framebox-c, .framebox-l, .framebox-r { padding-left:3.0pt; padding-right:3.0pt; text-indent:0pt; border:solid black 0.4pt; } .framebox-c {text-align:center;} .framebox-l {text-align:left;} .framebox-r {text-align:right;} span.thank-mark{ vertical-align: super } span.footnote-mark sup.textsuperscript, span.footnote-mark a sup.textsuperscript{ font-size:80%; } div.tabular, div.center div.tabular {text-align: center; margin-top:0.5em; margin-bottom:0.5em; } table.tabular td p{margin-top:0em;} table.tabular {margin-left: auto; margin-right: auto;} td p:first-child{ margin-top:0em; } td p:last-child{ margin-bottom:0em; } div.td00{ margin-left:0pt; margin-right:0pt; } div.td01{ margin-left:0pt; margin-right:5pt; } div.td10{ margin-left:5pt; margin-right:0pt; } div.td11{ margin-left:5pt; margin-right:5pt; } table[rules] {border-left:solid black 0.4pt; border-right:solid black 0.4pt; } td.td00{ padding-left:0pt; padding-right:0pt; } td.td01{ padding-left:0pt; padding-right:5pt; } td.td10{ padding-left:5pt; padding-right:0pt; } td.td11{ padding-left:5pt; padding-right:5pt; } table[rules] {border-left:solid black 0.4pt; border-right:solid black 0.4pt; } .hline hr, .cline hr{ height : 1px; margin:0px; } .tabbing-right {text-align:right;} span.TEX {letter-spacing: -0.125em; } span.TEX span.E{ position:relative;top:0.5ex;left:-0.0417em;} a span.TEX span.E {text-decoration: none; } span.LATEX span.A{ position:relative; top:-0.5ex; left:-0.4em; font-size:85%;} span.LATEX span.TEX{ position:relative; left: -0.4em; } div.float, div.figure {margin-left: auto; margin-right: auto;} div.float img {text-align:center;} div.figure img {text-align:center;} .marginpar {width:20%; float:right; text-align:left; margin-left:auto; margin-top:0.5em; font-size:85%; text-decoration:underline;} .marginpar p{margin-top:0.4em; margin-bottom:0.4em;} table.equation {width:100%;} .equation td{text-align:center; } td.equation { margin-top:1em; margin-bottom:1em; } td.equation-label { width:5%; text-align:center; } td.eqnarray4 { width:5%; white-space: normal; } td.eqnarray2 { width:5%; } table.eqnarray-star, table.eqnarray {width:100%;} div.eqnarray{text-align:center;} div.array {text-align:center;} div.pmatrix {text-align:center;} table.pmatrix {width:100%;} span.pmatrix img{vertical-align:middle;} div.pmatrix {text-align:center;} table.pmatrix {width:100%;} span.bar-css {text-decoration:overline;} img.cdots{vertical-align:middle;} .figure img.graphics {margin-left:10%;} /* end css.sty */ = 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; const NodeDefManager *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.emplace_back(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 (const VoxelArea &a : block_pad) { v3s16 relpos; // For each node of the border: for (relpos.X = a.MinEdge.X; relpos.X <= a.MaxEdge.X; relpos.X++) for (relpos.Z = a.MinEdge.Z; relpos.Z <= a.MaxEdge.Z; relpos.Z++) for (relpos.Y = a.MinEdge.Y; relpos.Y <= a.MaxEdge.Y; relpos.Y++) { // Get node MapNode node = block->getNodeNoCheck(relpos, &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) : m_start_position(start_position), m_line_vector(line_vector) { m_current_node_pos = floatToInt(m_start_position, 1); m_start_node_pos = m_current_node_pos; m_last_index = getIndex(floatToInt(start_position + line_vector, 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; } } void VoxelLineIterator::next() { m_current_index++; 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; } } s16 VoxelLineIterator::getIndex(v3s16 voxel){ return abs(voxel.X - m_start_node_pos.X) + abs(voxel.Y - m_start_node_pos.Y) + abs(voxel.Z - m_start_node_pos.Z); } } // namespace voxalgo