/* Minetest-c55 Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU 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 "voxel.h" #include "map.h" // For TimeTaker #include "utility.h" #include "gettime.h" /* Debug stuff */ u32 addarea_time = 0; u32 emerge_time = 0; u32 emerge_load_time = 0; u32 clearflag_time = 0; //u32 getwaterpressure_time = 0; //u32 spreadwaterpressure_time = 0; u32 updateareawaterpressure_time = 0; u32 flowwater_pre_time = 0; VoxelManipulator::VoxelManipulator(): m_data(NULL), m_flags(NULL) { m_disable_water_climb = false; } VoxelManipulator::~VoxelManipulator() { clear(); if(m_data) delete[] m_data; if(m_flags) delete[] m_flags; } void VoxelManipulator::clear() { // Reset area to volume=0 m_area = VoxelArea(); if(m_data) delete[] m_data; m_data = NULL; if(m_flags) delete[] m_flags; m_flags = NULL; } void VoxelManipulator::print(std::ostream &o, VoxelPrintMode mode) { v3s16 em = m_area.getExtent(); v3s16 of = m_area.MinEdge; o<<"size: "<=m_area.MinEdge.Y; y--) { if(em.X >= 3 && em.Y >= 3) { if (y==m_area.MinEdge.Y+2) o<<"^ "; else if(y==m_area.MinEdge.Y+1) o<<"| "; else if(y==m_area.MinEdge.Y+0) o<<"y x-> "; else o<<" "; } for(s32 z=m_area.MinEdge.Z; z<=m_area.MaxEdge.Z; z++) { for(s32 x=m_area.MinEdge.X; x<=m_area.MaxEdge.X; x++) { u8 f = m_flags[m_area.index(x,y,z)]; char c; if(f & VOXELFLAG_NOT_LOADED) c = 'N'; else if(f & VOXELFLAG_INEXISTENT) c = 'I'; else { c = 'X'; u8 m = m_data[m_area.index(x,y,z)].d; u8 pr = m_data[m_area.index(x,y,z)].param2; if(mode == VOXELPRINT_MATERIAL) { if(m <= 9) c = m + '0'; } else if(mode == VOXELPRINT_WATERPRESSURE) { if(m == CONTENT_WATER) { c = 'w'; if(pr <= 9) c = pr + '0'; } else if(liquid_replaces_content(m)) { c = ' '; } else { c = '#'; } } } o< & light_sources) { 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 }; emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1))); // Loop through 6 neighbors for(u16 i=0; i<6; i++) { // Get the position of the neighbor node v3s16 n2pos = p + dirs[i]; u32 n2i = m_area.index(n2pos); if(m_flags[n2i] & VOXELFLAG_INEXISTENT) continue; MapNode &n2 = m_data[n2i]; /* If the neighbor is dimmer than what was specified as oldlight (the light of the previous node) */ if(n2.getLight(bank) < oldlight) { /* And the neighbor is transparent and it has some light */ if(n2.light_propagates() && n2.getLight(bank) != 0) { /* Set light to 0 and add to queue */ u8 current_light = n2.getLight(bank); n2.setLight(bank, 0); unspreadLight(bank, n2pos, current_light, light_sources); /* Remove from light_sources if it is there NOTE: This doesn't happen nearly at all */ /*if(light_sources.find(n2pos)) { std::cout<<"Removed from light_sources"< & from_nodes, core::map & light_sources) { if(from_nodes.size() == 0) return; core::map::Iterator j; j = from_nodes.getIterator(); for(; j.atEnd() == false; j++) { v3s16 pos = j.getNode()->getKey(); //MapNode &n = m_data[m_area.index(pos)]; u8 oldlight = j.getNode()->getValue(); unspreadLight(bank, pos, oldlight, light_sources); } } #endif #if 0 /* 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 VoxelManipulator::unspreadLight(enum LightBank bank, core::map & from_nodes, core::map & light_sources) { 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.size() == 0) return; core::map unlighted_nodes; core::map::Iterator j; j = from_nodes.getIterator(); for(; j.atEnd() == false; j++) { v3s16 pos = j.getNode()->getKey(); emerge(VoxelArea(pos - v3s16(1,1,1), pos + v3s16(1,1,1))); //MapNode &n = m_data[m_area.index(pos)]; u8 oldlight = j.getNode()->getValue(); // Loop through 6 neighbors for(u16 i=0; i<6; i++) { // Get the position of the neighbor node v3s16 n2pos = pos + dirs[i]; u32 n2i = m_area.index(n2pos); if(m_flags[n2i] & VOXELFLAG_INEXISTENT) continue; MapNode &n2 = m_data[n2i]; /* If the neighbor is dimmer than what was specified as oldlight (the light of the previous node) */ if(n2.getLight(bank) < oldlight) { /* And the neighbor is transparent and it has some light */ if(n2.light_propagates() && n2.getLight(bank) != 0) { /* Set light to 0 and add to queue */ u8 current_light = n2.getLight(bank); n2.setLight(bank, 0); unlighted_nodes.insert(n2pos, current_light); /* Remove from light_sources if it is there NOTE: This doesn't happen nearly at all */ /*if(light_sources.find(n2pos)) { std::cout<<"Removed from light_sources"< 0) unspreadLight(bank, unlighted_nodes, light_sources); } #endif void VoxelManipulator::spreadLight(enum LightBank bank, v3s16 p) { 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 }; emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1))); u32 i = m_area.index(p); if(m_flags[i] & VOXELFLAG_INEXISTENT) return; MapNode &n = m_data[i]; u8 oldlight = n.getLight(bank); 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 = p + dirs[i]; u32 n2i = m_area.index(n2pos); if(m_flags[n2i] & VOXELFLAG_INEXISTENT) continue; MapNode &n2 = m_data[n2i]; /* 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) > undiminish_light(oldlight)) { spreadLight(bank, n2pos); } /* If the neighbor is dimmer than how much light this node would spread on it, add to list */ if(n2.getLight(bank) < newlight) { if(n2.light_propagates()) { n2.setLight(bank, newlight); spreadLight(bank, n2pos); } } } } #if 1 /* Lights neighbors of from_nodes, collects all them and then goes on recursively. */ void VoxelManipulator::spreadLight(enum LightBank bank, core::map & from_nodes) { if(from_nodes.size() == 0) return; core::map lighted_nodes; core::map::Iterator j; j = from_nodes.getIterator(); for(; j.atEnd() == false; j++) { v3s16 pos = j.getNode()->getKey(); spreadLight(bank, pos); } } #endif #if 0 /* Lights neighbors of from_nodes, collects all them and then goes on recursively. */ void VoxelManipulator::spreadLight(enum LightBank bank, core::map & from_nodes) { 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.size() == 0) return; core::map lighted_nodes; core::map::Iterator j; j = from_nodes.getIterator(); for(; j.atEnd() == false; j++) { v3s16 pos = j.getNode()->getKey(); emerge(VoxelArea(pos - v3s16(1,1,1), pos + v3s16(1,1,1))); u32 i = m_area.index(pos); if(m_flags[i] & VOXELFLAG_INEXISTENT) continue; MapNode &n = m_data[i]; u8 oldlight = n.getLight(bank); 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]; try { u32 n2i = m_area.index(n2pos); if(m_flags[n2i] & VOXELFLAG_INEXISTENT) continue; MapNode &n2 = m_data[n2i]; /* 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) > undiminish_light(oldlight)) { lighted_nodes.insert(n2pos, true); } /* If the neighbor is dimmer than how much light this node would spread on it, add to list */ if(n2.getLight(bank) < newlight) { if(n2.light_propagates()) { n2.setLight(bank, newlight); lighted_nodes.insert(n2pos, true); } } } catch(InvalidPositionException &e) { continue; } } } /*dstream<<"spreadLight(): Changed block " < 0) spreadLight(bank, lighted_nodes); } #endif #if 0 int VoxelManipulator::getWaterPressure(v3s16 p, s16 &highest_y, int recur_count) { m_flags[m_area.index(p)] |= VOXELFLAG_CHECKED2; if(p.Y > highest_y) highest_y = p.Y; /*if(recur_count > 1000) throw ProcessingLimitException ("getWaterPressure recur_count limit reached");*/ if(recur_count > 10000) return -1; recur_count++; v3s16 dirs[6] = { v3s16(0,1,0), // top v3s16(0,0,1), // back v3s16(0,0,-1), // front v3s16(1,0,0), // right v3s16(-1,0,0), // left v3s16(0,-1,0), // bottom }; // Load neighboring nodes emerge(VoxelArea(p - v3s16(1,1,1), p + v3s16(1,1,1)), 1); s32 i; for(i=0; i<6; i++) { v3s16 p2 = p + dirs[i]; u8 f = m_flags[m_area.index(p2)]; // Ignore inexistent or checked nodes if(f & (VOXELFLAG_INEXISTENT | VOXELFLAG_CHECKED2)) continue; MapNode &n = m_data[m_area.index(p2)]; // Ignore non-liquid nodes if(content_liquid(n.d) == false) continue; int pr; // If at ocean surface if(n.pressure == 1 && n.d == CONTENT_WATERSOURCE) //if(n.pressure == 1) // Causes glitches but is fast { pr = 1; } // Otherwise recurse more else { pr = getWaterPressure(p2, highest_y, recur_count); if(pr == -1) continue; } // If block is at top, pressure here is one higher if(i == 0) { if(pr < 255) pr++; } // If block is at bottom, pressure here is one lower else if(i == 5) { if(pr > 1) pr--; } // Node is on the pressure route m_flags[m_area.index(p)] |= VOXELFLAG_CHECKED4; // Got pressure return pr; } // Nothing useful found return -1; } void VoxelManipulator::spreadWaterPressure(v3s16 p, int pr, VoxelArea request_area, core::map &active_nodes, int recur_count) { //if(recur_count > 10000) /*throw ProcessingLimitException ("spreadWaterPressure recur_count limit reached");*/ if(recur_count > 10) return; recur_count++; /*dstream<<"spreadWaterPressure: p=(" < 255) pr = 255; /*dstream<<"WARNING: Pressure at (" < &active_nodes, int recursion_depth, bool debugprint, u32 stoptime) { v3s16 dirs[6] = { v3s16(0,1,0), // top v3s16(0,0,-1), // front v3s16(0,0,1), // back v3s16(-1,0,0), // left v3s16(1,0,0), // right v3s16(0,-1,0), // bottom }; recursion_depth++; v3s16 p; bool from_ocean = false; // Randomize horizontal order static s32 cs = 0; if(cs < 3) cs++; else cs = 0; s16 s1 = (cs & 1) ? 1 : -1; s16 s2 = (cs & 2) ? 1 : -1; //dstream<<"s1="<= PRESERVE_WATER_VOLUME ? 3 : 2) if(n.pressure >= 3) break; continue; } // Else block is at some side. Select it if it has enough pressure //if(n.pressure >= PRESERVE_WATER_VOLUME ? 2 : 1) if(n.pressure >= 2) { break; } } // If there is nothing to move, return if(i==6) return false; /* Move water and bubble */ u8 m = m_data[m_area.index(p)].d; u8 f = m_flags[m_area.index(p)]; if(m == CONTENT_WATERSOURCE) from_ocean = true; // Move air bubble if not taking water from ocean if(from_ocean == false) { m_data[m_area.index(p)].d = m_data[m_area.index(removed_pos)].d; m_flags[m_area.index(p)] = m_flags[m_area.index(removed_pos)]; } /* This has to be done to copy the brightness of a light source correctly. Otherwise unspreadLight will fuck up when water has replaced a light source. */ u8 light = m_data[m_area.index(removed_pos)].getLightBanksWithSource(); m_data[m_area.index(removed_pos)].d = m; m_flags[m_area.index(removed_pos)] = f; m_data[m_area.index(removed_pos)].setLightBanks(light); // Mark removed_pos checked m_flags[m_area.index(removed_pos)] |= VOXELFLAG_CHECKED; // If block was dropped from surface, increase pressure if(i == 0 && m_data[m_area.index(removed_pos)].pressure == 1) { m_data[m_area.index(removed_pos)].pressure = 2; } /* NOTE: This does not work as-is if(m == CONTENT_WATERSOURCE) { // If block was raised to surface, increase pressure of // source node if(i == 5 && m_data[m_area.index(p)].pressure == 1) { m_data[m_area.index(p)].pressure = 2; } }*/ /*if(debugprint) { dstream<<"VoxelManipulator::flowWater(): Moved bubble:"<= stoptime || overflow) { dstream<<"flowWater: stoptime reached"<=0; i--) { // Don't try to flow to top if(m_disable_water_climb && i == 0) continue; //v3s16 p = removed_pos + dirs[i]; p = removed_pos + v3s16(s1*dirs[i].X, dirs[i].Y, s2*dirs[i].Z); u8 f = m_flags[m_area.index(p)]; // Water can't move to inexistent nodes if(f & VOXELFLAG_INEXISTENT) continue; MapNode &n = m_data[m_area.index(p)]; // Water can only move to air if(liquid_replaces_content(n.d) == false) continue; // Flow water to node bool moved = flowWater(p, active_nodes, recursion_depth, debugprint, stoptime); /*flowWater(p, active_nodes, recursion_depth, debugprint, counter, counterlimit);*/ if(moved) { // Search again from all neighbors goto find_again; } } return true; } void VoxelManipulator::flowWater( core::map &active_nodes, int recursion_depth, bool debugprint, u32 timelimit) { addarea_time = 0; emerge_time = 0; emerge_load_time = 0; clearflag_time = 0; updateareawaterpressure_time = 0; flowwater_pre_time = 0; if(active_nodes.size() == 0) { dstream<<"flowWater: no active nodes"<::Node *n = active_nodes.getIterator().getNode(); #endif #if 1 core::map::Iterator i = active_nodes.getIterator().getNode(); for(s32 j=0; j::Node *n = i.getNode(); // Decrement index if less than 0. // This keeps us in existing indices always. if(k > 0) k--; #endif v3s16 p = n->getKey(); active_nodes.remove(p); flowWater(p, active_nodes, recursion_depth, debugprint, stoptime); } } catch(ProcessingLimitException &e) { //dstream<<"getWaterPressure ProcessingLimitException"<