summaryrefslogtreecommitdiff
path: root/src/map.cpp
diff options
context:
space:
mode:
authorPerttu Ahola <celeron55@gmail.com>2010-11-27 01:02:21 +0200
committerPerttu Ahola <celeron55@gmail.com>2010-11-27 01:02:21 +0200
commit4e249fb3fbf75f0359758760d88e22aa5b14533c (patch)
tree323087d05efbd2ace27b316d4f017cf812a31992 /src/map.cpp
downloadminetest-4e249fb3fbf75f0359758760d88e22aa5b14533c.tar.gz
minetest-4e249fb3fbf75f0359758760d88e22aa5b14533c.tar.bz2
minetest-4e249fb3fbf75f0359758760d88e22aa5b14533c.zip
Initial files
Diffstat (limited to 'src/map.cpp')
-rw-r--r--src/map.cpp2854
1 files changed, 2854 insertions, 0 deletions
diff --git a/src/map.cpp b/src/map.cpp
new file mode 100644
index 000000000..c69c3f248
--- /dev/null
+++ b/src/map.cpp
@@ -0,0 +1,2854 @@
+/*
+(c) 2010 Perttu Ahola <celeron55@gmail.com>
+*/
+
+#include "map.h"
+//#include "player.h"
+#include "main.h"
+#include "jmutexautolock.h"
+#include "client.h"
+#include "filesys.h"
+#include "utility.h"
+
+#ifdef _WIN32
+ #include <windows.h>
+ #define sleep_ms(x) Sleep(x)
+#else
+ #include <unistd.h>
+ #define sleep_ms(x) usleep(x*1000)
+#endif
+
+Map::Map(std::ostream &dout):
+ m_dout(dout),
+ m_camera_position(0,0,0),
+ m_camera_direction(0,0,1),
+ m_sector_cache(NULL),
+ m_hwrapper(this),
+ drawoffset(0,0,0)
+{
+ m_sector_mutex.Init();
+ m_camera_mutex.Init();
+ assert(m_sector_mutex.IsInitialized());
+ assert(m_camera_mutex.IsInitialized());
+
+ // Get this so that the player can stay on it at first
+ //getSector(v2s16(0,0));
+}
+
+Map::~Map()
+{
+ /*
+ Stop updater thread
+ */
+ /*updater.setRun(false);
+ while(updater.IsRunning())
+ sleep_s(1);*/
+
+ /*
+ Free all MapSectors.
+ */
+ core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapSector *sector = i.getNode()->getValue();
+ delete sector;
+ }
+}
+
+/*bool Map::sectorExists(v2s16 p)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+ core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
+ return (n != NULL);
+}*/
+
+MapSector * Map::getSectorNoGenerate(v2s16 p)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+
+ if(m_sector_cache != NULL && p == m_sector_cache_p){
+ MapSector * sector = m_sector_cache;
+ // Reset inactivity timer
+ sector->usage_timer = 0.0;
+ return sector;
+ }
+
+ core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p);
+ // If sector doesn't exist, throw an exception
+ if(n == NULL)
+ {
+ throw InvalidPositionException();
+ }
+
+ MapSector *sector = n->getValue();
+
+ // Cache the last result
+ m_sector_cache_p = p;
+ m_sector_cache = sector;
+
+ //MapSector * ref(sector);
+
+ // Reset inactivity timer
+ sector->usage_timer = 0.0;
+ return sector;
+}
+
+MapBlock * Map::getBlockNoCreate(v3s16 p3d)
+{
+ v2s16 p2d(p3d.X, p3d.Z);
+ MapSector * sector = getSectorNoGenerate(p2d);
+
+ MapBlock *block = sector->getBlockNoCreate(p3d.Y);
+
+ return block;
+}
+
+/*MapBlock * Map::getBlock(v3s16 p3d, bool generate)
+{
+ dstream<<"Map::getBlock() with generate=true called"
+ <<std::endl;
+ v2s16 p2d(p3d.X, p3d.Z);
+ //MapSector * sector = getSector(p2d, generate);
+ MapSector * sector = getSectorNoGenerate(p2d);
+
+ if(sector == NULL)
+ throw InvalidPositionException();
+
+ return sector->getBlockNoCreate(p3d.Y);
+}*/
+
+f32 Map::getGroundHeight(v2s16 p, bool generate)
+{
+ try{
+ v2s16 sectorpos = getNodeSectorPos(p);
+ MapSector * sref = getSectorNoGenerate(sectorpos);
+ v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
+ f32 y = sref->getGroundHeight(relpos);
+ return y;
+ }
+ catch(InvalidPositionException &e)
+ {
+ return GROUNDHEIGHT_NOTFOUND_SETVALUE;
+ }
+}
+
+void Map::setGroundHeight(v2s16 p, f32 y, bool generate)
+{
+ /*m_dout<<DTIME<<"Map::setGroundHeight(("
+ <<p.X<<","<<p.Y
+ <<"), "<<y<<")"<<std::endl;*/
+ v2s16 sectorpos = getNodeSectorPos(p);
+ MapSector * sref = getSectorNoGenerate(sectorpos);
+ v2s16 relpos = p - sectorpos * MAP_BLOCKSIZE;
+ //sref->mutex.Lock();
+ sref->setGroundHeight(relpos, y);
+ //sref->mutex.Unlock();
+}
+
+bool Map::isNodeUnderground(v3s16 p)
+{
+ v3s16 blockpos = getNodeBlockPos(p);
+ try{
+ MapBlock * block = getBlockNoCreate(blockpos);
+ return block->getIsUnderground();
+ }
+ catch(InvalidPositionException &e)
+ {
+ return false;
+ }
+}
+
+#ifdef LKJnb
+//TODO: Remove: Not used.
+/*
+ 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.
+
+ Some things are made strangely to make it as fast as possible.
+
+ Usage: (for clearing all possible spreaded light of a lamp)
+ NOTE: This is outdated
+ core::list<v3s16> light_sources;
+ core::map<v3s16, MapBlock*> modified_blocks;
+ u8 oldlight = node_at_pos.light;
+ node_at_pos.setLight(0);
+ unLightNeighbors(pos, oldlight, light_sources, modified_blocks);
+*/
+void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & 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
+ };
+
+ /*
+ Initialize block cache
+ */
+ v3s16 blockpos_last;
+ MapBlock *block = NULL;
+ // Cache this a bit, too
+ bool block_checked_in_modified = false;
+
+ // 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 = getNodeBlockPos(n2pos);
+
+ // 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 = n2pos - blockpos * MAP_BLOCKSIZE;
+ // Get node straight from the block
+ MapNode n2 = block->getNode(relpos);
+
+ /*
+ If the neighbor is dimmer than what was specified
+ as oldlight (the light of the previous node)
+ */
+ if(n2.getLight() < oldlight)
+ {
+ /*
+ And the neighbor is transparent and it has some light
+ */
+ if(n2.light_propagates() && n2.getLight() != 0)
+ {
+ /*
+ Set light to 0 and recurse.
+ */
+ u8 current_light = n2.getLight();
+ n2.setLight(0);
+ block->setNode(relpos, n2);
+ unLightNeighbors(n2pos, current_light,
+ light_sources, modified_blocks);
+
+ if(block_checked_in_modified == false)
+ {
+ // If the block is not found in modified_blocks, add.
+ if(modified_blocks.find(blockpos) == NULL)
+ {
+ modified_blocks.insert(blockpos, block);
+ }
+ block_checked_in_modified = true;
+ }
+ }
+ }
+ else{
+ //light_sources.push_back(n2pos);
+ light_sources.insert(n2pos, true);
+ }
+ }
+}
+#endif
+
+/*
+ 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(core::map<v3s16, u8> & from_nodes,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & 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.size() == 0)
+ return;
+
+ u32 blockchangecount = 0;
+
+ core::map<v3s16, u8> unlighted_nodes;
+ core::map<v3s16, u8>::Iterator j;
+ j = from_nodes.getIterator();
+
+ /*
+ Initialize block cache
+ */
+ v3s16 blockpos_last;
+ MapBlock *block = NULL;
+ // Cache this a bit, too
+ bool block_checked_in_modified = false;
+
+ for(; j.atEnd() == false; j++)
+ {
+ v3s16 pos = j.getNode()->getKey();
+ 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.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];
+
+ // Get the block where the node is located
+ v3s16 blockpos = getNodeBlockPos(n2pos);
+
+ try
+ {
+ // 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;
+ }
+
+ // Calculate relative position in block
+ v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+ // Get node straight from the block
+ MapNode n2 = block->getNode(relpos);
+
+ 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() < oldlight)
+ {
+ /*
+ And the neighbor is transparent and it has some light
+ */
+ if(n2.light_propagates() && n2.getLight() != 0)
+ {
+ /*
+ Set light to 0 and add to queue
+ */
+
+ u8 current_light = n2.getLight();
+ n2.setLight(0);
+ block->setNode(relpos, n2);
+
+ unlighted_nodes.insert(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))
+ {
+ std::cout<<"Removed from light_sources"<<std::endl;
+ light_sources.remove(n2pos);
+ }*/
+ }
+ }
+ else{
+ light_sources.insert(n2pos, 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) == NULL)
+ {
+ modified_blocks.insert(blockpos, block);
+ }
+ block_checked_in_modified = true;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+ }
+ }
+
+ /*dstream<<"unspreadLight(): Changed block "
+ <<blockchangecount<<" times"
+ <<" for "<<from_nodes.size()<<" nodes"
+ <<std::endl;*/
+
+ if(unlighted_nodes.size() > 0)
+ unspreadLight(unlighted_nodes, light_sources, modified_blocks);
+}
+
+/*
+ A single-node wrapper of the above
+*/
+void Map::unLightNeighbors(v3s16 pos, u8 lightwas,
+ core::map<v3s16, bool> & light_sources,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ core::map<v3s16, u8> from_nodes;
+ from_nodes.insert(pos, lightwas);
+
+ unspreadLight(from_nodes, light_sources, modified_blocks);
+}
+
+/*
+ Lights neighbors of from_nodes, collects all them and then
+ goes on recursively.
+*/
+void Map::spreadLight(core::map<v3s16, bool> & from_nodes,
+ core::map<v3s16, MapBlock*> & 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.size() == 0)
+ return;
+
+ u32 blockchangecount = 0;
+
+ core::map<v3s16, bool> lighted_nodes;
+ core::map<v3s16, bool>::Iterator j;
+ j = from_nodes.getIterator();
+
+ /*
+ Initialize block cache
+ */
+ v3s16 blockpos_last;
+ MapBlock *block = NULL;
+ // Cache this a bit, too
+ bool block_checked_in_modified = false;
+
+ for(; j.atEnd() == false; j++)
+ //for(; j != from_nodes.end(); j++)
+ {
+ v3s16 pos = j.getNode()->getKey();
+ //v3s16 pos = *j;
+ //dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
+ 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 = n.getLight();
+ 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 = getNodeBlockPos(n2pos);
+
+ try
+ {
+ // 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;
+ }
+
+ // Calculate relative position in block
+ v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
+ // Get node straight from the block
+ MapNode n2 = block->getNode(relpos);
+
+ 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() > undiminish_light(oldlight))
+ {
+ lighted_nodes.insert(n2pos, true);
+ //lighted_nodes.push_back(n2pos);
+ changed = true;
+ }
+ /*
+ If the neighbor is dimmer than how much light this node
+ would spread on it, add to list
+ */
+ if(n2.getLight() < newlight)
+ {
+ if(n2.light_propagates())
+ {
+ n2.setLight(newlight);
+ block->setNode(relpos, n2);
+ lighted_nodes.insert(n2pos, true);
+ //lighted_nodes.push_back(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) == NULL)
+ {
+ modified_blocks.insert(blockpos, block);
+ }
+ block_checked_in_modified = true;
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+ }
+ }
+
+ /*dstream<<"spreadLight(): Changed block "
+ <<blockchangecount<<" times"
+ <<" for "<<from_nodes.size()<<" nodes"
+ <<std::endl;*/
+
+ if(lighted_nodes.size() > 0)
+ spreadLight(lighted_nodes, modified_blocks);
+}
+
+/*
+ A single-node source variation of the above.
+*/
+void Map::lightNeighbors(v3s16 pos,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ core::map<v3s16, bool> from_nodes;
+ from_nodes.insert(pos, true);
+ spreadLight(from_nodes, modified_blocks);
+}
+
+v3s16 Map::getBrightestNeighbour(v3s16 p)
+{
+ 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
+ };
+
+ u8 brightest_light = 0;
+ v3s16 brightest_pos(0,0,0);
+ bool found_something = false;
+
+ // Loop through 6 neighbors
+ for(u16 i=0; i<6; i++){
+ // Get the position of the neighbor node
+ v3s16 n2pos = p + dirs[i];
+ MapNode n2;
+ try{
+ n2 = getNode(n2pos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ continue;
+ }
+ if(n2.getLight() > brightest_light || found_something == false){
+ brightest_light = n2.getLight();
+ brightest_pos = n2pos;
+ found_something = true;
+ }
+ }
+
+ if(found_something == false)
+ throw InvalidPositionException();
+
+ return brightest_pos;
+}
+
+/*
+ Propagates sunlight down from a node.
+ Starting point gets sunlight.
+
+ Returns the lowest y value of where the sunlight went.
+*/
+s16 Map::propagateSunlight(v3s16 start,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ s16 y = start.Y;
+ for(; ; y--)
+ {
+ v3s16 pos(start.X, y, start.Z);
+
+ v3s16 blockpos = getNodeBlockPos(pos);
+ MapBlock *block;
+ try{
+ block = getBlockNoCreate(blockpos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ break;
+ }
+
+ v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE;
+ MapNode n = block->getNode(relpos);
+
+ if(n.sunlight_propagates())
+ {
+ n.setLight(LIGHT_SUN);
+ block->setNode(relpos, n);
+
+ modified_blocks.insert(blockpos, block);
+ }
+ else{
+ break;
+ }
+ }
+ return y + 1;
+}
+
+void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
+ core::map<v3s16, MapBlock*> & modified_blocks)
+{
+ /*m_dout<<DTIME<<"Map::updateLighting(): "
+ <<a_blocks.getSize()<<" blocks... ";*/
+
+ // For debugging
+ bool debug=false;
+ u32 count_was = modified_blocks.size();
+
+ /*core::list<MapBlock *>::Iterator i = a_blocks.begin();
+ for(; i != a_blocks.end(); i++)
+ {
+ MapBlock *block = *i;*/
+
+ core::map<v3s16, bool> light_sources;
+
+ core::map<v3s16, u8> unlight_from;
+
+ core::map<v3s16, MapBlock*>::Iterator i;
+ i = a_blocks.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+
+ for(;;)
+ {
+ // Don't bother with dummy blocks.
+ if(block->isDummy())
+ break;
+
+ v3s16 pos = block->getPos();
+ modified_blocks.insert(pos, block);
+
+ /*
+ Clear all light from block
+ */
+ for(s16 z=0; z<MAP_BLOCKSIZE; z++)
+ for(s16 x=0; x<MAP_BLOCKSIZE; x++)
+ for(s16 y=0; y<MAP_BLOCKSIZE; y++)
+ {
+
+ try{
+ v3s16 p(x,y,z);
+ MapNode n = block->getNode(v3s16(x,y,z));
+ u8 oldlight = n.getLight();
+ n.setLight(0);
+ block->setNode(v3s16(x,y,z), n);
+
+ // Collect borders for unlighting
+ if(x==0 || x == MAP_BLOCKSIZE-1
+ || y==0 || y == MAP_BLOCKSIZE-1
+ || z==0 || z == MAP_BLOCKSIZE-1)
+ {
+ v3s16 p_map = p + v3s16(
+ MAP_BLOCKSIZE*pos.X,
+ MAP_BLOCKSIZE*pos.Y,
+ MAP_BLOCKSIZE*pos.Z);
+ unlight_from.insert(p_map, oldlight);
+ }
+ }
+ catch(InvalidPositionException &e)
+ {
+ /*
+ This would happen when dealing with a
+ dummy block.
+ */
+ //assert(0);
+ dstream<<"updateLighting(): InvalidPositionException"
+ <<std::endl;
+ }
+ }
+
+ bool bottom_valid = block->propagateSunlight(light_sources);
+
+ // If bottom is valid, we're done.
+ if(bottom_valid)
+ break;
+
+ /*dstream<<"Bottom for sunlight-propagated block ("
+ <<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
+ <<std::endl;*/
+
+ // Else get the block below and loop to it
+
+ pos.Y--;
+ try{
+ block = getBlockNoCreate(pos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ assert(0);
+ }
+
+ }
+ }
+
+ {
+ //TimeTaker timer("unspreadLight", g_device);
+ unspreadLight(unlight_from, light_sources, modified_blocks);
+ }
+
+ if(debug)
+ {
+ u32 diff = modified_blocks.size() - count_was;
+ count_was = modified_blocks.size();
+ dstream<<"unspreadLight modified "<<diff<<std::endl;
+ }
+
+ // TODO: Spread light from propagated sunlight?
+ // Yes, add it to light_sources... somehow.
+ // It has to be added at somewhere above, in the loop.
+ // TODO
+
+ {
+ //TimeTaker timer("spreadLight", g_device);
+ spreadLight(light_sources, modified_blocks);
+ }
+
+ if(debug)
+ {
+ u32 diff = modified_blocks.size() - count_was;
+ count_was = modified_blocks.size();
+ dstream<<"spreadLight modified "<<diff<<std::endl;
+ }
+
+ //m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
+}
+
+/*
+ This is called after changing a node from transparent to opaque.
+ The lighting value of the node should be left as-is after changing
+ other values. This sets the lighting value to 0.
+*/
+/*void Map::nodeAddedUpdate(v3s16 p, u8 lightwas,
+ core::map<v3s16, MapBlock*> &modified_blocks)*/
+void Map::addNodeAndUpdate(v3s16 p, MapNode n,
+ core::map<v3s16, MapBlock*> &modified_blocks)
+{
+ /*PrintInfo(m_dout);
+ m_dout<<DTIME<<"Map::nodeAddedUpdate(): p=("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+ u8 lightwas = getNode(p).getLight();
+
+ //core::list<v3s16> light_sources;
+ core::map<v3s16, bool> light_sources;
+ //MapNode n = getNode(p);
+
+ /*
+ From this node to nodes underneath:
+ If lighting is sunlight (1.0), unlight neighbours and
+ set lighting to 0.
+ Else discontinue.
+ */
+
+ bool node_under_sunlight = true;
+
+ v3s16 toppos = p + v3s16(0,1,0);
+
+ /*
+ If there is a node at top and it doesn't have sunlight,
+ there has not been any sunlight going down.
+
+ Otherwise there probably is.
+ */
+ try{
+ MapNode topnode = getNode(toppos);
+
+ if(topnode.getLight() != LIGHT_SUN)
+ node_under_sunlight = false;
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ // Add the block of the added node to modified_blocks
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * block = getBlockNoCreate(blockpos);
+ assert(block != NULL);
+ modified_blocks.insert(blockpos, block);
+
+ if(isValidPosition(p) == false)
+ throw;
+
+ // Unlight neighbours of node.
+ // This means setting light of all consequent dimmer nodes
+ // to 0.
+ // This also collects the nodes at the border which will spread
+ // light again into this.
+ unLightNeighbors(p, lightwas, light_sources, modified_blocks);
+
+ n.setLight(0);
+ setNode(p, n);
+
+ /*
+ If node is under sunlight, take all sunlighted nodes under
+ it and clear light from them and from where the light has
+ been spread.
+ */
+ if(node_under_sunlight)
+ {
+ s16 y = p.Y - 1;
+ for(;; y--){
+ //m_dout<<DTIME<<"y="<<y<<std::endl;
+ v3s16 n2pos(p.X, y, p.Z);
+
+ MapNode n2;
+ try{
+ n2 = getNode(n2pos);
+ }
+ catch(InvalidPositionException &e)
+ {
+ break;
+ }
+
+ if(n2.getLight() == LIGHT_SUN)
+ {
+ //m_dout<<DTIME<<"doing"<<std::endl;
+ unLightNeighbors(n2pos, n2.getLight(), light_sources, modified_blocks);
+ n2.setLight(0);
+ setNode(n2pos, n2);
+ }
+ else
+ break;
+ }
+ }
+
+ /*
+ Spread light from all nodes that might be capable of doing so
+ TODO: Convert to spreadLight
+ */
+ spreadLight(light_sources, modified_blocks);
+}
+
+/*
+*/
+void Map::removeNodeAndUpdate(v3s16 p,
+ core::map<v3s16, MapBlock*> &modified_blocks)
+{
+ /*PrintInfo(m_dout);
+ m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
+ <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+
+ bool node_under_sunlight = true;
+
+ v3s16 toppos = p + v3s16(0,1,0);
+
+ /*
+ If there is a node at top and it doesn't have sunlight,
+ there will be no sunlight going down.
+ */
+ try{
+ MapNode topnode = getNode(toppos);
+
+ if(topnode.getLight() != LIGHT_SUN)
+ node_under_sunlight = false;
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ /*
+ Unlight neighbors (in case the node is a light source)
+ */
+ //core::list<v3s16> light_sources;
+ core::map<v3s16, bool> light_sources;
+ unLightNeighbors(p, getNode(p).getLight(),
+ light_sources, modified_blocks);
+
+ /*
+ Remove the node
+ */
+ MapNode n;
+ n.d = MATERIAL_AIR;
+ n.setLight(0);
+ setNode(p, n);
+
+ /*
+ Recalculate lighting
+ */
+ spreadLight(light_sources, modified_blocks);
+
+ // Add the block of the removed node to modified_blocks
+ v3s16 blockpos = getNodeBlockPos(p);
+ MapBlock * block = getBlockNoCreate(blockpos);
+ assert(block != NULL);
+ modified_blocks.insert(blockpos, block);
+
+ /*
+ If the removed node was under sunlight, propagate the
+ sunlight down from it and then light all neighbors
+ of the propagated blocks.
+ */
+ if(node_under_sunlight)
+ {
+ s16 ybottom = propagateSunlight(p, modified_blocks);
+ /*m_dout<<DTIME<<"Node was under sunlight. "
+ "Propagating sunlight";
+ m_dout<<DTIME<<" -> ybottom="<<ybottom<<std::endl;*/
+ s16 y = p.Y;
+ for(; y >= ybottom; y--)
+ {
+ v3s16 p2(p.X, y, p.Z);
+ /*m_dout<<DTIME<<"lighting neighbors of node ("
+ <<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
+ <<std::endl;*/
+ lightNeighbors(p2, modified_blocks);
+ }
+ }
+ else
+ {
+ // Set the lighting of this node to 0
+ try{
+ MapNode n = getNode(p);
+ n.setLight(0);
+ setNode(p, n);
+ }
+ catch(InvalidPositionException &e)
+ {
+ throw;
+ }
+ }
+
+ // Get the brightest neighbour node and propagate light from it
+ v3s16 n2p = getBrightestNeighbour(p);
+ try{
+ MapNode n2 = getNode(n2p);
+ lightNeighbors(n2p, modified_blocks);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+}
+
+void Map::updateMeshes(v3s16 blockpos)
+{
+ assert(mapType() == MAPTYPE_CLIENT);
+
+ try{
+ v3s16 p = blockpos + v3s16(0,0,0);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+ try{
+ v3s16 p = blockpos + v3s16(-1,0,0);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+ try{
+ v3s16 p = blockpos + v3s16(0,-1,0);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+ try{
+ v3s16 p = blockpos + v3s16(0,0,-1);
+ MapBlock *b = getBlockNoCreate(p);
+ b->updateMesh();
+ }
+ catch(InvalidPositionException &e){}
+}
+
+/*
+ Updates usage timers
+*/
+void Map::timerUpdate(float dtime)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::map<v2s16, MapSector*>::Iterator si;
+
+ si = m_sectors.getIterator();
+ for(; si.atEnd() == false; si++)
+ {
+ MapSector *sector = si.getNode()->getValue();
+ sector->usage_timer += dtime;
+ }
+}
+
+void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
+{
+ core::list<v2s16>::Iterator j;
+ for(j=list.begin(); j!=list.end(); j++)
+ {
+ MapSector *sector = m_sectors[*j];
+ if(only_blocks)
+ {
+ sector->deleteBlocks();
+ }
+ else
+ {
+ /*
+ If sector is in sector cache, remove it from there
+ */
+ if(m_sector_cache == sector)
+ {
+ m_sector_cache = NULL;
+ }
+ /*
+ Remove from map and delete
+ */
+ m_sectors.remove(*j);
+ delete sector;
+ }
+ }
+}
+
+u32 Map::deleteUnusedSectors(float timeout, bool only_blocks,
+ core::list<v3s16> *deleted_blocks)
+{
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::list<v2s16> sector_deletion_queue;
+ core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapSector *sector = i.getNode()->getValue();
+ /*
+ Delete sector from memory if it hasn't been used in a long time
+ */
+ if(sector->usage_timer > timeout)
+ {
+ sector_deletion_queue.push_back(i.getNode()->getKey());
+
+ if(deleted_blocks != NULL)
+ {
+ // Collect positions of blocks of sector
+ MapSector *sector = i.getNode()->getValue();
+ core::list<MapBlock*> blocks;
+ sector->getBlocks(blocks);
+ for(core::list<MapBlock*>::Iterator i = blocks.begin();
+ i != blocks.end(); i++)
+ {
+ deleted_blocks->push_back((*i)->getPos());
+ }
+ }
+ }
+ }
+ deleteSectors(sector_deletion_queue, only_blocks);
+ return sector_deletion_queue.getSize();
+}
+
+void Map::PrintInfo(std::ostream &out)
+{
+ out<<"Map: ";
+}
+
+/*
+ ServerMap
+*/
+
+ServerMap::ServerMap(std::string savedir, MapgenParams params):
+ Map(dout_server),
+ m_heightmap(NULL)
+{
+ m_savedir = savedir;
+ m_map_saving_enabled = false;
+
+ try
+ {
+ // If directory exists, check contents and load if possible
+ if(fs::PathExists(m_savedir))
+ {
+ // If directory is empty, it is safe to save into it.
+ if(fs::GetDirListing(m_savedir).size() == 0)
+ {
+ dstream<<DTIME<<"Server: Empty save directory is valid."
+ <<std::endl;
+ m_map_saving_enabled = true;
+ }
+ else
+ {
+ // Load master heightmap
+ loadMasterHeightmap();
+
+ // Load sector (0,0) and throw and exception on fail
+ if(loadSectorFull(v2s16(0,0)) == false)
+ throw LoadError("Failed to load sector (0,0)");
+
+ dstream<<DTIME<<"Server: Successfully loaded master "
+ "heightmap and sector (0,0) from "<<savedir<<
+ ", assuming valid save directory."
+ <<std::endl;
+
+ m_map_saving_enabled = true;
+ // Map loaded, not creating new one
+ return;
+ }
+ }
+ // If directory doesn't exist, it is safe to save to it
+ else{
+ m_map_saving_enabled = true;
+ }
+ }
+ catch(std::exception &e)
+ {
+ dstream<<DTIME<<"Server: Failed to load map from "<<savedir
+ <<", exception: "<<e.what()<<std::endl;
+ dstream<<DTIME<<"Please remove the map or fix it."<<std::endl;
+ dstream<<DTIME<<"WARNING: Map saving will be disabled."<<std::endl;
+ }
+
+ dstream<<DTIME<<"Initializing new map."<<std::endl;
+
+ ValueGenerator *maxgen =
+ ValueGenerator::deSerialize(params.height_randmax);
+ ValueGenerator *factorgen =
+ ValueGenerator::deSerialize(params.height_randfactor);
+ ValueGenerator *basegen =
+ ValueGenerator::deSerialize(params.height_base);
+ m_heightmap = new UnlimitedHeightmap
+ (params.heightmap_blocksize, maxgen, factorgen, basegen);
+
+ // Create zero sector
+ emergeSector(v2s16(0,0));
+
+ // Initially write whole map
+ save(false);
+}
+
+ServerMap::~ServerMap()
+{
+ try
+ {
+ if(m_map_saving_enabled)
+ {
+ //save(false);
+ // Save only changed parts
+ save(true);
+ dstream<<DTIME<<"Server: saved map to "<<m_savedir<<std::endl;
+ }
+ else
+ {
+ dstream<<DTIME<<"Server: map not saved"<<std::endl;
+ }
+ }
+ catch(std::exception &e)
+ {
+ dstream<<DTIME<<"Server: Failed to save map to "<<m_savedir
+ <<", exception: "<<e.what()<<std::endl;
+ }
+
+ if(m_heightmap != NULL)
+ delete m_heightmap;
+}
+
+MapSector * ServerMap::emergeSector(v2s16 p2d)
+{
+ DSTACK("%s: p2d=(%d,%d)",
+ __FUNCTION_NAME,
+ p2d.X, p2d.Y);
+ // Check that it doesn't exist already
+ try{
+ return getSectorNoGenerate(p2d);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ /*
+ Try to load the sector from disk.
+ */
+ if(loadSectorFull(p2d) == true)
+ {
+ return getSectorNoGenerate(p2d);
+ }
+
+ /*
+ If there is no master heightmap, throw.
+ */
+ if(m_heightmap == NULL)
+ {
+ throw InvalidPositionException("emergeSector(): no heightmap");
+ }
+
+ /*
+ Do not generate over-limit
+ */
+ if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
+ || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
+ throw InvalidPositionException("emergeSector(): pos. over limit");
+
+ /*
+ Generate sector and heightmaps
+ */
+
+ // Number of heightmaps in sector in each direction
+ u16 hm_split = SECTOR_HEIGHTMAP_SPLIT;
+
+ // Heightmap side width
+ s16 hm_d = MAP_BLOCKSIZE / hm_split;
+
+ ServerMapSector *sector = new ServerMapSector(this, p2d, hm_split);
+
+ /*dstream<<"Generating sector ("<<p2d.X<<","<<p2d.Y<<")"
+ " heightmaps and objects"<<std::endl;*/
+
+ // Loop through sub-heightmaps
+ for(s16 y=0; y<hm_split; y++)
+ for(s16 x=0; x<hm_split; x++)
+ {
+ v2s16 p_in_sector = v2s16(x,y);
+ v2s16 mhm_p = p2d * hm_split + p_in_sector;
+ f32 corners[4] = {
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)),
+ };
+
+ /*dstream<<"p_in_sector=("<<p_in_sector.X<<","<<p_in_sector.Y<<")"
+ <<" mhm_p=("<<mhm_p.X<<","<<mhm_p.Y<<")"
+ <<std::endl;*/
+
+ FixedHeightmap *hm = new FixedHeightmap(&m_hwrapper,
+ mhm_p, hm_d);
+ sector->setHeightmap(p_in_sector, hm);
+
+ //TODO: Make these values configurable
+ hm->generateContinued(1.0, 0.2, corners);
+ //hm->generateContinued(2.0, 0.2, corners);
+
+ //hm->print();
+
+ }
+
+ /*
+ Generate objects
+ */
+
+ core::map<v3s16, u8> *objects = new core::map<v3s16, u8>;
+ sector->setObjects(objects);
+
+ v2s16 mhm_p = p2d * hm_split;
+ f32 corners[4] = {
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,0)*hm_split),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,0)*hm_split),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(1,1)*hm_split),
+ m_heightmap->getGroundHeight(mhm_p+v2s16(0,1)*hm_split),
+ };
+
+ float avgheight = (corners[0]+corners[1]+corners[2]+corners[3])/4.0;
+ float avgslope = 0.0;
+ avgslope += fabs(avgheight - corners[0]);
+ avgslope += fabs(avgheight - corners[1]);
+ avgslope += fabs(avgheight - corners[2]);
+ avgslope += fabs(avgheight - corners[3]);
+ avgslope /= 4.0;
+ avgslope /= MAP_BLOCKSIZE;
+ //dstream<<"avgslope="<<avgslope<<std::endl;
+
+ float pitness = 0.0;
+ v2f32 a;
+ a = m_heightmap->getSlope(p2d+v2s16(0,0));
+ pitness += -a.X;
+ pitness += -a.Y;
+ a = m_heightmap->getSlope(p2d+v2s16(0,1));
+ pitness += -a.X;
+ pitness += a.Y;
+ a = m_heightmap->getSlope(p2d+v2s16(1,1));
+ pitness += a.X;
+ pitness += a.Y;
+ a = m_heightmap->getSlope(p2d+v2s16(1,0));
+ pitness += a.X;
+ pitness += -a.Y;
+ pitness /= 4.0;
+ pitness /= MAP_BLOCKSIZE;
+ //dstream<<"pitness="<<pitness<<std::endl;
+
+ /*
+ Plant some trees if there is not much slope
+ */
+ {
+ // Avgslope is the derivative of a hill
+ float t = avgslope * avgslope;
+ float a = MAP_BLOCKSIZE * 2;
+ u32 tree_max;
+ if(t > 0.03)
+ tree_max = a / (t/0.03);
+ else
+ tree_max = a;
+ u32 count = (rand()%(tree_max+1));
+ //u32 count = tree_max;
+ for(u32 i=0; i<count; i++)
+ {
+ s16 x = (rand()%(MAP_BLOCKSIZE-2))+1;
+ s16 z = (rand()%(MAP_BLOCKSIZE-2))+1;
+ s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+ if(y < WATER_LEVEL)
+ continue;
+ objects->insert(v3s16(x, y, z),
+ SECTOR_OBJECT_TREE_1);
+ }
+ }
+ {
+ // Pitness usually goes at around -0.5...0.5
+ u32 bush_max = 0;
+ u32 a = MAP_BLOCKSIZE * 3;
+ if(pitness > 0)
+ bush_max = (pitness*a*4);
+ if(bush_max > a)
+ bush_max = a;
+ u32 count = (rand()%(bush_max+1));
+ for(u32 i=0; i<count; i++)
+ {
+ s16 x = rand()%(MAP_BLOCKSIZE-0)+0;
+ s16 z = rand()%(MAP_BLOCKSIZE-0)+0;
+ s16 y = sector->getGroundHeight(v2s16(x,z))+1;
+ if(y < WATER_LEVEL)
+ continue;
+ objects->insert(v3s16(x, y, z),
+ SECTOR_OBJECT_BUSH_1);
+ }
+ }
+
+ /*
+ Insert to container
+ */
+ JMutexAutoLock lock(m_sector_mutex);
+ m_sectors.insert(p2d, sector);
+
+ return sector;
+}
+
+MapBlock * ServerMap::emergeBlock(
+ v3s16 p,
+ bool only_from_disk,
+ core::map<v3s16, MapBlock*> &changed_blocks,
+ core::map<v3s16, MapBlock*> &lighting_invalidated_blocks
+)
+{
+ DSTACK("%s: p=(%d,%d,%d), only_from_disk=%d",
+ __FUNCTION_NAME,
+ p.X, p.Y, p.Z, only_from_disk);
+
+ /*dstream<<"ServerMap::emergeBlock(): "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<", only_from_disk="<<only_from_disk<<std::endl;*/
+ v2s16 p2d(p.X, p.Z);
+ s16 block_y = p.Y;
+ /*
+ This will create or load a sector if not found in memory.
+ If block exists on disk, it will be loaded.
+
+ NOTE: On old save formats, this will be slow, as it generates
+ lighting on blocks for them.
+ */
+ ServerMapSector *sector = (ServerMapSector*)emergeSector(p2d);
+ assert(sector->getId() == MAPSECTOR_SERVER);
+
+ // Try to get a block from the sector
+ MapBlock *block = NULL;
+ bool not_on_disk = false;
+ try{
+ block = sector->getBlockNoCreate(block_y);
+ if(block->isDummy() == true)
+ not_on_disk = true;
+ else
+ return block;
+ }
+ catch(InvalidPositionException &e)
+ {
+ not_on_disk = true;
+ }
+
+ /*
+ If block was not found on disk and not going to generate a
+ new one, make sure there is a dummy block in place.
+ */
+ if(not_on_disk && only_from_disk)
+ {
+ if(block == NULL)
+ {
+ // Create dummy block
+ block = new MapBlock(this, p, true);
+
+ // Add block to sector
+ sector->insertBlock(block);
+ }
+ // Done.
+ return block;
+ }
+
+ //dstream<<"Not found on disk, generating."<<std::endl;
+
+ /*
+ Do not generate over-limit
+ */
+ if(blockpos_over_limit(p))
+ throw InvalidPositionException("emergeBlock(): pos. over limit");
+
+ /*
+ OK; Not found.
+
+ Go on generating the block.
+
+ TODO: If a dungeon gets generated so that it's side gets
+ revealed to the outside air, the lighting should be
+ recalculated.
+ */
+
+ /*
+ If block doesn't exist, create one.
+ If it exists, it is a dummy. In that case unDummify() it.
+ */
+ if(block == NULL)
+ {
+ block = sector->createBlankBlockNoInsert(block_y);
+ }
+ else
+ {
+ // Remove the block so that nobody can get a half-generated one.
+ sector->removeBlock(block);
+ // Allocate the block to be a proper one.
+ block->unDummify();
+ }
+
+ // Randomize a bit. This makes dungeons.
+ bool low_block_is_empty = false;
+ if(rand() % 4 == 0)
+ low_block_is_empty = true;
+
+ // This is the basic material of what the visible flat ground
+ // will consist of
+ u8 material = MATERIAL_GRASS;
+
+ s32 lowest_ground_y = 32767;
+
+ // DEBUG
+ //sector->printHeightmaps();
+
+ for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++)
+ for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++)
+ {
+ //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
+ float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
+
+ assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
+
+ s16 surface_y = surface_y_f;
+ //avg_ground_y += surface_y;
+ if(surface_y < lowest_ground_y)
+ lowest_ground_y = surface_y;
+
+ s32 surface_depth = 0;
+
+ float slope = sector->getSlope(v2s16(x0,z0)).getLength();
+
+ float min_slope = 0.45;
+ float max_slope = 0.85;
+ float min_slope_depth = 5.0;
+ float max_slope_depth = 0;
+ if(slope < min_slope)
+ surface_depth = min_slope_depth;
+ else if(slope > max_slope)
+ surface_depth = max_slope_depth;
+ else
+ surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
+
+ for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++){
+ s16 real_y = block_y * MAP_BLOCKSIZE + y0;
+ MapNode n;
+ /*
+ Calculate lighting
+
+ FIXME: If there are some man-made structures above the
+ newly created block, they won't be taken into account.
+ */
+ if(real_y > surface_y)
+ n.setLight(LIGHT_SUN);
+ /*
+ Calculate material
+ */
+ // If node is very low
+ if(real_y <= surface_y - 10){
+ // Create dungeons
+ if(low_block_is_empty){
+ n.d = MATERIAL_AIR;
+ }
+ else{
+ n.d = MATERIAL_STONE;
+ }
+ }
+ // If node is under surface level
+ else if(real_y <= surface_y - surface_depth)
+ n.d = MATERIAL_STONE;
+ // If node is at or under heightmap y
+ else if(real_y <= surface_y)
+ n.d = material;
+ // If node is over heightmap y
+ else{
+ // If under water level, it's water
+ if(real_y < WATER_LEVEL)
+ {
+ n.d = MATERIAL_WATER;
+ n.setLight(diminish_light(LIGHT_SUN, WATER_LEVEL-real_y+1));
+ }
+ // else air
+ else
+ n.d = MATERIAL_AIR;
+ }
+ block->setNode(v3s16(x0,y0,z0), n);
+ }
+ }
+
+ /*
+ Calculate is_underground
+ */
+ // Probably underground if the highest part of block is under lowest
+ // ground height
+ bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y;
+ block->setIsUnderground(is_underground);
+
+ /*
+ Add some minerals
+ */
+
+ if(is_underground && low_block_is_empty == false)
+ {
+ s16 underground_level = lowest_ground_y/MAP_BLOCKSIZE - block_y;
+ for(s16 i=0; i<underground_level*3; i++)
+ {
+ if(rand()%2 == 0)
+ {
+ v3s16 cp(
+ /*(rand()%(MAP_BLOCKSIZE-4))+2,
+ (rand()%(MAP_BLOCKSIZE-4))+2,
+ (rand()%(MAP_BLOCKSIZE-4))+2*/
+ (rand()%(MAP_BLOCKSIZE-2))+1,
+ (rand()%(MAP_BLOCKSIZE-2))+1,
+ (rand()%(MAP_BLOCKSIZE-2))+1
+ );
+
+ MapNode n;
+ n.d = MATERIAL_MESE;
+
+ if(rand()%8 == 0)
+ block->setNode(cp, n);
+
+ for(u16 i=0; i<26; i++)
+ {
+ if(rand()%8 == 0)
+ block->setNode(cp+g_26dirs[i], n);
+ }
+ }
+ }
+ }
+
+ /*
+ Create a few rats in empty blocks underground
+ */
+ if(is_underground && low_block_is_empty == true)
+ {
+ //for(u16 i=0; i<2; i++)
+ {
+ v3s16 pos(8, 1, 8);
+ RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
+ block->addObject(obj);
+ }
+ }
+
+ /*
+ TODO: REMOVE
+ DEBUG
+ Add some objects to the block for testing.
+ */
+ /*if(p == v3s16(0,0,0))
+ {
+ //TestObject *obj = new TestObject(NULL, -1, v3f(BS*8,BS*8,BS*8));
+ Test2Object *obj = new Test2Object(NULL, -1, v3f(BS*8,BS*15,BS*8));
+ block->addObject(obj);
+ }*/
+
+ /*
+ {
+ v3s16 pos(8, 11, 8);
+ SignObject *obj = new SignObject(NULL, -1, intToFloat(pos));
+ obj->setText("Moicka");
+ obj->setYaw(45);
+ block->addObject(obj);
+ }
+
+ {
+ v3s16 pos(8, 11, 8);
+ RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
+ block->addObject(obj);
+ }
+ */
+
+ /*
+ Add block to sector.
+ */
+ sector->insertBlock(block);
+
+ // An y-wise container if changed blocks
+ core::map<s16, MapBlock*> changed_blocks_sector;
+
+ /*
+ Check if any sector's objects can be placed now.
+ If so, place them.
+ */
+ core::map<v3s16, u8> *objects = sector->getObjects();
+ core::list<v3s16> objects_to_remove;
+ for(core::map<v3s16, u8>::Iterator i = objects->getIterator();
+ i.atEnd() == false; i++)
+ {
+ v3s16 p = i.getNode()->getKey();
+ u8 d = i.getNode()->getValue();
+
+ //v3s16 p = p_sector - v3s16(0, block_y*MAP_BLOCKSIZE, 0);
+
+ try
+ {
+
+ if(d == SECTOR_OBJECT_TEST)
+ {
+ if(sector->isValidArea(p + v3s16(0,0,0),
+ p + v3s16(0,0,0), &changed_blocks_sector))
+ {
+ MapNode n;
+ n.d = MATERIAL_LIGHT;
+ sector->setNode(p, n);
+ objects_to_remove.push_back(p);
+ }
+ }
+ else if(d == SECTOR_OBJECT_TREE_1)
+ {
+ v3s16 p_min = p + v3s16(-1,0,-1);
+ v3s16 p_max = p + v3s16(1,4,1);
+ if(sector->isValidArea(p_min, p_max,
+ &changed_blocks_sector))
+ {
+ MapNode n;
+ n.d = MATERIAL_TREE;
+ sector->setNode(p+v3s16(0,0,0), n);
+ sector->setNode(p+v3s16(0,1,0), n);
+ sector->setNode(p+v3s16(0,2,0), n);
+ sector->setNode(p+v3s16(0,3,0), n);
+
+ n.d = MATERIAL_LEAVES;
+
+ sector->setNode(p+v3s16(0,4,0), n);
+
+ sector->setNode(p+v3s16(-1,4,0), n);
+ sector->setNode(p+v3s16(1,4,0), n);
+ sector->setNode(p+v3s16(0,4,-1), n);
+ sector->setNode(p+v3s16(0,4,1), n);
+ sector->setNode(p+v3s16(1,4,1), n);
+ sector->setNode(p+v3s16(-1,4,1), n);
+ sector->setNode(p+v3s16(-1,4,-1), n);
+ sector->setNode(p+v3s16(1,4,-1), n);
+
+ sector->setNode(p+v3s16(-1,3,0), n);
+ sector->setNode(p+v3s16(1,3,0), n);
+ sector->setNode(p+v3s16(0,3,-1), n);
+ sector->setNode(p+v3s16(0,3,1), n);
+ sector->setNode(p+v3s16(1,3,1), n);
+ sector->setNode(p+v3s16(-1,3,1), n);
+ sector->setNode(p+v3s16(-1,3,-1), n);
+ sector->setNode(p+v3s16(1,3,-1), n);
+
+ objects_to_remove.push_back(p);
+
+ // Lighting has to be recalculated for this one.
+ sector->getBlocksInArea(p_min, p_max,
+ lighting_invalidated_blocks);
+ }
+ }
+ else if(d == SECTOR_OBJECT_BUSH_1)
+ {
+ if(sector->isValidArea(p + v3s16(0,0,0),
+ p + v3s16(0,0,0), &changed_blocks_sector))
+ {
+ MapNode n;
+ n.d = MATERIAL_LEAVES;
+ sector->setNode(p+v3s16(0,0,0), n);
+
+ objects_to_remove.push_back(p);
+ }
+ }
+ else
+ {
+ dstream<<"ServerMap::emergeBlock(): "
+ "Invalid heightmap object"
+ <<std::endl;
+ }
+
+ }//try
+ catch(InvalidPositionException &e)
+ {
+ dstream<<"WARNING: "<<__FUNCTION_NAME
+ <<": while inserting object "<<(int)d
+ <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+ <<" InvalidPositionException.what()="
+ <<e.what()<<std::endl;
+ // This is not too fatal and seems to happen sometimes.
+ assert(0);
+ }
+ }
+
+ for(core::list<v3s16>::Iterator i = objects_to_remove.begin();
+ i != objects_to_remove.end(); i++)
+ {
+ objects->remove(*i);
+ }
+
+ for(core::map<s16, MapBlock*>::Iterator
+ i = changed_blocks_sector.getIterator();
+ i.atEnd() == false; i++)
+ {
+ MapBlock *block = i.getNode()->getValue();
+
+ changed_blocks.insert(block->getPos(), block);
+ }
+
+ return block;
+}
+
+void ServerMap::createDir(std::string path)
+{
+ if(fs::CreateDir(path) == false)
+ {
+ m_dout<<DTIME<<"ServerMap: Failed to create directory "
+ <<"\""<<path<<"\""<<std::endl;
+ throw BaseException("ServerMap failed to create directory");
+ }
+}
+
+std::string ServerMap::getSectorSubDir(v2s16 pos)
+{
+ char cc[9];
+ snprintf(cc, 9, "%.4x%.4x",
+ (unsigned int)pos.X&0xffff,
+ (unsigned int)pos.Y&0xffff);
+
+ return std::string(cc);
+}
+
+std::string ServerMap::getSectorDir(v2s16 pos)
+{
+ return m_savedir + "/sectors/" + getSectorSubDir(pos);
+}
+
+v2s16 ServerMap::getSectorPos(std::string dirname)
+{
+ if(dirname.size() != 8)
+ throw InvalidFilenameException("Invalid sector directory name");
+ unsigned int x, y;
+ int r = sscanf(dirname.c_str(), "%4x%4x", &x, &y);
+ if(r != 2)
+ throw InvalidFilenameException("Invalid sector directory name");
+ v2s16 pos((s16)x, (s16)y);
+ return pos;
+}
+
+v3s16 ServerMap::getBlockPos(std::string sectordir, std::string blockfile)
+{
+ v2s16 p2d = getSectorPos(sectordir);
+
+ if(blockfile.size() != 4){
+ throw InvalidFilenameException("Invalid block filename");
+ }
+ unsigned int y;
+ int r = sscanf(blockfile.c_str(), "%4x", &y);
+ if(r != 1)
+ throw InvalidFilenameException("Invalid block filename");
+ return v3s16(p2d.X, y, p2d.Y);
+}
+
+// Debug helpers
+#define ENABLE_SECTOR_SAVING 1
+#define ENABLE_SECTOR_LOADING 1
+#define ENABLE_BLOCK_SAVING 1
+#define ENABLE_BLOCK_LOADING 1
+
+void ServerMap::save(bool only_changed)
+{
+ DSTACK(__FUNCTION_NAME);
+ if(m_map_saving_enabled == false)
+ {
+ dstream<<DTIME<<"WARNING: Not saving map, saving disabled."<<std::endl;
+ return;
+ }
+
+ if(only_changed == false)
+ dstream<<DTIME<<"ServerMap: Saving whole map, this can take time."
+ <<std::endl;
+
+ saveMasterHeightmap();
+
+ u32 sector_meta_count = 0;
+ u32 block_count = 0;
+
+ { //sectorlock
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::map<v2s16, MapSector*>::Iterator i = m_sectors.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ ServerMapSector *sector = (ServerMapSector*)i.getNode()->getValue();
+ assert(sector->getId() == MAPSECTOR_SERVER);
+
+ if(ENABLE_SECTOR_SAVING)
+ {
+ if(sector->differs_from_disk || only_changed == false)
+ {
+ saveSectorMeta(sector);
+ sector_meta_count++;
+ }
+ }
+ if(ENABLE_BLOCK_SAVING)
+ {
+ core::list<MapBlock*> blocks;
+ sector->getBlocks(blocks);
+ core::list<MapBlock*>::Iterator j;
+ for(j=blocks.begin(); j!=blocks.end(); j++)
+ {
+ MapBlock *block = *j;
+ if(block->getChangedFlag() || only_changed == false)
+ {
+ saveBlock(block);
+ block_count++;
+ }
+ }
+ }
+ }
+
+ }//sectorlock
+
+ u32 deleted_count = 0;
+ deleted_count = deleteUnusedSectors
+ (SERVERMAP_DELETE_UNUSED_SECTORS_TIMEOUT);
+
+ /*
+ Only print if something happened or saved whole map
+ */
+ if(only_changed == false || sector_meta_count != 0
+ || block_count != 0 || deleted_count != 0)
+ {
+ dstream<<DTIME<<"ServerMap: Written: "
+ <<sector_meta_count<<" sector metadata files, "
+ <<block_count<<" block files, "
+ <<deleted_count<<" sectors unloaded from memory."
+ <<std::endl;
+ }
+}
+
+void ServerMap::loadAll()
+{
+ DSTACK(__FUNCTION_NAME);
+ dstream<<DTIME<<"ServerMap: Loading map..."<<std::endl;
+
+ loadMasterHeightmap();
+
+ std::vector<fs::DirListNode> list = fs::GetDirListing(m_savedir+"/sectors/");
+
+ dstream<<DTIME<<"There are "<<list.size()<<" sectors."<<std::endl;
+
+ JMutexAutoLock lock(m_sector_mutex);
+
+ s32 counter = 0;
+ s32 printed_counter = -100000;
+ s32 count = list.size();
+
+ std::vector<fs::DirListNode>::iterator i;
+ for(i=list.begin(); i!=list.end(); i++)
+ {
+ if(counter > printed_counter + 10)
+ {
+ dstream<<DTIME<<counter<<"/"<<count<<std::endl;
+ printed_counter = counter;
+ }
+ counter++;
+
+ MapSector *sector = NULL;
+
+ // We want directories
+ if(i->dir == false)
+ continue;
+ try{
+ sector = loadSectorMeta(i->name);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ // This catches unknown crap in directory
+ }
+
+ if(ENABLE_BLOCK_LOADING)
+ {
+ std::vector<fs::DirListNode> list2 = fs::GetDirListing
+ (m_savedir+"/sectors/"+i->name);
+ std::vector<fs::DirListNode>::iterator i2;
+ for(i2=list2.begin(); i2!=list2.end(); i2++)
+ {
+ // We want files
+ if(i2->dir)
+ continue;
+ try{
+ loadBlock(i->name, i2->name, sector);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ // This catches unknown crap in directory
+ }
+ }
+ }
+ }
+ dstream<<DTIME<<"ServerMap: Map loaded."<<std::endl;
+}
+
+void ServerMap::saveMasterHeightmap()
+{
+ DSTACK(__FUNCTION_NAME);
+ createDir(m_savedir);
+
+ std::string fullpath = m_savedir + "/master_heightmap";
+ std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+ if(o.good() == false)
+ throw FileNotGoodException("Cannot open master heightmap");
+
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+
+#if 0
+ SharedBuffer<u8> hmdata = m_heightmap->serialize(version);
+ /*
+ [0] u8 serialization version
+ [1] X master heightmap
+ */
+ u32 fullsize = 1 + hmdata.getSize();
+ SharedBuffer<u8> data(fullsize);
+
+ data[0] = version;
+ memcpy(&data[1], *hmdata, hmdata.getSize());
+
+ o.write((const char*)*data, fullsize);
+#endif
+
+ m_heightmap->serialize(o, version);
+}
+
+void ServerMap::loadMasterHeightmap()
+{
+ DSTACK(__FUNCTION_NAME);
+ std::string fullpath = m_savedir + "/master_heightmap";
+ std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+ if(is.good() == false)
+ throw FileNotGoodException("Cannot open master heightmap");
+
+ if(m_heightmap != NULL)
+ delete m_heightmap;
+
+ m_heightmap = UnlimitedHeightmap::deSerialize(is);
+}
+
+void ServerMap::saveSectorMeta(ServerMapSector *sector)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+ // Get destination
+ v2s16 pos = sector->getPos();
+ createDir(m_savedir);
+ createDir(m_savedir+"/sectors");
+ std::string dir = getSectorDir(pos);
+ createDir(dir);
+
+ std::string fullpath = dir + "/heightmap";
+ std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+ if(o.good() == false)
+ throw FileNotGoodException("Cannot open master heightmap");
+
+ sector->serialize(o, version);
+
+ sector->differs_from_disk = false;
+}
+
+MapSector* ServerMap::loadSectorMeta(std::string dirname)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Get destination
+ v2s16 p2d = getSectorPos(dirname);
+ std::string dir = m_savedir + "/sectors/" + dirname;
+
+ std::string fullpath = dir + "/heightmap";
+ std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+ if(is.good() == false)
+ throw FileNotGoodException("Cannot open sector heightmap");
+
+ ServerMapSector *sector = ServerMapSector::deSerialize
+ (is, this, p2d, &m_hwrapper, m_sectors);
+
+ sector->differs_from_disk = false;
+
+ return sector;
+}
+
+bool ServerMap::loadSectorFull(v2s16 p2d)
+{
+ DSTACK(__FUNCTION_NAME);
+ std::string sectorsubdir = getSectorSubDir(p2d);
+
+ MapSector *sector = NULL;
+
+ JMutexAutoLock lock(m_sector_mutex);
+
+ try{
+ sector = loadSectorMeta(sectorsubdir);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ return false;
+ }
+ catch(FileNotGoodException &e)
+ {
+ return false;
+ }
+ catch(std::exception &e)
+ {
+ return false;
+ }
+
+ if(ENABLE_BLOCK_LOADING)
+ {
+ std::vector<fs::DirListNode> list2 = fs::GetDirListing
+ (m_savedir+"/sectors/"+sectorsubdir);
+ std::vector<fs::DirListNode>::iterator i2;
+ for(i2=list2.begin(); i2!=list2.end(); i2++)
+ {
+ // We want files
+ if(i2->dir)
+ continue;
+ try{
+ loadBlock(sectorsubdir, i2->name, sector);
+ }
+ catch(InvalidFilenameException &e)
+ {
+ // This catches unknown crap in directory
+ }
+ }
+ }
+ return true;
+}
+
+#if 0
+bool ServerMap::deFlushSector(v2s16 p2d)
+{
+ DSTACK(__FUNCTION_NAME);
+ // See if it already exists in memory
+ try{
+ MapSector *sector = getSectorNoGenerate(p2d);
+ return true;
+ }
+ catch(InvalidPositionException &e)
+ {
+ /*
+ Try to load the sector from disk.
+ */
+ if(loadSectorFull(p2d) == true)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+void ServerMap::saveBlock(MapBlock *block)
+{
+ DSTACK(__FUNCTION_NAME);
+ /*
+ Dummy blocks are not written
+ */
+ if(block->isDummy())
+ {
+ /*v3s16 p = block->getPos();
+ dstream<<"ServerMap::saveBlock(): WARNING: Not writing dummy block "
+ <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
+ return;
+ }
+
+ // Format used for writing
+ u8 version = SER_FMT_VER_HIGHEST;
+ // Get destination
+ v3s16 p3d = block->getPos();
+ v2s16 p2d(p3d.X, p3d.Z);
+ createDir(m_savedir);
+ createDir(m_savedir+"/sectors");
+ std::string dir = getSectorDir(p2d);
+ createDir(dir);
+
+ // Block file is map/sectors/xxxxxxxx/xxxx
+ char cc[5];
+ snprintf(cc, 5, "%.4x", (unsigned int)p3d.Y&0xffff);
+ std::string fullpath = dir + "/" + cc;
+ std::ofstream o(fullpath.c_str(), std::ios_base::binary);
+ if(o.good() == false)
+ throw FileNotGoodException("Cannot open block data");
+
+ /*
+ [0] u8 serialization version
+ [1] data
+ */
+ o.write((char*)&version, 1);
+
+ block->serialize(o, version);
+
+ /*
+ Versions up from 9 have block objects.
+ */
+ if(version >= 9)
+ {
+ block->serializeObjects(o, version);
+ }
+
+ // We just wrote it to the disk
+ block->resetChangedFlag();
+}
+
+void ServerMap::loadBlock(std::string sectordir, std::string blockfile, MapSector *sector)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Block file is map/sectors/xxxxxxxx/xxxx
+ std::string fullpath = m_savedir+"/sectors/"+sectordir+"/"+blockfile;
+ std::ifstream is(fullpath.c_str(), std::ios_base::binary);
+ if(is.good() == false)
+ throw FileNotGoodException("Cannot open block file");
+
+ v3s16 p3d = getBlockPos(sectordir, blockfile);
+ v2s16 p2d(p3d.X, p3d.Z);
+
+ assert(sector->getPos() == p2d);
+
+ u8 version = SER_FMT_VER_INVALID;
+ is.read((char*)&version, 1);
+
+ /*u32 block_size = MapBlock::serializedLength(version);
+ SharedBuffer<u8> data(block_size);
+ is.read((char*)*data, block_size);*/
+
+ // This will always return a sector because we're the server
+ //MapSector *sector = emergeSector(p2d);
+
+ MapBlock *block = NULL;
+ bool created_new = false;
+ try{
+ block = sector->getBlockNoCreate(p3d.Y);
+ }
+ catch(InvalidPositionException &e)
+ {
+ block = sector->createBlankBlockNoInsert(p3d.Y);
+ created_new = true;
+ }
+
+ block->deSerialize(is, version);
+
+ /*
+ Versions up from 9 have block objects.
+ */
+ if(version >= 9)
+ {
+ block->updateObjects(is, version, NULL);
+ }
+
+ if(created_new)
+ sector->insertBlock(block);
+
+ /*
+ Convert old formats to new and save
+ */
+
+ if(version == 0 || version == 1)
+ {
+ dstream<<"Block ("<<p3d.X<<","<<p3d.Y<<","<<p3d.Z<<")"
+ " is in old format. Updating lighting and saving"
+ " modified blocks in new format."<<std::endl;
+
+ // Old version has zero lighting, update it
+ core::map<v3s16, MapBlock*> blocks_changed;
+ blocks_changed.insert(block->getPos(), block);
+ core::map<v3s16, MapBlock*> modified_blocks;
+ updateLighting(blocks_changed, modified_blocks);
+
+ // Close input file
+ is.close();
+
+ // Save modified blocks
+ core::map<v3s16, MapBlock * >::Iterator i = modified_blocks.getIterator();
+ for(; i.atEnd() == false; i++)
+ {
+ MapBlock *b2 = i.getNode()->getValue();
+ saveBlock(b2);
+ }
+ }
+ // Save blocks in new format
+ else if(version < SER_FMT_VER_HIGHEST)
+ {
+ saveBlock(block);
+ }
+
+ // We just loaded it from the disk, so it's up-to-date.
+ block->resetChangedFlag();
+}
+
+// Gets from master heightmap
+void ServerMap::getSectorCorners(v2s16 p2d, s16 *corners)
+{
+ assert(m_heightmap != NULL);
+ /*
+ Corner definition:
+ v2s16(0,0),
+ v2s16(1,0),
+ v2s16(1,1),
+ v2s16(0,1),
+ */
+ corners[0] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(0,0))*SECTOR_HEIGHTMAP_SPLIT);
+ corners[1] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(1,0))*SECTOR_HEIGHTMAP_SPLIT);
+ corners[2] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(1,1))*SECTOR_HEIGHTMAP_SPLIT);
+ corners[3] = m_heightmap->getGroundHeight
+ ((p2d+v2s16(0,1))*SECTOR_HEIGHTMAP_SPLIT);
+}
+
+void ServerMap::PrintInfo(std::ostream &out)
+{
+ out<<"ServerMap: ";
+}
+
+/*
+ ClientMap
+*/
+
+ClientMap::ClientMap(
+ Client *client,
+ video::SMaterial *materials,
+ scene::ISceneNode* parent,
+ scene::ISceneManager* mgr,
+ s32 id
+):
+ Map(dout_client),
+ scene::ISceneNode(parent, mgr, id),
+ m_client(client),
+ m_materials(materials),
+ mesh(NULL)
+{
+ /*m_box = core::aabbox3d<f32>(0,0,0,
+ map->getW()*BS, map->getH()*BS, map->getD()*BS);*/
+ /*m_box = core::aabbox3d<f32>(0,0,0,
+ map->getSizeNodes().X * BS,
+ map->getSizeNodes().Y * BS,
+ map->getSizeNodes().Z * BS);*/
+ m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
+ BS*1000000,BS*1000000,BS*1000000);
+
+ mesh_mutex.Init();
+}
+
+ClientMap::~ClientMap()
+{
+ JMutexAutoLock lock(mesh_mutex);
+
+ if(mesh != NULL)
+ {
+ mesh->drop();
+ mesh = NULL;
+ }
+}
+
+MapSector * ClientMap::emergeSector(v2s16 p2d)
+{
+ DSTACK(__FUNCTION_NAME);
+ // Check that it doesn't exist already
+ try{
+ return getSectorNoGenerate(p2d);
+ }
+ catch(InvalidPositionException &e)
+ {
+ }
+
+ // Create a sector with no heightmaps
+ ClientMapSector *sector = new ClientMapSector(this, p2d);
+
+ {
+ JMutexAutoLock lock(m_sector_mutex);
+ m_sectors.insert(p2d, sector);
+ }
+
+ return sector;
+}
+
+void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is)
+{
+ DSTACK(__FUNCTION_NAME);
+ ClientMapSector *sector = NULL;
+
+ JMutexAutoLock lock(m_sector_mutex);
+
+ core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d);
+
+ if(n != NULL)
+ {
+ sector = (ClientMapSector*)n->getValue();
+ assert(sector->getId() == MAPSECTOR_CLIENT);
+ }
+ else
+ {
+ sector = new ClientMapSector(this, p2d);
+ {
+ JMutexAutoLock lock(m_sector_mutex);
+ m_sectors.insert(p2d, sector);
+ }
+ }
+
+ sector->deSerialize(is);
+}
+
+void ClientMap::renderMap(video::IVideoDriver* driver,
+ video::SMaterial *materials, s32 pass)
+{
+ //m_dout<<DTIME<<"Rendering map..."<<std::endl;
+ DSTACK(__FUNCTION_NAME);
+
+ bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
+#if 0
+ /*
+ Draw master heightmap mesh
+ */
+
+ {
+ JMutexAutoLock lock(mesh_mutex);
+ if(mesh != NULL)
+ {
+ u32 c = mesh->getMeshBufferCount();
+
+ for(u32 i=0; i<c; i++)
+ {
+ scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+ const video::SMaterial& material = buf->getMaterial();
+ video::IMaterialRenderer* rnd =
+ driver->getMaterialRenderer(material.MaterialType);
+ bool transparent = (rnd && rnd->isTransparent());
+ // Render transparent on transparent pass and likewise.
+ if(transparent == is_transparent_pass)
+ {
+ driver->setMaterial(buf->getMaterial());
+ driver->drawMeshBuffer(buf);
+ }
+ }
+ }
+ }
+#endif
+
+ /*
+ Get time for measuring timeout.
+
+ Measuring time is very useful for long delays when the
+ machine is swapping a lot.
+ */
+ int time1 = time(0);
+
+ /*
+ Collect all blocks that are in the view range
+
+ Should not optimize more here as we want to auto-update
+ all changed nodes in viewing range at the next step.
+ */
+
+ s16 viewing_range_nodes;
+ bool viewing_range_all;
+ {
+ JMutexAutoLock lock(g_range_mutex);
+ viewing_range_nodes = g_viewing_range_nodes;
+ viewing_range_all = g_viewing_range_all;
+ }
+
+ m_camera_mutex.Lock();
+ v3f camera_position = m_camera_position;
+ v3f camera_direction = m_camera_direction;
+ m_camera_mutex.Unlock();
+
+ /*
+ Get all blocks and draw all visible ones
+ */
+
+ v3s16 cam_pos_nodes(
+ camera_position.X / BS,
+ camera_position.Y / BS,
+ camera_position.Z / BS);
+
+ v3s16 box_nodes_d = viewing_range_nodes * v3s16(1,1,1);
+
+ v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
+ v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+
+ // Take a fair amount as we will be dropping more out later
+ v3s16 p_blocks_min(
+ p_nodes_min.X / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Y / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+ v3s16 p_blocks_max(
+ p_nodes_max.X / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+
+ u32 vertex_count = 0;
+
+ core::map<v2s16, MapSector*>::Iterator si;
+
+ //NOTE: The sectors map should be locked but we're not doing it
+ // because it'd cause too much delays
+
+ si = m_sectors.getIterator();
+ for(; si.atEnd() == false; si++)
+ {
+ {
+ static int timecheck_counter = 0;
+ timecheck_counter++;
+ if(timecheck_counter > 50)
+ {
+ int time2 = time(0);
+ if(time2 > time1 + 4)
+ {
+ dstream<<"ClientMap::renderMap(): "
+ "Rendering takes ages, returning."
+ <<std::endl;
+ return;
+ }
+ }
+ }
+
+ MapSector *sector = si.getNode()->getValue();
+ v2s16 sp = sector->getPos();
+
+ if(viewing_range_all == false)
+ {
+ if(sp.X < p_blocks_min.X
+ || sp.X > p_blocks_max.X
+ || sp.Y < p_blocks_min.Z
+ || sp.Y > p_blocks_max.Z)
+ continue;
+ }
+
+ core::list< MapBlock * > sectorblocks;
+ sector->getBlocks(sectorblocks);
+
+ /*
+ Draw blocks
+ */
+
+ core::list< MapBlock * >::Iterator i;
+ for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++)
+ {
+ MapBlock *block = *i;
+
+ /*
+ Compare block position to camera position, skip
+ if not seen on display
+ */
+
+ v3s16 blockpos_nodes = block->getPosRelative();
+
+ // Block center position
+ v3f blockpos(
+ ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
+ ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
+ ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
+ );
+
+ // Block position relative to camera
+ v3f blockpos_relative = blockpos - camera_position;
+
+ // Distance in camera direction (+=front, -=back)
+ f32 dforward = blockpos_relative.dotProduct(camera_direction);
+
+ // Total distance
+ f32 d = blockpos_relative.getLength();
+
+ if(viewing_range_all == false)
+ {
+ // If block is far away, don't draw it
+ if(d > viewing_range_nodes * BS)
+ continue;
+ }
+
+ // Maximum radius of a block
+ f32 block_max_radius = 0.5*1.44*1.44*MAP_BLOCKSIZE*BS;
+
+ // If block is (nearly) touching the camera, don't
+ // bother validating further (that is, render it anyway)
+ if(d > block_max_radius * 1.5)
+ {
+ // Cosine of the angle between the camera direction
+ // and the block direction (camera_direction is an unit vector)
+ f32 cosangle = dforward / d;
+
+ // Compensate for the size of the block
+ // (as the block has to be shown even if it's a bit off FOV)
+ // This is an estimate.
+ cosangle += block_max_radius / dforward;
+
+ // If block is not in the field of view, skip it
+ //if(cosangle < cos(FOV_ANGLE/2))
+ if(cosangle < cos(FOV_ANGLE/2. * 4./3.))
+ continue;
+ }
+
+ /*
+ Draw the faces of the block
+ */
+
+ {
+ JMutexAutoLock lock(block->mesh_mutex);
+
+ // Cancel if block has no mesh
+ if(block->mesh == NULL)
+ continue;
+
+ u32 c = block->mesh->getMeshBufferCount();
+
+ for(u32 i=0; i<c; i++)
+ {
+ scene::IMeshBuffer *buf = block->mesh->getMeshBuffer(i);
+ const video::SMaterial& material = buf->getMaterial();
+ video::IMaterialRenderer* rnd =
+ driver->getMaterialRenderer(material.MaterialType);
+ bool transparent = (rnd && rnd->isTransparent());
+ // Render transparent on transparent pass and likewise.
+ if(transparent == is_transparent_pass)
+ {
+ driver->setMaterial(buf->getMaterial());
+ driver->drawMeshBuffer(buf);
+ vertex_count += buf->getVertexCount();
+ }
+ }
+ }
+ } // foreach sectorblocks
+ }
+
+ /*dstream<<"renderMap(): is_transparent_pass="<<is_transparent_pass
+ <<", rendered "<<vertex_count<<" vertices."<<std::endl;*/
+}
+
+void ClientMap::updateMesh()
+{
+#if 0
+ DSTACK(__FUNCTION_NAME);
+ //TODO
+ /*
+ Check what sectors don't draw anything useful at ground level
+ and create a mesh of the rough heightmap at those positions.
+ */
+
+ m_camera_mutex.Lock();
+ v3f camera_position = m_camera_position;
+ v3f camera_direction = m_camera_direction;
+ m_camera_mutex.Unlock();
+
+ v3s16 cam_pos_nodes(
+ camera_position.X / BS,
+ camera_position.Y / BS,
+ camera_position.Z / BS);
+
+ v3s16 box_nodes_d = HEIGHTMAP_RANGE_NODES * v3s16(1,1,1);
+
+ v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
+ v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
+
+ // Take a fair amount as we will be dropping more out later
+ v3s16 p_blocks_min(
+ p_nodes_min.X / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Y / MAP_BLOCKSIZE - 1,
+ p_nodes_min.Z / MAP_BLOCKSIZE - 1);
+ v3s16 p_blocks_max(
+ p_nodes_max.X / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+ p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+
+ /*
+ Initialize new mesh
+ */
+
+ scene::SMesh *mesh_new = new scene::SMesh();
+ //scene::IMeshBuffer *buf = NULL;
+ scene::SMeshBuffer *buf = NULL;
+
+ u8 material_in_use = 0;
+
+ /*
+ Loop through sectors
+ */
+
+ for(core::map<v2s16, MapSector*>::Iterator
+ si = m_sectors.getIterator();
+ si.atEnd() == false; si++)
+ {
+ MapSector *sector = si.getNode()->getValue();
+
+ if(sector->getId() != MAPSECTOR_CLIENT)
+ {
+ dstream<<"WARNING: Client has a non-client sector"
+ <<std::endl;
+ continue;
+ }
+
+ ClientMapSector *cs = (ClientMapSector*)sector;
+
+ v2s16 sp = sector->getPos();
+
+ if(sp.X < p_blocks_min.X
+ || sp.X > p_blocks_max.X
+ || sp.Y < p_blocks_min.Z
+ || sp.Y > p_blocks_max.Z)
+ continue;
+
+ /*
+ Get some ground level info
+ */
+
+ s16 a = -5;
+
+ s16 cn[4] =
+ {
+ cs->getCorner(0)+a,
+ cs->getCorner(1)+a,
+ cs->getCorner(2)+a,
+ cs->getCorner(3)+a,
+ };
+ s16 cn_avg = (cn[0]+cn[1]+cn[2]+cn[3])/4;
+ s16 cn_min = 32767;
+ s16 cn_max = -32768;
+ for(s16 i=0; i<4; i++)
+ {
+ if(cn[i] < cn_min)
+ cn_min = cn[i];
+ if(cn[i] > cn_max)
+ cn_max = cn[i];
+ }
+ s16 cn_slope = cn_max - cn_min;
+
+ /*
+ Generate this part of the heightmap mesh
+ */
+
+ u8 material;
+ if(cn_avg + MAP_BLOCKSIZE/4 <= WATER_LEVEL)
+ material = 0;
+ else if(cn_slope <= MAP_BLOCKSIZE)
+ material = 1;
+ else
+ material = 2;
+
+ if(material != material_in_use || buf == NULL)
+ {
+ // Try to get a meshbuffer associated with the material
+ buf = (scene::SMeshBuffer*)mesh_new->getMeshBuffer
+ (g_mesh_materials[material]);
+ // If not found, create one
+ if(buf == NULL)
+ {
+ // This is a "Standard MeshBuffer",
+ // it's a typedeffed CMeshBuffer<video::S3DVertex>
+ buf = new scene::SMeshBuffer();
+
+ // Set material
+ buf->Material = g_mesh_materials[material];
+ // Use VBO
+ //buf->setHardwareMappingHint(scene::EHM_STATIC);
+ // Add to mesh
+ mesh_new->addMeshBuffer(buf);
+ // Mesh grabbed it
+ buf->drop();
+ }
+ material_in_use = material;
+ }
+
+ // Sector side width in floating-point units
+ f32 sd = BS * MAP_BLOCKSIZE;
+ // Sector position in global floating-point units
+ v3f spf = v3f((f32)sp.X, 0, (f32)sp.Y) * sd;
+
+ //video::SColor c(255,255,255,255);
+ u8 cc = 180;
+ video::SColor c(255,cc,cc,cc);
+
+ video::S3DVertex vertices[4] =
+ {
+ video::S3DVertex(spf.X, (f32)BS*cn[0],spf.Z, 0,0,0, c, 0,1),
+ video::S3DVertex(spf.X+sd,(f32)BS*cn[1],spf.Z, 0,0,0, c, 1,1),
+ video::S3DVertex(spf.X+sd,(f32)BS*cn[2],spf.Z+sd,0,0,0, c, 1,0),
+ video::S3DVertex(spf.X, (f32)BS*cn[3],spf.Z+sd,0,0,0, c, 0,0),
+ };
+ u16 indices[] = {0,1,2,2,3,0};
+
+ buf->append(vertices, 4, indices, 6);
+ }
+
+ // Set VBO on
+ //mesh_new->setHardwareMappingHint(scene::EHM_STATIC);
+
+ /*
+ Replace the mesh
+ */
+
+ mesh_mutex.Lock();
+
+ scene::SMesh *mesh_old = mesh;
+
+ //DEBUG
+ /*mesh = NULL;
+ mesh_new->drop();*/
+ mesh = mesh_new;
+
+ mesh_mutex.Unlock();
+
+ if(mesh_old != NULL)
+ {
+ /*dstream<<"mesh_old refcount="<<mesh_old->getReferenceCount()
+ <<std::endl;
+ scene::IMeshBuffer *buf = mesh_new->getMeshBuffer
+ (g_materials[MATERIAL_GRASS]);
+ if(buf != NULL)
+ dstream<<"grass buf refcount="<<buf->getReferenceCount()
+ <<std::endl;*/
+
+ mesh_old->drop();
+ }
+ else
+ {
+ dstream<<"WARNING: There was no old master heightmap mesh"<<std::endl;
+ }
+#endif
+}
+
+void ClientMap::PrintInfo(std::ostream &out)
+{
+ out<<"ClientMap: ";
+}
+
+