Light calculation: New bulk node lighting code

This commit introduces a new bulk node lighting algorithm to minimize
lighting bugs during l-system tree generation, schematic placement and
non-mapgen-object lua voxelmanip light calculation.

If the block above the changed area is not loaded, it gets loaded to avoid
lighting bugs.
Light is updated as soon as write_to_map is called on a voxel manipulator,
therefore update_map does nothing.
This commit is contained in:
Dániel Juhász 2016-12-10 19:02:44 +01:00 committed by paramat
parent d785456b3f
commit ab371cc934
10 changed files with 408 additions and 636 deletions

@ -3282,9 +3282,6 @@ format as produced by get_data() et al. and is *not required* to be a table retr
Once the internal VoxelManip state has been modified to your liking, the changes can be committed back Once the internal VoxelManip state has been modified to your liking, the changes can be committed back
to the map by calling `VoxelManip:write_to_map()`. to the map by calling `VoxelManip:write_to_map()`.
Finally, a call to `VoxelManip:update_map()` is required to re-calculate lighting and set the blocks
as being modified so that connected clients are sent the updated parts of map.
##### Flat array format ##### Flat array format
Let Let
@ -3349,8 +3346,6 @@ but with a few differences:
will also update the Mapgen VoxelManip object's internal state active on the current thread. will also update the Mapgen VoxelManip object's internal state active on the current thread.
* After modifying the Mapgen VoxelManip object's internal buffer, it may be necessary to update lighting * After modifying the Mapgen VoxelManip object's internal buffer, it may be necessary to update lighting
information using either: `VoxelManip:calc_lighting()` or `VoxelManip:set_lighting()`. information using either: `VoxelManip:calc_lighting()` or `VoxelManip:set_lighting()`.
* `VoxelManip:update_map()` does not need to be called after `write_to_map()`. The map update is performed
automatically after all on_generated callbacks have been run for that generated block.
##### Other API functions operating on a VoxelManip ##### Other API functions operating on a VoxelManip
If any VoxelManip contents were set to a liquid node, `VoxelManip:update_liquids()` must be called If any VoxelManip contents were set to a liquid node, `VoxelManip:update_liquids()` must be called
@ -3393,9 +3388,7 @@ will place the schematic inside of the VoxelManip.
* returns raw node data in the form of an array of node content IDs * returns raw node data in the form of an array of node content IDs
* if the param `buffer` is present, this table will be used to store the result instead * if the param `buffer` is present, this table will be used to store the result instead
* `set_data(data)`: Sets the data contents of the `VoxelManip` object * `set_data(data)`: Sets the data contents of the `VoxelManip` object
* `update_map()`: Update map after writing chunk back to map. * `update_map()`: Does nothing, kept for compatibility.
* To be used only by `VoxelManip` objects created by the mod itself;
not a `VoxelManip` that was retrieved from `minetest.get_mapgen_object`
* `set_lighting(light, [p1, p2])`: Set the lighting within the `VoxelManip` to a uniform value * `set_lighting(light, [p1, p2])`: Set the lighting within the `VoxelManip` to a uniform value
* `light` is a table, `{day=<0...15>, night=<0...15>}` * `light` is a table, `{day=<0...15>, night=<0...15>}`
* To be used only by a `VoxelManip` object from `minetest.get_mapgen_object` * To be used only by a `VoxelManip` object from `minetest.get_mapgen_object`

@ -235,571 +235,6 @@ void Map::setNode(v3s16 p, MapNode & n)
block->setNodeNoCheck(relpos, n); block->setNodeNoCheck(relpos, n);
} }
/*
Goes recursively through the neighbours of the node.
Alters only transparent nodes.
If the lighting of the neighbour is lower than the lighting of
the node was (before changing it to 0 at the step before), the
lighting of the neighbour is set to 0 and then the same stuff
repeats for the neighbour.
The ending nodes of the routine are stored in light_sources.
This is useful when a light is removed. In such case, this
routine can be called for the light node and then again for
light_sources to re-light the area without the removed light.
values of from_nodes are lighting values.
*/
void Map::unspreadLight(enum LightBank bank,
std::map<v3s16, u8> & from_nodes,
std::set<v3s16> & light_sources,
std::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.empty())
return;
u32 blockchangecount = 0;
std::map<v3s16, u8> unlighted_nodes;
/*
Initialize block cache
*/
v3s16 blockpos_last;
MapBlock *block = NULL;
// Cache this a bit, too
bool block_checked_in_modified = false;
for(std::map<v3s16, u8>::iterator j = from_nodes.begin();
j != from_nodes.end(); ++j)
{
v3s16 pos = j->first;
v3s16 blockpos = getNodeBlockPos(pos);
// Only fetch a new block if the block position has changed
try{
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e)
{
continue;
}
if(block->isDummy())
continue;
// Calculate relative position in block
//v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
// Get node straight from the block
//MapNode n = block->getNode(relpos);
u8 oldlight = j->second;
// Loop through 6 neighbors
for(u16 i=0; i<6; i++)
{
// Get the position of the neighbor node
v3s16 n2pos = pos + dirs[i];
// Get the block where the node is located
v3s16 blockpos, relpos;
getNodeBlockPosWithOffset(n2pos, blockpos, relpos);
// Only fetch a new block if the block position has changed
try {
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e) {
continue;
}
// Get node straight from the block
bool is_valid_position;
MapNode n2 = block->getNode(relpos, &is_valid_position);
if (!is_valid_position)
continue;
bool changed = false;
//TODO: Optimize output by optimizing light_sources?
/*
If the neighbor is dimmer than what was specified
as oldlight (the light of the previous node)
*/
if(n2.getLight(bank, m_nodedef) < oldlight)
{
/*
And the neighbor is transparent and it has some light
*/
if(m_nodedef->get(n2).light_propagates
&& n2.getLight(bank, m_nodedef) != 0)
{
/*
Set light to 0 and add to queue
*/
u8 current_light = n2.getLight(bank, m_nodedef);
n2.setLight(bank, 0, m_nodedef);
block->setNode(relpos, n2);
unlighted_nodes[n2pos] = current_light;
changed = true;
/*
Remove from light_sources if it is there
NOTE: This doesn't happen nearly at all
*/
/*if(light_sources.find(n2pos))
{
infostream<<"Removed from light_sources"<<std::endl;
light_sources.remove(n2pos);
}*/
}
/*// DEBUG
if(light_sources.find(n2pos) != NULL)
light_sources.remove(n2pos);*/
}
else{
light_sources.insert(n2pos);
}
// Add to modified_blocks
if(changed == true && block_checked_in_modified == false)
{
// If the block is not found in modified_blocks, add.
if(modified_blocks.find(blockpos) == modified_blocks.end())
{
modified_blocks[blockpos] = block;
}
block_checked_in_modified = true;
}
}
}
/*infostream<<"unspreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
if(!unlighted_nodes.empty())
unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
}
/*
Lights neighbors of from_nodes, collects all them and then
goes on recursively.
*/
void Map::spreadLight(enum LightBank bank,
std::set<v3s16> & from_nodes,
std::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.empty())
return;
u32 blockchangecount = 0;
std::set<v3s16> lighted_nodes;
/*
Initialize block cache
*/
v3s16 blockpos_last;
MapBlock *block = NULL;
// Cache this a bit, too
bool block_checked_in_modified = false;
for(std::set<v3s16>::iterator j = from_nodes.begin();
j != from_nodes.end(); ++j)
{
v3s16 pos = *j;
v3s16 blockpos, relpos;
getNodeBlockPosWithOffset(pos, blockpos, relpos);
// Only fetch a new block if the block position has changed
try {
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e) {
continue;
}
if(block->isDummy())
continue;
// Get node straight from the block
bool is_valid_position;
MapNode n = block->getNode(relpos, &is_valid_position);
u8 oldlight = is_valid_position ? n.getLight(bank, m_nodedef) : 0;
u8 newlight = diminish_light(oldlight);
// Loop through 6 neighbors
for(u16 i=0; i<6; i++){
// Get the position of the neighbor node
v3s16 n2pos = pos + dirs[i];
// Get the block where the node is located
v3s16 blockpos, relpos;
getNodeBlockPosWithOffset(n2pos, blockpos, relpos);
// Only fetch a new block if the block position has changed
try {
if(block == NULL || blockpos != blockpos_last){
block = getBlockNoCreate(blockpos);
blockpos_last = blockpos;
block_checked_in_modified = false;
blockchangecount++;
}
}
catch(InvalidPositionException &e) {
continue;
}
// Get node straight from the block
MapNode n2 = block->getNode(relpos, &is_valid_position);
if (!is_valid_position)
continue;
bool changed = false;
/*
If the neighbor is brighter than the current node,
add to list (it will light up this node on its turn)
*/
if(n2.getLight(bank, m_nodedef) > undiminish_light(oldlight))
{
lighted_nodes.insert(n2pos);
changed = true;
}
/*
If the neighbor is dimmer than how much light this node
would spread on it, add to list
*/
if(n2.getLight(bank, m_nodedef) < newlight)
{
if(m_nodedef->get(n2).light_propagates)
{
n2.setLight(bank, newlight, m_nodedef);
block->setNode(relpos, n2);
lighted_nodes.insert(n2pos);
changed = true;
}
}
// Add to modified_blocks
if(changed == true && block_checked_in_modified == false)
{
// If the block is not found in modified_blocks, add.
if(modified_blocks.find(blockpos) == modified_blocks.end())
{
modified_blocks[blockpos] = block;
}
block_checked_in_modified = true;
}
}
}
/*infostream<<"spreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
if(!lighted_nodes.empty())
spreadLight(bank, lighted_nodes, modified_blocks);
}
void Map::updateLighting(enum LightBank bank,
std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks)
{
/*m_dout<<"Map::updateLighting(): "
<<a_blocks.size()<<" blocks."<<std::endl;*/
//TimeTaker timer("updateLighting");
// For debugging
//bool debug=true;
//u32 count_was = modified_blocks.size();
//std::map<v3s16, MapBlock*> blocks_to_update;
std::set<v3s16> light_sources;
std::map<v3s16, u8> unlight_from;
int num_bottom_invalid = 0;
{
//TimeTaker t("first stuff");
for(std::map<v3s16, MapBlock*>::iterator i = a_blocks.begin();
i != a_blocks.end(); ++i)
{
MapBlock *block = i->second;
for(;;)
{
// Don't bother with dummy blocks.
if(block->isDummy())
break;
v3s16 pos = block->getPos();
v3s16 posnodes = block->getPosRelative();
modified_blocks[pos] = block;
//blocks_to_update[pos] = block;
/*
Clear all light from block
*/
for(s16 z=0; z<MAP_BLOCKSIZE; z++)
for(s16 x=0; x<MAP_BLOCKSIZE; x++)
for(s16 y=0; y<MAP_BLOCKSIZE; y++)
{
v3s16 p(x,y,z);
bool is_valid_position;
MapNode n = block->getNode(p, &is_valid_position);
if (!is_valid_position) {
/* This would happen when dealing with a
dummy block.
*/
infostream<<"updateLighting(): InvalidPositionException"
<<std::endl;
continue;
}
u8 oldlight = n.getLight(bank, m_nodedef);
n.setLight(bank, 0, m_nodedef);
block->setNode(p, n);
// If node sources light, add to list
u8 source = m_nodedef->get(n).light_source;
if(source != 0)
light_sources.insert(p + posnodes);
// Collect borders for unlighting
if((x==0 || x == MAP_BLOCKSIZE-1
|| y==0 || y == MAP_BLOCKSIZE-1
|| z==0 || z == MAP_BLOCKSIZE-1)
&& oldlight != 0)
{
v3s16 p_map = p + posnodes;
unlight_from[p_map] = oldlight;
}
}
if(bank == LIGHTBANK_DAY)
{
bool bottom_valid = block->propagateSunlight(light_sources);
if(!bottom_valid)
num_bottom_invalid++;
// If bottom is valid, we're done.
if(bottom_valid)
break;
}
else if(bank == LIGHTBANK_NIGHT)
{
// For night lighting, sunlight is not propagated
break;
}
else
{
assert("Invalid lighting bank" == NULL);
}
/*infostream<<"Bottom for sunlight-propagated block ("
<<pos.X<<","<<pos.Y<<","<<pos.Z<<") not valid"
<<std::endl;*/
// Bottom sunlight is not valid; get the block and loop to it
pos.Y--;
try{
block = getBlockNoCreate(pos);
}
catch(InvalidPositionException &e)
{
FATAL_ERROR("Invalid position");
}
}
}
}
/*
Enable this to disable proper lighting for speeding up map
generation for testing or whatever
*/
#if 0
//if(g_settings->get(""))
{
core::map<v3s16, MapBlock*>::Iterator i;
i = blocks_to_update.getIterator();
for(; i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
v3s16 p = block->getPos();
block->setLightingExpired(false);
}
return;
}
#endif
#if 1
{
//TimeTaker timer("unspreadLight");
unspreadLight(bank, unlight_from, light_sources, modified_blocks);
}
/*if(debug)
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
infostream<<"unspreadLight modified "<<diff<<std::endl;
}*/
{
//TimeTaker timer("spreadLight");
spreadLight(bank, light_sources, modified_blocks);
}
/*if(debug)
{
u32 diff = modified_blocks.size() - count_was;
count_was = modified_blocks.size();
infostream<<"spreadLight modified "<<diff<<std::endl;
}*/
#endif
#if 0
{
//MapVoxelManipulator vmanip(this);
// Make a manual voxel manipulator and load all the blocks
// that touch the requested blocks
ManualMapVoxelManipulator vmanip(this);
{
//TimeTaker timer("initialEmerge");
core::map<v3s16, MapBlock*>::Iterator i;
i = blocks_to_update.getIterator();
for(; i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
v3s16 p = block->getPos();
// Add all surrounding blocks
vmanip.initialEmerge(p - v3s16(1,1,1), p + v3s16(1,1,1));
/*
Add all surrounding blocks that have up-to-date lighting
NOTE: This doesn't quite do the job (not everything
appropriate is lighted)
*/
/*for(s16 z=-1; z<=1; z++)
for(s16 y=-1; y<=1; y++)
for(s16 x=-1; x<=1; x++)
{
v3s16 p2 = p + v3s16(x,y,z);
MapBlock *block = getBlockNoCreateNoEx(p2);
if(block == NULL)
continue;
if(block->isDummy())
continue;
if(block->getLightingExpired())
continue;
vmanip.initialEmerge(p2, p2);
}*/
// Lighting of block will be updated completely
block->setLightingExpired(false);
}
}
{
//TimeTaker timer("unSpreadLight");
vmanip.unspreadLight(bank, unlight_from, light_sources, nodemgr);
}
{
//TimeTaker timer("spreadLight");
vmanip.spreadLight(bank, light_sources, nodemgr);
}
{
//TimeTaker timer("blitBack");
vmanip.blitBack(modified_blocks);
}
/*infostream<<"emerge_time="<<emerge_time<<std::endl;
emerge_time = 0;*/
}
#endif
//m_dout<<"Done ("<<getTimestamp()<<")"<<std::endl;
}
void Map::updateLighting(std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks)
{
updateLighting(LIGHTBANK_DAY, a_blocks, modified_blocks);
updateLighting(LIGHTBANK_NIGHT, a_blocks, modified_blocks);
/*
Update information about whether day and night light differ
*/
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
{
MapBlock *block = i->second;
block->expireDayNightDiff();
}
}
void Map::addNodeAndUpdate(v3s16 p, MapNode n, void Map::addNodeAndUpdate(v3s16 p, MapNode n,
std::map<v3s16, MapBlock*> &modified_blocks, std::map<v3s16, MapBlock*> &modified_blocks,
bool remove_metadata) bool remove_metadata)

@ -208,22 +208,6 @@ public:
// position is valid, otherwise false // position is valid, otherwise false
MapNode getNodeNoEx(v3s16 p, bool *is_valid_position = NULL); MapNode getNodeNoEx(v3s16 p, bool *is_valid_position = NULL);
void unspreadLight(enum LightBank bank,
std::map<v3s16, u8> & from_nodes,
std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks);
void spreadLight(enum LightBank bank,
std::set<v3s16> & from_nodes,
std::map<v3s16, MapBlock*> & modified_blocks);
void updateLighting(enum LightBank bank,
std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks);
void updateLighting(std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks);
/* /*
These handle lighting but not faces. These handle lighting but not faces.
*/ */

@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/serialize.h" #include "util/serialize.h"
#include "serialization.h" #include "serialization.h"
#include "filesys.h" #include "filesys.h"
#include "voxelalgorithms.h"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -202,7 +203,7 @@ bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags,
return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1))); return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1)));
} }
void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags, void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags,
Rotation rot, bool force_place) Rotation rot, bool force_place)
{ {
std::map<v3s16, MapBlock *> lighting_modified_blocks; std::map<v3s16, MapBlock *> lighting_modified_blocks;
@ -238,15 +239,10 @@ void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags,
blitToVManip(&vm, p, rot, force_place); blitToVManip(&vm, p, rot, force_place);
vm.blitBackAll(&modified_blocks); voxalgo::blit_back_with_light(map, &vm, &modified_blocks);
//// Carry out post-map-modification actions //// Carry out post-map-modification actions
//// Update lighting
// TODO: Optimize this by using Mapgen::calcLighting() instead
lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end());
map->updateLighting(lighting_modified_blocks, modified_blocks);
//// Create & dispatch map modification events to observers //// Create & dispatch map modification events to observers
MapEditEvent event; MapEditEvent event;
event.type = MEET_OTHER; event.type = MEET_OTHER;

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h" #include "util/string.h"
class Map; class Map;
class ServerMap;
class Mapgen; class Mapgen;
class MMVManip; class MMVManip;
class PseudoRandom; class PseudoRandom;
@ -108,7 +109,7 @@ public:
void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place); void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place);
bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place);
void placeOnMap(Map *map, v3s16 p, u32 flags, Rotation rot, bool force_place); void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place);
void applyProbabilities(v3s16 p0, void applyProbabilities(v3s16 p0,
std::vector<std::pair<v3s16, u8> > *plist, std::vector<std::pair<v3s16, u8> > *plist,

@ -1357,7 +1357,9 @@ int ModApiMapgen::l_place_schematic(lua_State *L)
{ {
MAP_LOCK_REQUIRED; MAP_LOCK_REQUIRED;
Map *map = &(getEnv(L)->getMap()); GET_ENV_PTR;
ServerMap *map = &(env->getServerMap());
SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr;
//// Read position //// Read position

@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "map.h" #include "map.h"
#include "server.h" #include "server.h"
#include "mapgen.h" #include "mapgen.h"
#include "voxelalgorithms.h"
// garbage collector // garbage collector
int LuaVoxelManip::gc_object(lua_State *L) int LuaVoxelManip::gc_object(lua_State *L)
@ -109,10 +110,24 @@ int LuaVoxelManip::l_write_to_map(lua_State *L)
MAP_LOCK_REQUIRED; MAP_LOCK_REQUIRED;
LuaVoxelManip *o = checkobject(L, 1); LuaVoxelManip *o = checkobject(L, 1);
MMVManip *vm = o->vm; GET_ENV_PTR;
ServerMap *map = &(env->getServerMap());
if (o->is_mapgen_vm) {
o->vm->blitBackAll(&(o->modified_blocks));
} else {
voxalgo::blit_back_with_light(map, o->vm,
&(o->modified_blocks));
}
vm->blitBackAll(&o->modified_blocks); MapEditEvent event;
event.type = MEET_OTHER;
for (std::map<v3s16, MapBlock *>::iterator it = o->modified_blocks.begin();
it != o->modified_blocks.end(); ++it)
event.modified_blocks.insert(it->first);
map->dispatchEvent(&event);
o->modified_blocks.clear();
return 0; return 0;
} }
@ -322,33 +337,6 @@ int LuaVoxelManip::l_set_param2_data(lua_State *L)
int LuaVoxelManip::l_update_map(lua_State *L) int LuaVoxelManip::l_update_map(lua_State *L)
{ {
GET_ENV_PTR;
LuaVoxelManip *o = checkobject(L, 1);
if (o->is_mapgen_vm)
return 0;
Map *map = &(env->getMap());
// TODO: Optimize this by using Mapgen::calcLighting() instead
std::map<v3s16, MapBlock *> lighting_mblocks;
std::map<v3s16, MapBlock *> *mblocks = &o->modified_blocks;
lighting_mblocks.insert(mblocks->begin(), mblocks->end());
map->updateLighting(lighting_mblocks, *mblocks);
MapEditEvent event;
event.type = MEET_OTHER;
for (std::map<v3s16, MapBlock *>::iterator
it = mblocks->begin();
it != mblocks->end(); ++it)
event.modified_blocks.insert(it->first);
map->dispatchEvent(&event);
mblocks->clear();
return 0; return 0;
} }

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serverenvironment.h" #include "serverenvironment.h"
#include "nodedef.h" #include "nodedef.h"
#include "treegen.h" #include "treegen.h"
#include "voxelalgorithms.h"
namespace treegen namespace treegen
{ {
@ -125,12 +126,8 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
if (e != SUCCESS) if (e != SUCCESS)
return e; return e;
vmanip.blitBackAll(&modified_blocks); voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks);
// update lighting
std::map<v3s16, MapBlock*> lighting_modified_blocks;
lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end());
map->updateLighting(lighting_modified_blocks, modified_blocks);
// Send a MEET_OTHER event // Send a MEET_OTHER event
MapEditEvent event; MapEditEvent event;
event.type = MEET_OTHER; event.type = MEET_OTHER;

@ -542,6 +542,21 @@ void spread_light(Map *map, INodeDefManager *nodemgr, LightBank bank,
} }
} }
struct SunlightPropagationUnit{
v2s16 relative_pos;
bool is_sunlit;
SunlightPropagationUnit(v2s16 relpos, bool sunlit):
relative_pos(relpos),
is_sunlit(sunlit)
{}
};
struct SunlightPropagationData{
std::vector<SunlightPropagationUnit> data;
v3s16 target_block;
};
/*! /*!
* Returns true if the node gets sunlight from the * Returns true if the node gets sunlight from the
* node above it. * node above it.
@ -753,7 +768,7 @@ void update_lighting_nodes(Map *map,
for (u8 i = 0; i <= LIGHT_SUN; i++) { for (u8 i = 0; i <= LIGHT_SUN; i++) {
const std::vector<ChangingLight> &lights = light_sources.lights[i]; const std::vector<ChangingLight> &lights = light_sources.lights[i];
for (std::vector<ChangingLight>::const_iterator it = lights.begin(); for (std::vector<ChangingLight>::const_iterator it = lights.begin();
it < lights.end(); it++) { it < lights.end(); ++it) {
MapNode n = it->block->getNodeNoCheck(it->rel_position, MapNode n = it->block->getNodeNoCheck(it->rel_position,
&is_valid_position); &is_valid_position);
n.setLight(bank, i, ndef); n.setLight(bank, i, ndef);
@ -817,8 +832,10 @@ void update_block_border_lighting(Map *map, MapBlock *block,
bool is_valid_position; bool is_valid_position;
for (s32 i = 0; i < 2; i++) { for (s32 i = 0; i < 2; i++) {
LightBank bank = banks[i]; LightBank bank = banks[i];
UnlightQueue disappearing_lights(256); // Since invalid light is not common, do not allocate
ReLightQueue light_sources(256); // memory if not needed.
UnlightQueue disappearing_lights(0);
ReLightQueue light_sources(0);
// Get incorrect lights // Get incorrect lights
for (direction d = 0; d < 6; d++) { for (direction d = 0; d < 6; d++) {
// For each direction // For each direction
@ -873,7 +890,7 @@ void update_block_border_lighting(Map *map, MapBlock *block,
for (u8 i = 0; i <= LIGHT_SUN; i++) { for (u8 i = 0; i <= LIGHT_SUN; i++) {
const std::vector<ChangingLight> &lights = light_sources.lights[i]; const std::vector<ChangingLight> &lights = light_sources.lights[i];
for (std::vector<ChangingLight>::const_iterator it = lights.begin(); for (std::vector<ChangingLight>::const_iterator it = lights.begin();
it < lights.end(); it++) { it < lights.end(); ++it) {
MapNode n = it->block->getNodeNoCheck(it->rel_position, MapNode n = it->block->getNodeNoCheck(it->rel_position,
&is_valid_position); &is_valid_position);
n.setLight(bank, i, ndef); n.setLight(bank, i, ndef);
@ -885,6 +902,352 @@ void update_block_border_lighting(Map *map, MapBlock *block,
} }
} }
/*!
* Resets the lighting of the given VoxelManipulator to
* complete darkness and full sunlight.
* Operates in one map sector.
*
* \param offset contains the least x and z node coordinates
* of the map sector.
* \param light incoming sunlight, light[x][z] is true if there
* is sunlight above the voxel manipulator at the given x-z coordinates.
* The array's indices are relative node coordinates in the sector.
* After the procedure returns, this contains outgoing light at
* the bottom of the voxel manipulator.
*/
void fill_with_sunlight(MMVManip *vm, INodeDefManager *ndef, v2s16 offset,
bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE])
{
// Distance in array between two nodes on top of each other.
s16 ystride = vm->m_area.getExtent().X;
// Cache the ignore node.
MapNode ignore = MapNode(CONTENT_IGNORE);
// For each column of nodes:
for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
// Position of the column on the map.
v2s16 realpos = offset + v2s16(x, z);
// Array indices in the voxel manipulator
s32 maxindex = vm->m_area.index(realpos.X, vm->m_area.MaxEdge.Y,
realpos.Y);
s32 minindex = vm->m_area.index(realpos.X, vm->m_area.MinEdge.Y,
realpos.Y);
// True if the current node has sunlight.
bool lig = light[z][x];
// For each node, downwards:
for (s32 i = maxindex; i >= minindex; i -= ystride) {
MapNode *n;
if (vm->m_flags[i] & VOXELFLAG_NO_DATA)
n = &ignore;
else
n = &vm->m_data[i];
// Ignore IGNORE nodes, these are not generated yet.
if(n->getContent() == CONTENT_IGNORE)
continue;
const ContentFeatures &f = ndef->get(n->getContent());
if (lig && !f.sunlight_propagates)
// Sunlight is stopped.
lig = false;
// Reset light
n->setLight(LIGHTBANK_DAY, lig ? 15 : 0, f);
n->setLight(LIGHTBANK_NIGHT, 0, f);
}
// Output outgoing light.
light[z][x] = lig;
}
}
/*!
* Returns incoming sunlight for one map block.
* If block above is not found, it is loaded.
*
* \param pos position of the map block that gets the sunlight.
* \param light incoming sunlight, light[z][x] is true if there
* is sunlight above the block at the given z-x relative
* node coordinates.
*/
void is_sunlight_above_block(ServerMap *map, mapblock_v3 pos,
INodeDefManager *ndef, bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE])
{
mapblock_v3 source_block_pos = pos + v3s16(0, 1, 0);
// Get or load source block.
// It might take a while to load, but correcting incorrect
// sunlight may be even slower.
MapBlock *source_block = map->emergeBlock(source_block_pos, false);
// Trust only generated blocks.
if (source_block == NULL || source_block->isDummy()
|| !source_block->isGenerated()) {
// But if there is no block above, then use heuristics
bool sunlight = true;
MapBlock *node_block = map->getBlockNoCreateNoEx(pos);
if (node_block == NULL)
// This should not happen.
sunlight = false;
else
sunlight = !node_block->getIsUnderground();
for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
light[z][x] = sunlight;
} else {
// Dummy boolean, the position is valid.
bool is_valid_position;
// For each column:
for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
// Get the bottom block.
MapNode above = source_block->getNodeNoCheck(x, 0, z,
&is_valid_position);
light[z][x] = above.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN;
}
}
}
/*!
* Propagates sunlight down in a given map block.
*
* \param data contains incoming sunlight and shadow and
* the coordinates of the target block.
* \param unlight propagated shadow is inserted here
* \param relight propagated sunlight is inserted here
*
* \returns true if the block was modified, false otherwise.
*/
bool propagate_block_sunlight(Map *map, INodeDefManager *ndef,
SunlightPropagationData *data, UnlightQueue *unlight, ReLightQueue *relight)
{
bool modified = false;
// Get the block.
MapBlock *block = map->getBlockNoCreateNoEx(data->target_block);
if (block == NULL || block->isDummy()) {
// The work is done if the block does not contain data.
data->data.clear();
return false;
}
// Dummy boolean
bool is_valid;
// For each changing column of nodes:
size_t index;
for (index = 0; index < data->data.size(); index++) {
SunlightPropagationUnit it = data->data[index];
// Relative position of the currently inspected node.
relative_v3 current_pos(it.relative_pos.X, MAP_BLOCKSIZE - 1,
it.relative_pos.Y);
if (it.is_sunlit) {
// Propagate sunlight.
// For each node downwards:
for (; current_pos.Y >= 0; current_pos.Y--) {
MapNode n = block->getNodeNoCheck(current_pos, &is_valid);
const ContentFeatures &f = ndef->get(n);
if (n.getLightRaw(LIGHTBANK_DAY, f) < LIGHT_SUN
&& f.sunlight_propagates) {
// This node gets sunlight.
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, f);
block->setNodeNoCheck(current_pos, n);
modified = true;
relight->push(LIGHT_SUN, current_pos, data->target_block,
block, 4);
} else {
// Light already valid, propagation stopped.
break;
}
}
} else {
// Propagate shadow.
// For each node downwards:
for (; current_pos.Y >= 0; current_pos.Y--) {
MapNode n = block->getNodeNoCheck(current_pos, &is_valid);
const ContentFeatures &f = ndef->get(n);
if (n.getLightRaw(LIGHTBANK_DAY, f) == LIGHT_SUN) {
// The sunlight is no longer valid.
n.setLight(LIGHTBANK_DAY, 0, f);
block->setNodeNoCheck(current_pos, n);
modified = true;
unlight->push(LIGHT_SUN, current_pos, data->target_block,
block, 4);
} else {
// Reached shadow, propagation stopped.
break;
}
}
}
if (current_pos.Y >= 0) {
// Propagation stopped, remove from data.
data->data[index] = data->data.back();
data->data.pop_back();
index--;
}
}
return modified;
}
/*!
* Borders of a map block in relative node coordinates.
* The areas do not overlap.
* Compatible with type 'direction'.
*/
const VoxelArea block_pad[] = {
VoxelArea(v3s16(15, 0, 0), v3s16(15, 15, 15)), //X+
VoxelArea(v3s16(1, 15, 0), v3s16(14, 15, 15)), //Y+
VoxelArea(v3s16(1, 1, 15), v3s16(14, 14, 15)), //Z+
VoxelArea(v3s16(1, 1, 0), v3s16(14, 14, 0)), //Z-
VoxelArea(v3s16(1, 0, 0), v3s16(14, 0, 15)), //Y-
VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X-
};
void blit_back_with_light(ServerMap *map, MMVManip *vm,
std::map<v3s16, MapBlock*> *modified_blocks)
{
INodeDefManager *ndef = map->getNodeDefManager();
mapblock_v3 minblock = getNodeBlockPos(vm->m_area.MinEdge);
mapblock_v3 maxblock = getNodeBlockPos(vm->m_area.MaxEdge);
// First queue is for day light, second is for night light.
UnlightQueue unlight[] = { UnlightQueue(256), UnlightQueue(256) };
ReLightQueue relight[] = { ReLightQueue(256), ReLightQueue(256) };
// Will hold sunlight data.
bool lights[MAP_BLOCKSIZE][MAP_BLOCKSIZE];
SunlightPropagationData data;
// Dummy boolean.
bool is_valid;
// --- STEP 1: reset everything to sunlight
// For each map block:
for (s16 x = minblock.X; x <= maxblock.X; x++)
for (s16 z = minblock.Z; z <= maxblock.Z; z++) {
// Extract sunlight above.
is_sunlight_above_block(map, v3s16(x, maxblock.Y, z), ndef, lights);
v2s16 offset(x, z);
offset *= MAP_BLOCKSIZE;
// Reset the voxel manipulator.
fill_with_sunlight(vm, ndef, offset, lights);
// Copy sunlight data
data.target_block = v3s16(x, minblock.Y - 1, z);
for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
data.data.push_back(
SunlightPropagationUnit(v2s16(x, z), lights[z][x]));
// Propagate sunlight and shadow below the voxel manipulator.
while (!data.data.empty()) {
if (propagate_block_sunlight(map, ndef, &data, &unlight[0],
&relight[0]))
(*modified_blocks)[data.target_block] =
map->getBlockNoCreateNoEx(data.target_block);
// Step downwards.
data.target_block.Y--;
}
}
// --- STEP 2: Get nodes from borders to unlight
// In case there are unloaded holes in the voxel manipulator
// unlight each block.
// For each block:
for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++)
for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++)
for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) {
v3s16 blockpos(b_x, b_y, b_z);
MapBlock *block = map->getBlockNoCreateNoEx(blockpos);
if (!block || block->isDummy())
// Skip not existing blocks.
continue;
v3s16 offset = block->getPosRelative();
// For each border of the block:
for (direction d = 0; d < 6; d++) {
VoxelArea a = block_pad[d];
// For each node of the border:
for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++)
for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++)
for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
v3s16 relpos(x, y, z);
// Get old and new node
MapNode oldnode = block->getNodeNoCheck(x, y, z, &is_valid);
const ContentFeatures &oldf = ndef->get(oldnode);
MapNode newnode = vm->getNodeNoExNoEmerge(relpos + offset);
const ContentFeatures &newf = ndef->get(newnode);
// For each light bank
for (size_t b = 0; b < 2; b++) {
LightBank bank = banks[b];
u8 oldlight = oldf.param_type == CPT_LIGHT ?
oldnode.getLightNoChecks(bank, &oldf):
LIGHT_SUN; // no light information, force unlighting
u8 newlight = newf.param_type == CPT_LIGHT ?
newnode.getLightNoChecks(bank, &newf):
newf.light_source;
// If the new node is dimmer, unlight.
if (oldlight > newlight) {
unlight[b].push(
oldlight, relpos, blockpos, block, 6);
}
} // end of banks
} // end of nodes
} // end of borders
} // end of blocks
// --- STEP 3: All information extracted, overwrite
vm->blitBackAll(modified_blocks, true);
// --- STEP 4: Do unlighting
for (size_t bank = 0; bank < 2; bank++) {
LightBank b = banks[bank];
unspread_light(map, ndef, b, unlight[bank], relight[bank],
*modified_blocks);
}
// --- STEP 5: Get all newly inserted light sources
// For each block:
for (s16 b_x = minblock.X; b_x <= maxblock.X; b_x++)
for (s16 b_y = minblock.Y; b_y <= maxblock.Y; b_y++)
for (s16 b_z = minblock.Z; b_z <= maxblock.Z; b_z++) {
v3s16 blockpos(b_x, b_y, b_z);
MapBlock *block = map->getBlockNoCreateNoEx(blockpos);
if (!block || block->isDummy())
// Skip not existing blocks
continue;
// For each node in the block:
for (s32 x = 0; x < MAP_BLOCKSIZE; x++)
for (s32 z = 0; z < MAP_BLOCKSIZE; z++)
for (s32 y = 0; y < MAP_BLOCKSIZE; y++) {
v3s16 relpos(x, y, z);
MapNode node = block->getNodeNoCheck(x, y, z, &is_valid);
const ContentFeatures &f = ndef->get(node);
// For each light bank
for (size_t b = 0; b < 2; b++) {
LightBank bank = banks[b];
u8 light = f.param_type == CPT_LIGHT ?
node.getLightNoChecks(bank, &f):
f.light_source;
if (light > 1)
relight[b].push(light, relpos, blockpos, block, 6);
} // end of banks
} // end of nodes
} // end of blocks
// --- STEP 6: do light spreading
// For each light bank:
for (size_t b = 0; b < 2; b++) {
LightBank bank = banks[b];
// Sunlight is already initialized.
u8 maxlight = (b == 0) ? LIGHT_MAX : LIGHT_SUN;
// Initialize light values for light spreading.
for (u8 i = 0; i <= maxlight; i++) {
const std::vector<ChangingLight> &lights = relight[b].lights[i];
for (std::vector<ChangingLight>::const_iterator it = lights.begin();
it < lights.end(); ++it) {
MapNode n = it->block->getNodeNoCheck(it->rel_position,
&is_valid);
n.setLight(bank, i, ndef);
it->block->setNodeNoCheck(it->rel_position, n);
}
}
// Spread lights.
spread_light(map, ndef, bank, relight[b], *modified_blocks);
}
}
VoxelLineIterator::VoxelLineIterator( VoxelLineIterator::VoxelLineIterator(
const v3f &start_position, const v3f &start_position,
const v3f &line_vector) : const v3f &line_vector) :

@ -26,7 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/cpp11_container.h" #include "util/cpp11_container.h"
class Map; class Map;
class ServerMap;
class MapBlock; class MapBlock;
class MMVManip;
namespace voxalgo namespace voxalgo
{ {
@ -84,6 +86,17 @@ void update_lighting_nodes(
void update_block_border_lighting(Map *map, MapBlock *block, void update_block_border_lighting(Map *map, MapBlock *block,
std::map<v3s16, MapBlock*> &modified_blocks); std::map<v3s16, MapBlock*> &modified_blocks);
/*!
* Copies back nodes from a voxel manipulator
* to the map and updates lighting.
* For server use only.
*
* \param modified_blocks output, contains all map blocks that
* the function modified
*/
void blit_back_with_light(ServerMap *map, MMVManip *vm,
std::map<v3s16, MapBlock*> *modified_blocks);
/*! /*!
* This class iterates trough voxels that intersect with * This class iterates trough voxels that intersect with
* a line. The collision detection does not see nodeboxes, * a line. The collision detection does not see nodeboxes,