Improved lighting

This commit rewrites the procedure that is responsible for light
updating.

this commit
-provides iterative solutions for unlighting and light spreading
-introduces a new priority queue-like container for the iteration
-creates per-node MapBlock caching to reduce retrieving MapBlocks from
the map
-calculates with map block positions and in-block relative node
coordinates
-skips light updating if it is not necessary since the node's new light
will be the same as its old light was
This commit is contained in:
Dániel Juhász 2016-10-20 21:41:38 +02:00 committed by Ner'zhul
parent 1fd9a07497
commit c071efaa43
6 changed files with 648 additions and 448 deletions

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h" #include "mapblock.h"
#include "filesys.h" #include "filesys.h"
#include "voxel.h" #include "voxel.h"
#include "voxelalgorithms.h"
#include "porting.h" #include "porting.h"
#include "serialization.h" #include "serialization.h"
#include "nodemetadata.h" #include "nodemetadata.h"
@ -234,7 +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. Goes recursively through the neighbours of the node.
@ -413,20 +413,6 @@ void Map::unspreadLight(enum LightBank bank,
unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks); unspreadLight(bank, unlighted_nodes, light_sources, modified_blocks);
} }
/*
A single-node wrapper of the above
*/
void Map::unLightNeighbors(enum LightBank bank,
v3s16 pos, u8 lightwas,
std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks)
{
std::map<v3s16, u8> from_nodes;
from_nodes[pos] = lightwas;
unspreadLight(bank, from_nodes, light_sources, modified_blocks);
}
/* /*
Lights neighbors of from_nodes, collects all them and then Lights neighbors of from_nodes, collects all them and then
goes on recursively. goes on recursively.
@ -568,108 +554,6 @@ void Map::spreadLight(enum LightBank bank,
spreadLight(bank, lighted_nodes, modified_blocks); spreadLight(bank, lighted_nodes, modified_blocks);
} }
/*
A single-node source variation of the above.
*/
void Map::lightNeighbors(enum LightBank bank,
v3s16 pos,
std::map<v3s16, MapBlock*> & modified_blocks)
{
std::set<v3s16> from_nodes;
from_nodes.insert(pos);
spreadLight(bank, from_nodes, modified_blocks);
}
v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
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;
bool is_valid_position;
n2 = getNodeNoEx(n2pos, &is_valid_position);
if (!is_valid_position)
continue;
if(n2.getLight(bank, nodemgr) > brightest_light || found_something == false){
brightest_light = n2.getLight(bank, nodemgr);
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.
Mud is turned into grass in where the sunlight stops.
*/
s16 Map::propagateSunlight(v3s16 start,
std::map<v3s16, MapBlock*> & modified_blocks)
{
INodeDefManager *nodemgr = m_gamedef->ndef();
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;
bool is_valid_position;
MapNode n = block->getNode(relpos, &is_valid_position);
if (!is_valid_position)
break;
if(nodemgr->get(n).sunlight_propagates)
{
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
block->setNode(relpos, n);
modified_blocks[blockpos] = block;
}
else
{
// Sunlight goes no further
break;
}
}
return y + 1;
}
void Map::updateLighting(enum LightBank bank, void Map::updateLighting(enum LightBank bank,
std::map<v3s16, MapBlock*> & a_blocks, std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks) std::map<v3s16, MapBlock*> & modified_blocks)
@ -922,150 +806,29 @@ void Map::updateLighting(std::map<v3s16, MapBlock*> & a_blocks,
} }
} }
/*
*/
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)
{ {
INodeDefManager *ndef = m_gamedef->ndef(); INodeDefManager *ndef = m_gamedef->ndef();
/*PrintInfo(m_dout); // Collect old node for rollback
m_dout<<"Map::addNodeAndUpdate(): p=("
<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
/*
From this node to nodes underneath:
If lighting is sunlight (1.0), unlight neighbours and
set lighting to 0.
Else discontinue.
*/
v3s16 toppos = p + v3s16(0,1,0);
//v3s16 bottompos = p + v3s16(0,-1,0);
bool node_under_sunlight = true;
std::set<v3s16> light_sources;
/*
Collect old node for rollback
*/
RollbackNode rollback_oldnode(this, p, m_gamedef); RollbackNode rollback_oldnode(this, p, m_gamedef);
/* // This is needed for updating the lighting
If there is a node at top and it doesn't have sunlight, MapNode oldnode = getNodeNoEx(p);
there has not been any sunlight going down.
Otherwise there probably is. // Remove node metadata
*/
bool is_valid_position;
MapNode topnode = getNodeNoEx(toppos, &is_valid_position);
if(is_valid_position && topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
node_under_sunlight = false;
/*
Remove all light that has come out of this node
*/
enum LightBank banks[] =
{
LIGHTBANK_DAY,
LIGHTBANK_NIGHT
};
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
u8 lightwas = getNodeNoEx(p).getLight(bank, ndef);
// Add the block of the added node to modified_blocks
v3s16 blockpos = getNodeBlockPos(p);
MapBlock * block = getBlockNoCreate(blockpos);
assert(block != NULL);
modified_blocks[blockpos] = block;
assert(isValidPosition(p));
// 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(bank, p, lightwas, light_sources, modified_blocks);
n.setLight(bank, 0, ndef);
}
/*
If node lets sunlight through and is under sunlight, it has
sunlight too.
*/
if(node_under_sunlight && ndef->get(n).sunlight_propagates)
{
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef);
}
/*
Remove node metadata
*/
if (remove_metadata) { if (remove_metadata) {
removeNodeMetadata(p); removeNodeMetadata(p);
} }
/* // Set the node on the map
Set the node on the map
*/
setNode(p, n); setNode(p, n);
/* // Update lighting
If node is under sunlight and doesn't let sunlight through, voxalgo::update_lighting_node(this, ndef, p, oldnode, modified_blocks);
take all sunlighted nodes under it and clear light from them
and from where the light has been spread.
TODO: This could be optimized by mass-unlighting instead
of looping
*/
if(node_under_sunlight && !ndef->get(n).sunlight_propagates)
{
s16 y = p.Y - 1;
for(;; y--){
//m_dout<<"y="<<y<<std::endl;
v3s16 n2pos(p.X, y, p.Z);
MapNode n2;
n2 = getNodeNoEx(n2pos, &is_valid_position);
if (!is_valid_position)
break;
if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
{
unLightNeighbors(LIGHTBANK_DAY,
n2pos, n2.getLight(LIGHTBANK_DAY, ndef),
light_sources, modified_blocks);
n2.setLight(LIGHTBANK_DAY, 0, ndef);
setNode(n2pos, n2);
}
else
break;
}
}
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
/*
Spread light from all nodes that might be capable of doing so
*/
spreadLight(bank, light_sources, modified_blocks);
}
/*
Update information about whether day and night light differ
*/
for(std::map<v3s16, MapBlock*>::iterator for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin(); i = modified_blocks.begin();
i != modified_blocks.end(); ++i) i != modified_blocks.end(); ++i)
@ -1073,187 +836,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
i->second->expireDayNightDiff(); i->second->expireDayNightDiff();
} }
/* // Report for rollback
Report for rollback
*/
if(m_gamedef->rollback())
{
RollbackNode rollback_newnode(this, p, m_gamedef);
RollbackAction action;
action.setSetNode(p, rollback_oldnode, rollback_newnode);
m_gamedef->rollback()->reportAction(action);
}
/*
Add neighboring liquid nodes and the node itself if it is
liquid (=water node was added) to transform queue.
*/
v3s16 dirs[7] = {
v3s16(0,0,0), // self
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
};
for(u16 i=0; i<7; i++)
{
v3s16 p2 = p + dirs[i];
MapNode n2 = getNodeNoEx(p2, &is_valid_position);
if(is_valid_position
&& (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR))
{
m_transforming_liquid.push_back(p2);
}
}
}
/*
*/
void Map::removeNodeAndUpdate(v3s16 p,
std::map<v3s16, MapBlock*> &modified_blocks)
{
INodeDefManager *ndef = m_gamedef->ndef();
/*PrintInfo(m_dout);
m_dout<<"Map::removeNodeAndUpdate(): p=("
<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
bool node_under_sunlight = true;
v3s16 toppos = p + v3s16(0,1,0);
// Node will be replaced with this
content_t replace_material = CONTENT_AIR;
/*
Collect old node for rollback
*/
RollbackNode rollback_oldnode(this, p, m_gamedef);
/*
If there is a node at top and it doesn't have sunlight,
there will be no sunlight going down.
*/
bool is_valid_position;
MapNode topnode = getNodeNoEx(toppos, &is_valid_position);
if(is_valid_position && topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
node_under_sunlight = false;
std::set<v3s16> light_sources;
enum LightBank banks[] =
{
LIGHTBANK_DAY,
LIGHTBANK_NIGHT
};
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
/*
Unlight neighbors (in case the node is a light source)
*/
unLightNeighbors(bank, p,
getNodeNoEx(p).getLight(bank, ndef),
light_sources, modified_blocks);
}
/*
Remove node metadata
*/
removeNodeMetadata(p);
/*
Remove the node.
This also clears the lighting.
*/
MapNode n(replace_material);
setNode(p, n);
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
/*
Recalculate lighting
*/
spreadLight(bank, 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[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<<"Node was under sunlight. "
"Propagating sunlight";
m_dout<<" -> ybottom="<<ybottom<<std::endl;*/
s16 y = p.Y;
for(; y >= ybottom; y--)
{
v3s16 p2(p.X, y, p.Z);
/*m_dout<<"lighting neighbors of node ("
<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
<<std::endl;*/
lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks);
}
}
else
{
// Set the lighting of this node to 0
// TODO: Is this needed? Lighting is cleared up there already.
MapNode n = getNodeNoEx(p, &is_valid_position);
if (is_valid_position) {
n.setLight(LIGHTBANK_DAY, 0, ndef);
setNode(p, n);
} else {
FATAL_ERROR("Invalid position");
}
}
for(s32 i=0; i<2; i++)
{
enum LightBank bank = banks[i];
// Get the brightest neighbour node and propagate light from it
v3s16 n2p = getBrightestNeighbour(bank, p);
try{
//MapNode n2 = getNode(n2p);
lightNeighbors(bank, n2p, modified_blocks);
}
catch(InvalidPositionException &e)
{
}
}
/*
Update information about whether day and night light differ
*/
for(std::map<v3s16, MapBlock*>::iterator
i = modified_blocks.begin();
i != modified_blocks.end(); ++i)
{
i->second->expireDayNightDiff();
}
/*
Report for rollback
*/
if(m_gamedef->rollback()) if(m_gamedef->rollback())
{ {
RollbackNode rollback_newnode(this, p, m_gamedef); RollbackNode rollback_newnode(this, p, m_gamedef);
@ -1264,8 +847,8 @@ void Map::removeNodeAndUpdate(v3s16 p,
/* /*
Add neighboring liquid nodes and this node to transform queue. Add neighboring liquid nodes and this node to transform queue.
(it's vital for the node itself to get updated last.) (it's vital for the node itself to get updated last, if it was removed.)
*/ */
v3s16 dirs[7] = { v3s16 dirs[7] = {
v3s16(0,0,1), // back v3s16(0,0,1), // back
v3s16(0,1,0), // top v3s16(0,1,0), // top
@ -1279,9 +862,9 @@ void Map::removeNodeAndUpdate(v3s16 p,
{ {
v3s16 p2 = p + dirs[i]; v3s16 p2 = p + dirs[i];
bool is_position_valid; bool is_valid_position;
MapNode n2 = getNodeNoEx(p2, &is_position_valid); MapNode n2 = getNodeNoEx(p2, &is_valid_position);
if (is_position_valid if(is_valid_position
&& (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)) && (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR))
{ {
m_transforming_liquid.push_back(p2); m_transforming_liquid.push_back(p2);
@ -1289,6 +872,12 @@ void Map::removeNodeAndUpdate(v3s16 p,
} }
} }
void Map::removeNodeAndUpdate(v3s16 p,
std::map<v3s16, MapBlock*> &modified_blocks)
{
addNodeAndUpdate(p, MapNode(CONTENT_AIR), modified_blocks, true);
}
bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata) bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata)
{ {
MapEditEvent event; MapEditEvent event;

@ -211,24 +211,10 @@ public:
std::set<v3s16> & light_sources, std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks); std::map<v3s16, MapBlock*> & modified_blocks);
void unLightNeighbors(enum LightBank bank,
v3s16 pos, u8 lightwas,
std::set<v3s16> & light_sources,
std::map<v3s16, MapBlock*> & modified_blocks);
void spreadLight(enum LightBank bank, void spreadLight(enum LightBank bank,
std::set<v3s16> & from_nodes, std::set<v3s16> & from_nodes,
std::map<v3s16, MapBlock*> & modified_blocks); std::map<v3s16, MapBlock*> & modified_blocks);
void lightNeighbors(enum LightBank bank,
v3s16 pos,
std::map<v3s16, MapBlock*> & modified_blocks);
v3s16 getBrightestNeighbour(enum LightBank bank, v3s16 p);
s16 propagateSunlight(v3s16 start,
std::map<v3s16, MapBlock*> & modified_blocks);
void updateLighting(enum LightBank bank, void updateLighting(enum LightBank bank,
std::map<v3s16, MapBlock*> & a_blocks, std::map<v3s16, MapBlock*> & a_blocks,
std::map<v3s16, MapBlock*> & modified_blocks); std::map<v3s16, MapBlock*> & modified_blocks);

@ -54,10 +54,10 @@ MapNode::MapNode(INodeDefManager *ndef, const std::string &name,
param2 = a_param2; param2 = a_param2;
} }
void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr) void MapNode::setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f)
{ {
// If node doesn't contain light data, ignore this // If node doesn't contain light data, ignore this
if(nodemgr->get(*this).param_type != CPT_LIGHT) if(f.param_type != CPT_LIGHT)
return; return;
if(bank == LIGHTBANK_DAY) if(bank == LIGHTBANK_DAY)
{ {
@ -73,6 +73,11 @@ void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr
assert("Invalid light bank" == NULL); assert("Invalid light bank" == NULL);
} }
void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr)
{
setLight(bank, a_light, nodemgr->get(*this));
}
bool MapNode::isLightDayNightEq(INodeDefManager *nodemgr) const bool MapNode::isLightDayNightEq(INodeDefManager *nodemgr) const
{ {
const ContentFeatures &f = nodemgr->get(*this); const ContentFeatures &f = nodemgr->get(*this);
@ -103,6 +108,13 @@ u8 MapNode::getLight(enum LightBank bank, INodeDefManager *nodemgr) const
return MYMAX(f.light_source, light); return MYMAX(f.light_source, light);
} }
u8 MapNode::getLightRaw(enum LightBank bank, const ContentFeatures &f) const
{
if(f.param_type == CPT_LIGHT)
return bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f;
return 0;
}
u8 MapNode::getLightNoChecks(enum LightBank bank, const ContentFeatures *f) const u8 MapNode::getLightNoChecks(enum LightBank bank, const ContentFeatures *f) const
{ {
return MYMAX(f->light_source, return MYMAX(f->light_source,

@ -191,6 +191,8 @@ struct MapNode
param2 = p; param2 = p;
} }
void setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f);
void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr); void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr);
/** /**
@ -202,6 +204,13 @@ struct MapNode
u8 getLight(enum LightBank bank, INodeDefManager *nodemgr) const; u8 getLight(enum LightBank bank, INodeDefManager *nodemgr) const;
/*!
* Returns the node's light level from param1.
* If the node emits light, it is ignored.
* \param f the ContentFeatures of this node.
*/
u8 getLightRaw(enum LightBank bank, const ContentFeatures &f) const;
/** /**
* This function differs from getLight(enum LightBank bank, INodeDefManager *nodemgr) * This function differs from getLight(enum LightBank bank, INodeDefManager *nodemgr)
* in that the ContentFeatures of the node in question are not retrieved by * in that the ContentFeatures of the node in question are not retrieved by

@ -19,6 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "voxelalgorithms.h" #include "voxelalgorithms.h"
#include "nodedef.h" #include "nodedef.h"
#include "mapblock.h"
#include "map.h"
#include "util/timetaker.h"
namespace voxalgo namespace voxalgo
{ {
@ -153,5 +156,586 @@ SunlightPropagateResult propagateSunlight(VoxelManipulator &v, VoxelArea a,
return SunlightPropagateResult(bottom_sunlight_valid); return SunlightPropagateResult(bottom_sunlight_valid);
} }
/*!
* A direction.
* 0=X+
* 1=Y+
* 2=Z+
* 3=Z-
* 4=Y-
* 5=X-
* 6=no direction
* Two directions ate opposite only if their sum is 5.
*/
typedef u8 direction;
/*!
* Relative node position.
* This represents a node's position in its map block.
* All coordinates must be between 0 and 15.
*/
typedef v3s16 relative_v3;
/*!
* Position of a map block.
* One block_pos unit is as long as 16 node position units.
*/
typedef v3s16 mapblock_v3;
//! Contains information about a node whose light is about to change.
struct ChangingLight {
//! Relative position of the node in its map block.
relative_v3 rel_position;
//! Position of the node's block.
mapblock_v3 block_position;
//! Reference to the node's block.
MapBlock *block;
/*!
* Direction from the node that caused this node's changing
* to this node.
*/
direction source_direction;
ChangingLight() :
rel_position(),
block_position(),
block(NULL),
source_direction(6)
{}
ChangingLight(relative_v3 rel_pos, mapblock_v3 block_pos,
MapBlock *b, direction source_dir) :
rel_position(rel_pos),
block_position(block_pos),
block(b),
source_direction(source_dir)
{}
};
/*!
* A fast, priority queue-like container to contain ChangingLights.
* The ChangingLights are ordered by the given light levels.
* The brightest ChangingLight is returned first.
*/
struct LightQueue {
//! For each light level there is a vector.
std::vector<ChangingLight> lights[LIGHT_SUN + 1];
//! Light of the brightest ChangingLight in the queue.
u8 max_light;
/*!
* Creates a LightQueue.
* \param reserve for each light level that many slots are reserved.
*/
LightQueue(size_t reserve)
{
max_light = LIGHT_SUN;
for (u8 i = 0; i <= LIGHT_SUN; i++) {
lights[i].reserve(reserve);
}
}
/*!
* Returns the next brightest ChangingLight and
* removes it from the queue.
* If there were no elements in the queue, the given parameters
* remain unmodified.
* \param light light level of the popped ChangingLight
* \param data the ChangingLight that was popped
* \returns true if there was a ChangingLight in the queue.
*/
bool next(u8 &light, ChangingLight &data)
{
while (lights[max_light].empty()) {
if (max_light == 0) {
return false;
}
max_light--;
}
light = max_light;
data = lights[max_light].back();
lights[max_light].pop_back();
return true;
}
/*!
* Adds an element to the queue.
* The parameters are the same as in ChangingLight's constructor.
* \param light light level of the ChangingLight
*/
inline void push(u8 light, relative_v3 &rel_pos, mapblock_v3 &block_pos,
MapBlock *block, direction source_dir)
{
lights[light].push_back(
ChangingLight(rel_pos, block_pos, block, source_dir));
}
};
/*!
* This type of light queue is for unlighting.
* A node can be pushed in it only if its raw light is zero.
* This prevents pushing nodes twice into this queue.
* The light of the pushed ChangingLight must be the
* light of the node before unlighting it.
*/
typedef LightQueue UnlightQueue;
/*!
* This type of light queue is for spreading lights.
* While spreading lights, all the nodes in it must
* have the same light as the light level the ChangingLights
* were pushed into this queue with. This prevents unnecessary
* re-pushing of the nodes into the queue.
* If a node doesn't let light trough but emits light, it can be added
* too.
*/
typedef LightQueue ReLightQueue;
/*!
* neighbor_dirs[i] points towards
* the direction i.
* See the definition of the type "direction"
*/
const static v3s16 neighbor_dirs[6] = {
v3s16(1, 0, 0), // right
v3s16(0, 1, 0), // top
v3s16(0, 0, 1), // back
v3s16(0, 0, -1), // front
v3s16(0, -1, 0), // bottom
v3s16(-1, 0, 0), // left
};
/*!
* Transforms the given map block offset by one node towards
* the specified direction.
* \param dir the direction of the transformation
* \param rel_pos the node's relative position in its map block
* \param block_pos position of the node's block
*/
bool stepRelBlockPos(direction dir, relative_v3 &rel_pos,
mapblock_v3 &block_pos)
{
switch (dir) {
case 0:
if (rel_pos.X < MAP_BLOCKSIZE - 1) {
rel_pos.X++;
} else {
rel_pos.X = 0;
block_pos.X++;
return true;
}
break;
case 1:
if (rel_pos.Y < MAP_BLOCKSIZE - 1) {
rel_pos.Y++;
} else {
rel_pos.Y = 0;
block_pos.Y++;
return true;
}
break;
case 2:
if (rel_pos.Z < MAP_BLOCKSIZE - 1) {
rel_pos.Z++;
} else {
rel_pos.Z = 0;
block_pos.Z++;
return true;
}
break;
case 3:
if (rel_pos.Z > 0) {
rel_pos.Z--;
} else {
rel_pos.Z = MAP_BLOCKSIZE - 1;
block_pos.Z--;
return true;
}
break;
case 4:
if (rel_pos.Y > 0) {
rel_pos.Y--;
} else {
rel_pos.Y = MAP_BLOCKSIZE - 1;
block_pos.Y--;
return true;
}
break;
case 5:
if (rel_pos.X > 0) {
rel_pos.X--;
} else {
rel_pos.X = MAP_BLOCKSIZE - 1;
block_pos.X--;
return true;
}
break;
}
return false;
}
/*
* Removes all light that is potentially emitted by the specified
* light sources. These nodes will have zero light.
* Returns all nodes whose light became zero but should be re-lighted.
*
* \param bank the light bank in which the procedure operates
* \param from_nodes nodes whose light is removed
* \param light_sources nodes that should be re-lighted
* \param modified_blocks output, all modified map blocks are added to this
*/
void unspreadLight(Map *map, INodeDefManager *nodemgr, LightBank bank,
UnlightQueue &from_nodes, ReLightQueue &light_sources,
std::map<v3s16, MapBlock*> & modified_blocks)
{
// Stores data popped from from_nodes
u8 current_light;
ChangingLight current;
// Data of the current neighbor
mapblock_v3 neighbor_block_pos;
relative_v3 neighbor_rel_pos;
// A dummy boolean
bool is_valid_position;
// Direction of the brightest neighbor of the node
direction source_dir;
while (from_nodes.next(current_light, current)) {
// For all nodes that need unlighting
// There is no brightest neighbor
source_dir = 6;
// The current node
const MapNode &node = current.block->getNodeNoCheck(
current.rel_position, &is_valid_position);
const ContentFeatures &f = nodemgr->get(node);
// If the node emits light, it behaves like it had a
// brighter neighbor.
u8 brightest_neighbor_light = f.light_source + 1;
for (direction i = 0; i < 6; i++) {
//For each neighbor
// The node that changed this node has already zero light
// and it can't give light to this node
if (current.source_direction + i == 5) {
continue;
}
// Get the neighbor's position and block
neighbor_rel_pos = current.rel_position;
neighbor_block_pos = current.block_position;
MapBlock *neighbor_block;
if (stepRelBlockPos(i, neighbor_rel_pos, neighbor_block_pos)) {
neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos);
if (neighbor_block == NULL) {
continue;
}
} else {
neighbor_block = current.block;
}
// Get the neighbor itself
MapNode neighbor = neighbor_block->getNodeNoCheck(neighbor_rel_pos,
&is_valid_position);
const ContentFeatures &neighbor_f = nodemgr->get(
neighbor.getContent());
u8 neighbor_light = neighbor.getLightRaw(bank, neighbor_f);
// If the neighbor has at least as much light as this node, then
// it won't lose its light, since it should have been added to
// from_nodes earlier, so its light would be zero.
if (neighbor_f.light_propagates && neighbor_light < current_light) {
// Unlight, but only if the node has light.
if (neighbor_light > 0) {
neighbor.setLight(bank, 0, neighbor_f);
neighbor_block->setNodeNoCheck(neighbor_rel_pos, neighbor);
from_nodes.push(neighbor_light, neighbor_rel_pos,
neighbor_block_pos, neighbor_block, i);
// The current node was modified earlier, so its block
// is in modified_blocks.
if (current.block != neighbor_block) {
modified_blocks[neighbor_block_pos] = neighbor_block;
}
}
} else {
// The neighbor can light up this node.
if (neighbor_light < neighbor_f.light_source) {
neighbor_light = neighbor_f.light_source;
}
if (brightest_neighbor_light < neighbor_light) {
brightest_neighbor_light = neighbor_light;
source_dir = i;
}
}
}
// If the brightest neighbor is able to light up this node,
// then add this node to the output nodes.
if (brightest_neighbor_light > 1 && f.light_propagates) {
brightest_neighbor_light--;
light_sources.push(brightest_neighbor_light, current.rel_position,
current.block_position, current.block,
(source_dir == 6) ? 6 : 5 - source_dir
/* with opposite direction*/);
}
}
}
/*
* Spreads light from the specified starting nodes.
*
* Before calling this procedure, make sure that all ChangingLights
* in light_sources have as much light on the map as they have in
* light_sources (if the queue contains a node multiple times, the brightest
* occurrence counts).
*
* \param bank the light bank in which the procedure operates
* \param light_sources starting nodes
* \param modified_blocks output, all modified map blocks are added to this
*/
void spreadLight(Map *map, INodeDefManager *nodemgr, LightBank bank,
LightQueue & light_sources, std::map<v3s16, MapBlock*> & modified_blocks)
{
// The light the current node can provide to its neighbors.
u8 spreading_light;
// The ChangingLight for the current node.
ChangingLight current;
// Position of the current neighbor.
mapblock_v3 neighbor_block_pos;
relative_v3 neighbor_rel_pos;
// A dummy boolean.
bool is_valid_position;
while (light_sources.next(spreading_light, current)) {
spreading_light--;
for (direction i = 0; i < 6; i++) {
// This node can't light up its light source
if (current.source_direction + i == 5) {
continue;
}
// Get the neighbor's position and block
neighbor_rel_pos = current.rel_position;
neighbor_block_pos = current.block_position;
MapBlock *neighbor_block;
if (stepRelBlockPos(i, neighbor_rel_pos, neighbor_block_pos)) {
neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos);
if (neighbor_block == NULL) {
continue;
}
} else {
neighbor_block = current.block;
}
// Get the neighbor itself
MapNode neighbor = neighbor_block->getNodeNoCheck(neighbor_rel_pos,
&is_valid_position);
const ContentFeatures &f = nodemgr->get(neighbor.getContent());
if (f.light_propagates) {
// Light up the neighbor, if it has less light than it should.
u8 neighbor_light = neighbor.getLightRaw(bank, f);
if (neighbor_light < spreading_light) {
neighbor.setLight(bank, spreading_light, f);
neighbor_block->setNodeNoCheck(neighbor_rel_pos, neighbor);
light_sources.push(spreading_light, neighbor_rel_pos,
neighbor_block_pos, neighbor_block, i);
// The current node was modified earlier, so its block
// is in modified_blocks.
if (current.block != neighbor_block) {
modified_blocks[neighbor_block_pos] = neighbor_block;
}
}
}
}
}
}
/*!
* Returns true if the node gets sunlight from the
* node above it.
*
* \param pos position of the node.
*/
bool isSunlightAbove(Map *map, v3s16 pos, INodeDefManager *ndef)
{
bool sunlight = true;
mapblock_v3 source_block_pos;
relative_v3 source_rel_pos;
getNodeBlockPosWithOffset(pos + v3s16(0, 1, 0), source_block_pos,
source_rel_pos);
// If the node above has sunlight, this node also can get it.
MapBlock *source_block = map->getBlockNoCreateNoEx(source_block_pos);
if (source_block == NULL) {
// But if there is no node above, then use heuristics
MapBlock *node_block = map->getBlockNoCreateNoEx(getNodeBlockPos(pos));
if (node_block == NULL) {
sunlight = false;
} else {
sunlight = !node_block->getIsUnderground();
}
} else {
bool is_valid_position;
MapNode above = source_block->getNodeNoCheck(source_rel_pos,
&is_valid_position);
if (is_valid_position) {
if (above.getContent() == CONTENT_IGNORE) {
// Trust heuristics
if (source_block->getIsUnderground()) {
sunlight = false;
}
} else if (above.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) {
// If the node above doesn't have sunlight, this
// node is in shadow.
sunlight = false;
}
}
}
return sunlight;
}
static const LightBank banks[] = { LIGHTBANK_DAY, LIGHTBANK_NIGHT };
void update_lighting_node(Map *map, INodeDefManager *ndef, v3s16 p,
MapNode oldnode, std::map<v3s16, MapBlock*> & modified_blocks)
{
// For node getter functions
bool is_valid_position;
// Get position and block of the changed node
relative_v3 rel_pos;
mapblock_v3 block_pos;
getNodeBlockPosWithOffset(p, block_pos, rel_pos);
MapBlock *block = map->getBlockNoCreateNoEx(block_pos);
if (block == NULL || block->isDummy()) {
return;
}
// Process each light bank separately
for (s32 i = 0; i < 2; i++) {
// Get the new node
MapNode n = block->getNodeNoCheck(rel_pos, &is_valid_position);
if (!is_valid_position) {
break;
}
LightBank bank = banks[i];
// Light of the old node
u8 old_light = oldnode.getLight(bank, ndef);
// Add the block of the added node to modified_blocks
modified_blocks[block_pos] = block;
// Get new light level of the node
u8 new_light = 0;
if (ndef->get(n).light_propagates) {
if (bank == LIGHTBANK_DAY && ndef->get(n).sunlight_propagates
&& isSunlightAbove(map, p, ndef)) {
new_light = LIGHT_SUN;
} else {
new_light = ndef->get(n).light_source;
for (int i = 0; i < 6; i++) {
v3s16 p2 = p + neighbor_dirs[i];
bool is_valid;
MapNode n2 = map->getNodeNoEx(p2, &is_valid);
if (is_valid) {
u8 spread = n2.getLight(bank, ndef);
// If the neighbor is at least as bright as
// this node then its light is not from
// this node.
// Its light can spread to this node.
if (spread > new_light && spread >= old_light) {
new_light = spread - 1;
}
}
}
}
} else {
// If this is an opaque node, it still can emit light.
new_light = ndef->get(n).light_source;
}
ReLightQueue light_sources(256);
if (new_light > 0) {
light_sources.push(new_light, rel_pos, block_pos, block, 6);
}
if (new_light < old_light) {
// The node became opaque or doesn't provide as much
// light as the previous one, so it must be unlighted.
LightQueue disappearing_lights(256);
// Add to unlight queue
n.setLight(bank, 0, ndef);
block->setNodeNoCheck(rel_pos, n);
disappearing_lights.push(old_light, rel_pos, block_pos, block, 6);
// Remove sunlight, if there was any
if (bank == LIGHTBANK_DAY && old_light == LIGHT_SUN) {
for (s16 y = p.Y - 1;; y--) {
v3s16 n2pos(p.X, y, p.Z);
MapNode n2;
n2 = map->getNodeNoEx(n2pos, &is_valid_position);
if (!is_valid_position)
break;
// If this node doesn't have sunlight, the nodes below
// it don't have too.
if (n2.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) {
break;
}
// Remove sunlight and add to unlight queue.
n2.setLight(LIGHTBANK_DAY, 0, ndef);
map->setNode(n2pos, n2);
relative_v3 rel_pos2;
mapblock_v3 block_pos2;
getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2);
MapBlock *block2 = map->getBlockNoCreateNoEx(block_pos2);
disappearing_lights.push(LIGHT_SUN, rel_pos2, block_pos2,
block2, 4 /* The node above caused the change */);
}
}
// Remove lights
unspreadLight(map, ndef, bank, disappearing_lights, light_sources,
modified_blocks);
} else if (new_light > old_light) {
// It is sure that the node provides more light than the previous
// one, unlighting is not necessary.
// Propagate sunlight
if (bank == LIGHTBANK_DAY && new_light == LIGHT_SUN) {
for (s16 y = p.Y - 1;; y--) {
v3s16 n2pos(p.X, y, p.Z);
MapNode n2;
n2 = map->getNodeNoEx(n2pos, &is_valid_position);
if (!is_valid_position)
break;
// This should not happen, but if the node has sunlight
// then the iteration should stop.
if (n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) {
break;
}
// If the node terminates sunlight, stop.
if (!ndef->get(n2).sunlight_propagates) {
break;
}
relative_v3 rel_pos2;
mapblock_v3 block_pos2;
getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2);
MapBlock *block2 = map->getBlockNoCreateNoEx(block_pos2);
// Mark node for lighting.
light_sources.push(LIGHT_SUN, rel_pos2, block_pos2, block2,
4);
}
}
}
// Initialize light values for light spreading.
for (u8 i = 0; i <= LIGHT_SUN; i++) {
const std::vector<ChangingLight> &lights = light_sources.lights[i];
for (std::vector<ChangingLight>::const_iterator it = lights.begin();
it < lights.end(); it++) {
MapNode n = it->block->getNodeNoCheck(it->rel_position,
&is_valid_position);
n.setLight(bank, i, ndef);
it->block->setNodeNoCheck(it->rel_position, n);
}
}
// Spread lights.
spreadLight(map, ndef, bank, light_sources, modified_blocks);
}
}
} // namespace voxalgo } // namespace voxalgo

@ -25,6 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <set> #include <set>
#include <map> #include <map>
class Map;
class MapBlock;
namespace voxalgo namespace voxalgo
{ {
@ -52,6 +55,23 @@ SunlightPropagateResult propagateSunlight(VoxelManipulator &v, VoxelArea a,
std::set<v3s16> & light_sources, std::set<v3s16> & light_sources,
INodeDefManager *ndef); INodeDefManager *ndef);
/*!
* Updates the lighting on the map.
* The result will be correct only if
* no nodes were changed except the given one.
*
* \param p position of the changed node
* \param oldnode this node was overwritten on the map
* \param modified_blocks output, contains all map blocks that
* the function modified
*/
void update_lighting_node(
Map *map,
INodeDefManager *ndef,
v3s16 p,
MapNode oldnode,
std::map<v3s16, MapBlock*> &modified_blocks);
} // namespace voxalgo } // namespace voxalgo
#endif #endif