* refactored liquid transformation code (has some flaws)

Completely rewrote the liquid transformation. The algorithm now exclusively modifies the current node in one iteration. Another change is that MapNode.param2 now stores a bit that tells other nodes if a flowing liquid node flows downwards. This is accomplished by two masks on param2 for the "flow down" bit and the "liquid level" bits.

This will be the base of future improvements to the liquid flow. However, in the current state flowing liquid does not always disappear when cut off its source. Be aware that this branch is work in progress.
This commit is contained in:
Felix Krause 2011-07-16 16:01:37 +02:00 committed by Nils Dagsson Moskopp
parent f2c6330505
commit def870953a
3 changed files with 218 additions and 262 deletions

@ -375,7 +375,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
if(n2.d == CONTENT_WATERSOURCE) if(n2.d == CONTENT_WATERSOURCE)
level = (-0.5+node_water_level) * BS; level = (-0.5+node_water_level) * BS;
else if(n2.d == CONTENT_WATER) else if(n2.d == CONTENT_WATER)
level = (-0.5 + ((float)n2.param2 + 0.5) / 8.0 level = (-0.5 + ((float)(n2.param2 & LIQUID_LEVEL_MASK) + 0.5) / 8.0
* node_water_level) * BS; * node_water_level) * BS;
// Check node above neighbor. // Check node above neighbor.

@ -1086,7 +1086,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
v3s16 p2 = p + dirs[i]; v3s16 p2 = p + dirs[i];
MapNode n2 = getNode(p2); MapNode n2 = getNode(p2);
if(content_liquid(n2.d)) if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
{ {
m_transforming_liquid.push_back(p2); m_transforming_liquid.push_back(p2);
} }
@ -1241,18 +1241,10 @@ void Map::removeNodeAndUpdate(v3s16 p,
/* /*
Add neighboring liquid nodes to transform queue. Add neighboring liquid nodes to transform queue.
Also add horizontal neighbors of node on top of removed node
because they could be affected of the water on top flowing
down instead of into them.
*/ */
v3s16 dirs[10] = { v3s16 dirs[10] = {
v3s16(0,0,1), // back v3s16(0,0,1), // back
v3s16(0,1,0), // top v3s16(0,1,0), // top
v3s16(1,1,0), // topright
v3s16(-1,1,0), // topleft
v3s16(0,1,1), // topback
v3s16(0,1,-1), // topfront
v3s16(1,0,0), // right v3s16(1,0,0), // right
v3s16(0,0,-1), // front v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom v3s16(0,-1,0), // bottom
@ -1266,7 +1258,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
v3s16 p2 = p + dirs[i]; v3s16 p2 = p + dirs[i];
MapNode n2 = getNode(p2); MapNode n2 = getNode(p2);
if(content_liquid(n2.d)) if(content_liquid(n2.d) || n2.d == CONTENT_AIR)
{ {
m_transforming_liquid.push_back(p2); m_transforming_liquid.push_back(p2);
} }
@ -1546,6 +1538,17 @@ void Map::PrintInfo(std::ostream &out)
#define WATER_DROP_BOOST 4 #define WATER_DROP_BOOST 4
enum NeighborType {
NEIGHBOR_UPPER,
NEIGHBOR_SAME_LEVEL,
NEIGHBOR_LOWER
};
struct NodeNeighbor {
MapNode n;
NeighborType t;
v3s16 p;
};
void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks) void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
{ {
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
@ -1566,264 +1569,210 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
MapNode n0 = getNodeNoEx(p0); MapNode n0 = getNodeNoEx(p0);
// Don't deal with non-liquids /*
if(content_liquid(n0.d) == false) Collect information about current node
*/
s8 liquid_level = -1;
u8 liquid_kind;
LiquidType liquid_type = content_features(n0.d).liquid_type;
switch (liquid_type) {
case LIQUID_SOURCE:
liquid_level = 8;
liquid_kind = content_features(n0.d).liquid_alternative_flowing;
break;
case LIQUID_FLOWING:
liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
liquid_kind = n0.d;
break;
case LIQUID_NONE:
// if this is an air node, it *could* be transformed into a liquid. otherwise,
// continue with the next node.
if (n0.d != CONTENT_AIR)
continue; continue;
liquid_kind = CONTENT_AIR;
bool is_source = !content_flowing_liquid(n0.d); break;
}
u8 liquid_level = 8;
if(is_source == false)
liquid_level = n0.param2 & 0x0f;
// Turn possible source into non-source
u8 nonsource_c = make_liquid_flowing(n0.d);
// Counts surrounding liquid source blocks
u8 surrounding_sources = 0;
/* /*
If not source, check that some node flows into this one Collect information about the environment
and what is the level of liquid in this one
*/ */
if(is_source == false) v3s16 dirs[6] = {
{
s8 new_liquid_level_max = -1;
v3s16 dirs_from[5] = {
v3s16( 0, 1, 0), // top v3s16( 0, 1, 0), // top
v3s16(0,0,1), // back v3s16( 0,-1, 0), // bottom
v3s16( 1, 0, 0), // right v3s16( 1, 0, 0), // right
v3s16(0,0,-1), // front
v3s16(-1, 0, 0), // left v3s16(-1, 0, 0), // left
v3s16( 0, 0, 1), // back
v3s16( 0, 0,-1) // front
}; };
for(u16 i=0; i<5; i++) NodeNeighbor sources[6]; // surrounding sources
{ int num_sources = 0;
bool from_top = (i==0); NodeNeighbor flows[6]; // surrounding flowing liquid nodes
int num_flows = 0;
v3s16 p2 = p0 + dirs_from[i]; NodeNeighbor airs[6]; // surrounding air
MapNode n2 = getNodeNoEx(p2); int num_airs = 0;
NodeNeighbor neutrals[6]; // nodes that are solid, another kind of liquid
if(content_liquid(n2.d)) int num_neutrals = 0;
{ bool flowing_down = false;
u8 n2_nonsource_c = make_liquid_flowing(n2.d); for (u16 i = 0; i < 6; i++) {
// Check that the liquids are the same type NeighborType nt = NEIGHBOR_SAME_LEVEL;
if(n2_nonsource_c != nonsource_c) switch (i) {
{ case 0:
dstream<<"WARNING: Not handling: different liquids" nt = NEIGHBOR_UPPER;
" collide"<<std::endl; break;
continue; case 1:
nt = NEIGHBOR_LOWER;
break;
} }
bool n2_is_source = !content_flowing_liquid(n2.d); v3s16 npos = p0 + dirs[i];
s8 n2_liquid_level = 8; NodeNeighbor nb = {getNodeNoEx(npos), nt, npos};
if(n2_is_source) switch (content_features(nb.n.d).liquid_type) {
surrounding_sources++; case LIQUID_NONE:
else if (nb.n.d == CONTENT_AIR) {
n2_liquid_level = n2.param2 & 0x07; airs[num_airs++] = nb;
// if the current nodes happens to be a flowing node, it will start to flow down here.
s8 new_liquid_level = -1; if (nb.t == NEIGHBOR_LOWER)
if(from_top) flowing_down = true;
{ } else {
//new_liquid_level = 7; neutrals[num_neutrals++] = nb;
if(n2_liquid_level >= 7 - WATER_DROP_BOOST)
new_liquid_level = 7;
else
new_liquid_level = n2_liquid_level + WATER_DROP_BOOST;
} }
else if(n2_liquid_level > 0) break;
{ case LIQUID_SOURCE:
// If the neighbor node isn't a source and flows downwards, // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
// it doesn't flow into this node if (liquid_kind == CONTENT_AIR)
if (n2_is_source) liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
{ if (content_features(nb.n.d).liquid_alternative_flowing !=liquid_kind) {
new_liquid_level = n2_liquid_level - 1; neutrals[num_neutrals++] = nb;
} else {
sources[num_sources++] = nb;
} }
else break;
{ case LIQUID_FLOWING:
// Node below n2 // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
MapNode n3 = getNodeNoEx(p2 + v3s16(0,-1,0)); // (while ignoring flowing liquids at the lowest level, which cannot flow into this node)
// NOTE: collision of different liquids not yet handled here. if (liquid_kind == CONTENT_AIR && ((nb.n.param2 & LIQUID_LEVEL_MASK) > 0))
if (content_features(n3.d).liquid_type != LIQUID_FLOWING) liquid_kind = content_features(nb.n.d).liquid_alternative_flowing;
new_liquid_level = n2_liquid_level - 1; if (content_features(nb.n.d).liquid_alternative_flowing != liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
// order flowing neighbors by liquid level descending
int insert_at = 0;
while (insert_at < num_flows && ((flows[insert_at].n.param2 & LIQUID_LEVEL_MASK) >
(nb.n.param2 & LIQUID_LEVEL_MASK))) {
insert_at++;
}
flows[insert_at] = nb;
num_flows++;
if (nb.t == NEIGHBOR_LOWER)
flowing_down = true;
}
break;
} }
} }
if(new_liquid_level > new_liquid_level_max)
new_liquid_level_max = new_liquid_level;
}
} //for
/* /*
If liquid level should be something else, update it and decide on the type (and possibly level) of the current node
add all the neighboring water nodes to the transform queue.
*/ */
if(new_liquid_level_max != liquid_level || (!is_source && surrounding_sources >= 2)) u8 new_node_content;
{ s8 new_node_level = -1;
if (surrounding_sources >= 2) if (num_sources >= 2 || liquid_type == LIQUID_SOURCE) {
{ // liquid_kind will be set to either the flowing alternative of the node (if it's a liquid)
n0.d = content_features(n0.d).liquid_alternative_source; // or the flowing alternative of the first of the surrounding sources (if it's air), so
setNode(p0,n0); // it's perfectly safe to use liquid_kind here to determine the new node content.
new_node_content = content_features(liquid_kind).liquid_alternative_source;
} else if (num_sources == 1 && sources[0].t != NEIGHBOR_LOWER) {
// liquid_kind is set properly, see above
new_node_content = liquid_kind;
new_node_level = 7;
} else {
// no surrounding sources, so get the maximum level that can flow into this node
for (u16 i = 0; i < num_flows; i++) {
u8 nb_liquid_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
switch (flows[i].t) {
case NEIGHBOR_UPPER:
if (nb_liquid_level + WATER_DROP_BOOST > new_node_level) {
new_node_level = 7;
if (nb_liquid_level + WATER_DROP_BOOST < 7)
new_node_level = nb_liquid_level + WATER_DROP_BOOST;
} }
else if(new_liquid_level_max == -1) break;
{ case NEIGHBOR_LOWER:
// Remove water alltoghether break;
n0.d = CONTENT_AIR; case NEIGHBOR_SAME_LEVEL:
n0.param2 = 0; if ((flows[i].n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK &&
setNode(p0, n0); nb_liquid_level > 0 && nb_liquid_level - 1 > new_node_level) {
new_node_level = nb_liquid_level - 1;
} }
break;
}
}
if (new_node_level >= 0)
new_node_content = liquid_kind;
else else
{ new_node_content = CONTENT_AIR;
n0.param2 = new_liquid_level_max;
liquid_level = new_liquid_level_max;
setNode(p0, n0);
} }
// Block has been modified /*
{ check if anything has changed. if not, just continue with the next node.
*/
if (new_node_content == n0.d && (content_features(n0.d).liquid_type != LIQUID_FLOWING ||
((n0.param2 & LIQUID_LEVEL_MASK) == (u8)new_node_level) &&
((n0.param2 & LIQUID_FLOW_DOWN_MASK) == LIQUID_FLOW_DOWN_MASK)
== flowing_down))
continue;
/*
update the current node
*/
bool flow_down_enabled = (flowing_down && ((n0.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK));
n0.d = new_node_content;
if (content_features(n0.d).liquid_type == LIQUID_FLOWING) {
// set level to last 3 bits, flowing down bit to 4th bit
n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK);
} else {
n0.param2 = 0;
}
setNode(p0, n0);
v3s16 blockpos = getNodeBlockPos(p0); v3s16 blockpos = getNodeBlockPos(p0);
MapBlock *block = getBlockNoCreateNoEx(blockpos); MapBlock *block = getBlockNoCreateNoEx(blockpos);
if(block != NULL) if(block != NULL)
modified_blocks.insert(blockpos, block); modified_blocks.insert(blockpos, block);
switch (content_features(n0.d).liquid_type) {
case LIQUID_SOURCE:
// make sure source flows into all neighboring nodes
for (u16 i = 0; i < num_flows; i++)
if (flows[i].t != NEIGHBOR_UPPER)
m_transforming_liquid.push_back(flows[i].p);
for (u16 i = 0; i < num_airs; i++)
if (airs[i].t != NEIGHBOR_UPPER)
m_transforming_liquid.push_back(airs[i].p);
break;
case LIQUID_NONE:
// this flow has turned to air; neighboring flows might need to do the same
for (u16 i = 0; i < num_flows; i++)
m_transforming_liquid.push_back(flows[i].p);
break;
case LIQUID_FLOWING:
for (u16 i = 0; i < num_flows; i++) {
u8 flow_level = (flows[i].n.param2 & LIQUID_LEVEL_MASK);
// liquid_level is still the ORIGINAL level of this node.
if (flows[i].t != NEIGHBOR_UPPER && ((flow_level < liquid_level || flow_level < new_node_level) ||
flow_down_enabled))
m_transforming_liquid.push_back(flows[i].p);
} }
for (u16 i = 0; i < num_airs; i++) {
/* if (airs[i].t != NEIGHBOR_UPPER && (airs[i].t == NEIGHBOR_LOWER || new_node_level > 0))
Add neighboring non-source liquid nodes to transform queue. m_transforming_liquid.push_back(airs[i].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
};
for(u16 i=0; i<6; i++)
{
v3s16 p2 = p0 + dirs[i];
MapNode n2 = getNodeNoEx(p2);
if(content_flowing_liquid(n2.d))
{
m_transforming_liquid.push_back(p2);
} }
}
}
}
// Get a new one from queue if the node has turned into non-water
if(content_liquid(n0.d) == false)
continue;
/*
Flow water from this node
*/
v3s16 dirs_to[5] = {
v3s16(0,-1,0), // bottom
v3s16(0,0,1), // back
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(-1,0,0), // left
};
for(u16 i=0; i<5; i++)
{
bool to_bottom = (i == 0);
// If liquid is at lowest possible height, it's not going
// anywhere except down
if(liquid_level == 0 && to_bottom == false)
continue;
u8 liquid_next_level = 0;
// If going to bottom
if(to_bottom)
{
//liquid_next_level = 7;
if(liquid_level >= 7 - WATER_DROP_BOOST)
liquid_next_level = 7;
else
liquid_next_level = liquid_level + WATER_DROP_BOOST;
}
else
liquid_next_level = liquid_level - 1;
bool n2_changed = false;
bool flowed = false;
v3s16 p2 = p0 + dirs_to[i];
MapNode n2 = getNodeNoEx(p2);
//dstream<<"[1] n2.param="<<(int)n2.param<<std::endl;
if(content_liquid(n2.d))
{
u8 n2_nonsource_c = make_liquid_flowing(n2.d);
// Check that the liquids are the same type
if(n2_nonsource_c != nonsource_c)
{
dstream<<"WARNING: Not handling: different liquids"
" collide"<<std::endl;
continue;
}
bool n2_is_source = !content_flowing_liquid(n2.d);
u8 n2_liquid_level = 8;
if(n2_is_source == false)
n2_liquid_level = n2.param2 & 0x07;
if(to_bottom)
{
flowed = true;
}
if(n2_is_source)
{
// Just flow into the source, nothing changes.
// n2_changed is not set because destination didn't change
flowed = true;
}
else
{
if(liquid_next_level > n2_liquid_level)
{
n2.param2 = liquid_next_level;
setNode(p2, n2);
n2_changed = true;
flowed = true;
}
}
}
else if(n2.d == CONTENT_AIR)
{
n2.d = nonsource_c;
n2.param2 = liquid_next_level;
setNode(p2, n2);
n2_changed = true;
flowed = true;
}
//dstream<<"[2] n2.param="<<(int)n2.param<<std::endl;
if(n2_changed)
{
m_transforming_liquid.push_back(p2);
v3s16 blockpos = getNodeBlockPos(p2);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if(block != NULL)
modified_blocks.insert(blockpos, block);
}
// If n2_changed to bottom, don't flow anywhere else
if(to_bottom && flowed && !is_source)
break; break;
} }
loopcount++; loopcount++;
//if(loopcount >= 100000) //if(loopcount >= 100000)
if(loopcount >= initial_size * 1) if(loopcount >= initial_size * 1) {
break; break;
} }
}
//dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl; //dstream<<"Map::transformLiquids(): loopcount="<<loopcount<<std::endl;
} }

@ -404,10 +404,17 @@ enum LightBank
LIGHTBANK_NIGHT LIGHTBANK_NIGHT
}; };
/*
Masks for MapNode.param2 of flowing liquids
*/
#define LIQUID_LEVEL_MASK 0x07
#define LIQUID_FLOW_DOWN_MASK 0x08
/* /*
This is the stuff what the whole world consists of. This is the stuff what the whole world consists of.
*/ */
struct MapNode struct MapNode
{ {
/* /*