# Managing A Large Tilemap [ ][ ][ ][ ][ ]

This topic is 2389 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I am making a game that utilizes voxels (3d pixels).

Currently (method1)
I have classes called Map, and Chunk.
The map has a 25x25x25 array of pointers to the chunks.
The chunks contain a 16x16x16 array of voxels

When the map shifts all I have to do is shift the pointers array.

Problem with method1
The problem with method1 is when I need to access an element. This needs to be alot faster.
I use the following code to set the voxel I want to access.
chunk is the chunk that's calling the routine, then the x,y, and z coordinates of the voxel.
Sometimes it is passed -1 or 16 for one of the block coordinates so it needs to shift the chunk it focuses on
and readjust the x,y,z to account for that.
void world::setTarget(int blockX, int blockY, int blockZ,iVertex3d chunk) //Sets target of other Functions { tmpPoint.x = blockX; tmpPoint.y = blockY; tmpPoint.z = blockZ; tmpOffset = chunk; //Get X Chunk while (tmpPoint.x>=16) { tmpPoint.x-=16; tmpOffset.x+=1; } while (tmpPoint.x<0) { tmpPoint.x+=16; tmpOffset.x-=1; } //Get Y Chunk while (tmpPoint.y>=16) { tmpPoint.y-=16; tmpOffset.y+=1; } while (tmpPoint.y<0) { tmpPoint.y+=16; tmpOffset.y-=1; } //Get Z Chunk while (tmpPoint.z>=16) { tmpPoint.z-=16; tmpOffset.z+=1; } while (tmpPoint.z<0) { tmpPoint.z+=16; tmpOffset.z-=1; } return; } //Voxel Access // // //To access a voxel all I have to do is Chunk[tmpOffset.x][tmpOffset.y][tmpOffset.z]->position[tmpPoint.x][tmpPoint.y][tmpPoint.z]; // // 
So this way we have the
chunk* Chunks[25][25][25] in the map class and
char position[16][16][16] in each chunk.
Is there someway I can arrange this is memory better to access them quicker?
The current routine is actually so slow that when checking for reactions it's better to test whether the block is on the edge of a chunk
and if it is use this method and if not then access the position[][][] in the current chunk. Redundant code which does the same thing
2 different ways just kills me.
What is the optimal way to do this?

Idea (method2)
My new idea was to just allocate a 16*25x16*25x16*25 array in map,
and access it directly.

Problem with method2
The problem with method2 is the time it takes to move the map,
when shifting the map using the following code it takes 15399 milliseconds.
*60FPS = 16milliseconds per frame, 30FPS = 33milliseconds per frame.
 for (int z=0; z<25*16; z++) { for (int y=0; y<25*16; y++) { for (int x=0; x<24*16; x++) { testWorld[x,y,z] = testWorld[x+16,y,z]; } } } 
Is there some way I can move the data faster?
Basically all the data just needs to be shifted 16 places left in the arrray when the
map scrolls to the right.

Overall
My game is all about voxels reacting to their adjacent voxels (fire+wood lava+water) so this routine is called a very large amount of times.
In general this routine is called blocksActive*6 times, to check to see if this new voxel has an effect on the surrounding voxels.
Considering the player has about 500 voxels moving in 1 chunk it's called 3000 times that frame, no way around it.
What is the optimal way to do this?

I have been trying to figure out the best way to do this since I started the game,
If anyone can come up with something that would be much appreciated.

coderWalker

##### Share on other sites
I am trying to figure out why you are searching for where you want to go when you already know.

For example, if you know you are at array position, 50, and you want to go back one, you goto 50-1. You dont search for 49 starting at 0.

If you split this into a 2d array, assuming the top left corner of the array is 0, 0 if you are currently at block {5][7] and you want to go down (move on the y axis in a 2d game for example) one, the array position you need is [5][7+1]

If you extend this to 3d array, and you are at position [20][25][47] assuming the first index is the x value, second is the z value ( assuimg a left handed coord system) and the last index is the depth, or height ( y value).

If you want to move up vertically, the next array position would be [20][25][46]

If you know where you are, it is easy to move from that position within an array. Each element in the array should be assigned a distance (best to use power of twos for certain optimizations). For example, each array should be 16 units x 16 units by 16 units. So, if the player moves 56 units in the forward (assuming down the z axis) direction, you know that 56>>4 (use some bit shifting, or you can go 56/16 = 3.5, but if you use integers it will be rounded down so the result will be 3. Armed with this information, you know that you need to jump forward (assuming down the z axis) you need to offset your array by 3 like so [20][25-3][46].

Hope this helps. My answers may not me 100% accurate because I just work up, but the method is solid.

##### Share on other sites

Problem with method2
The problem with method2 is the time it takes to move the map,
when shifting the map using the following code it takes 15399 milliseconds.

You're not supposed to move everything. The common approach is a wrapping index so when you move one chunk to the right, the left row of chunks is overwritten by the new chunks and a simple % on the index makes sure that 25 turns into 0. If you increase the number of chunks to 32, you can replace / and % with >> and & (or rather: your compiler shouldl do it for you anyway if the size is a const).

I have no idea what machine you are running on, but over 15s sounds kind of extreme. I'm loading 64x64 chunks of 16x16x128 blocks from hard drive in 400ms and creating the geometry with pseudo ambient occlusion takes about 1700ms. Shifting the world by a row as above takes about 100ms.

##### Share on other sites
(I had a huge response written and GameDrv went down and I lost it, so this is the short version)

I forgot to mention the map is infinite.
The 25x25x25 array of chunks the map holds are the ones around the player.

-For example, When the map scrolls right:
The Left most chunks are unallocated
The chunks are shifted left
The right most are marked to be generated.

Thats why I have to move the data, the map is not static but dynamic

How I currently have it set up is:
Map
-Chunks
--Voxels

So before I can access the voxel I have to calculate which chunk to access.
The chunks are classes and the voxels are an array in the classes.

However with method2 I could access it as you described.

"Shifting the world by a row as above takes about 100ms."

How? How are you allocating the memory? Just a strait out array?
When I said that I am referring to method2.

Here is the method2 code I did the speed test with:

array3d.h
class array3d { public: array3d(int size) : size(size) { data = new char[size*size*size]; } char &operator()(int x, int y, int z) { return data[index(x, y, z)]; } char &operator()(int x, int y, int z) const { return data[index(x, y, z)]; } private: inline int index(int x, int y, int z) const { return (x * size * size) + (y * size) + z; } int size; char* data; };

Main.cpp

//Create the array array3d testWorld = array3d(16*25); //Populate the array for (int z=0; z<25*16; z++) { for (int y=0; y<25*16; y++) { for (int x=0; x<25*16; x++) { testWorld(x,y,z) = 1; } } } //Move the array fps->start(); for (int z=0; z<25*16; z++) { for (int y=0; y<25*16; y++) { for (int x=0; x<24*16; x++) { testWorld(x,y,z) = testWorld(x+16,y,z); } } } cout << fps->get_ticks(); return 1;

*The fps object is for getting the amount of milliseconds since the start() using SDL.

##### Share on other sites

"Shifting the world by a row as above takes about 100ms."

How? How are you allocating the memory? Just a strait out array?

It's about 40ms to load/decompress/parse the 64 chunks and 60ms to create and upload the geometry (half of that is for "ambient occlusion"). The second part is independent of getting the data in the right place.

The only allocation is for a vector of 64x64 chunks (or whatever is configured). There is no point in moving around everything when scrolling only changes 1 row or column. New data overwrites old data and modulo does the rest. The chunk at x = 958482 will always be at index 18. No magic, no pointer shuffling, no reallocation.

##### Share on other sites
Can you elaborate a little more?

I still don't understand how it works without having to work like this:
-For example, When the map scrolls right:
The Left most chunks are unallocated
The chunks are shifted left
The right most are marked to be generated.[/quote]

I guess it's like anything else you get stuck on one idea and it's hard to think of other ideas.

I really want to understand how your method works!

##### Share on other sites
[quote name='coderWalker']
When the map shifts all I have to do is shift the pointers array.
[/quote]

At the moment, as I understand it you're moving each and every pointer within the array. If we're using a 4x4 array with each element representing a point on the map, and you want to move 1 to the left and 2 up:
 [5,22][6,22][7,22][8,22] [5,21][6,21][7,21][8,21] [5,20][6,20][7,20][8,20] [5,19][6,19][7,19][8,19]

becomes:
 [4,24][5,24][6,24][7,24] [4,23][5,23][6,23][7,23] [4,22][5,22][6,22][7,22] [4,21][5,21][6,21][7,21] 
Well, don't do that. With a 25^3 map, it's pretty inefficient. You're moving everything, but you only need to add a few rows or columns (newly loaded areas), and remove a few others on the other side (areas to unload). The solution's pretty simple (though maybe not so trivial to explain, so bear with me). Instead, handle the array so the corners of the currently loaded map are at some arbitrary point in the array. Then you only need to update one row or column of the array at a time. This way:
 [5,22][6,22][7,22][8,22] [5,21][6,21][7,21][8,21] [5,20][6,20][7,20][8,20] [5,19][6,19][7,19][8,19]

becomes:
 [5,22][6,22][7,22]|[4,22] [5,21][6,21][7,21]|[4,21] ------------------+------ [5,24][6,24][7,24]|[4,24] [5,23][6,23][7,23]|[4,23]

The lines mark where the array wraps. All four corners of the map are at the plus sign. Also notice that some of the original elements haven't changed at all. You'll need to keep track of where your map begins in the array (the plus sign).This way, when you want to move 1 to the left, you only have to change the position of the plus sign and replace one column with new values, instead of the whole map.

I hope that makes sense. I'm not good at explaining these things...

##### Share on other sites
Wouldn't this require alot of If statements to test if wrapping is necessary and also eliminate the ability to access it directly?

Direct:
 map[x][y] 

function:
 chunk* getChunk(int x, int y) { if (x<0 || x>width) { //wrap } if (y<0 || y>length) { //wrap } return map[x][y] } 

Is there someway to have quick access? (This is a total must!)

##### Share on other sites

Wouldn't this require alot of If statements to test if wrapping is necessary and also eliminate the ability to access it directly?

...

Is there someway to have quick access? (This is a total must!)

Why would you need to access it directly? Working with the 2D example still, access the map through a wrapper method, for example [font="Courier New"]map.At(x,y)[/font] in which you hide all the dirty-looking code. A bunch of if statements isn't going to be speed-critical unless it's in a tight loop, so it's not going to slow the application down as much as the original method, which repeatedly moves the whole array.

Sorry if I'm using the wrong terms but I'm pretty sure I'm sound in theory. I don't want to waste a lot of your time sending you down the wrong path though, so could someone please check and offer an expert opinion on what I've said?

EDIT: I see, the if statements are in a tight loop. You could possibly optimise further by setting a flag on all adjacent voxels whenever a voxel changes type or state, and then reacting only when this flag is on.

##### Share on other sites
The only problem is my game is based on reactions. So I need to get the voxels data over 3000 times average.
My current access routine is too slow, and implementing this map scrolling technique would make it slower

How it is now does take alot of time to move when the map scrolls, however no time at all when the voxel needs to be retrieved.
chunk[x][y];

If there was a way to access a chunk directly this would be perfect, because it would make the map faster and not slow the voxel access.

##### Share on other sites

The only problem is my game is based on reactions. So I need to get the voxels data over 3000 times average.

I think it would probably be best to optimise here. Are you checking every single voxel in memory against its neighbours, each update? If so, then can you pinpoint whatever causes a reaction, and only update the affected blocks?

##### Share on other sites
Each chunk has a list of blocks that need checking.

Basically when the player changes a block, the block and it's 6 neighbors are added to the list.

When updating the game checks all of the chunks, and if the Chunks list has contents then it calls the update for the chunk.
The update for the chunks goes threw the list and checks if the blocks change the neighbors if not it's removed and if something changes the changed voxels are moved.

##### Share on other sites
I think I could understand better if you told how changing blocks works.

Say the player changes a block on the map what does the code do to locate the block and chunk?

Your right when moving this is the most efficient, however I'm having trouble understanding how the block access could be realtime.

Thanks for all the help so far, this is a problem that has infinite solutions and finding the best is not simple.

Again thanks!

##### Share on other sites
Don't give up on the thread. I will post again later on when I have time.

##### Share on other sites
Sappharos,
I'm not giving up, and I appreciate all of the ideas presented. Thanks
Awaiting your next post.

##### Share on other sites
Correct me if I'm wrong, but I'm going to work with the assumption that you're working with C++, and making something similar to Minecraft. Assuming every voxel is a metre cubed (as in MC), the map information loaded in memory at any one time is 0.4 kilometres cubed. Maybe you could consider scaling down the y dimension, as because of the layout of terrain you generally can't see very far up or down. Taking it down to 12 chunks deep (25x12x25) would reduce the amount of information held in memory to less than half.

I have to ask, what kind of event is happening in your world that causes up to 500 interactions per update? In Minecraft that would be on the scale of a large forest fire, or a tidal wave. Bear in mind that what you are trying to do may simply be pushing the limits of your hardware (though I doubt it is impossible to optimise).

The most important thing is to find a way to load/unload chunks without shifting every single element in the array. Even if you do manage to make it do this at a reasonable speed, what if later on you want to increase the view distance from 400 to 640 metres, scaling by 1.6 for each dimension and expanding the map from 25^3 to 40^3? Then the problem comes back because you have about 4 times as much information to process.

You could try holding the chunk pointers in a 3D dynamic array, so you can add/remove elements to the array instead of shifting their data. Or find a speed-efficient way to implement the wrapping technique described earlier (use modulus as Trienco suggested, not if statements). Could you use pointer arithmetic anywhere?

Okay, I'm just throwing ideas out there now. I'm not sure if my opinion is really that valuable; some/most/all of these ideas may be ignorant, half-baked, poorly explained, bad practice, or just plain barmy. However, see what you think.

In total you currently store 400 x 400 x 400 voxels in memory at a time. If you haven't already, try changing the size and number of the chunks, so instead of 25 x 16 you could have 16 x 25, 20 x 20, 8 x 50, or 23 x 17. This may make certain operations faster at the expense of others, but will retain the total number of chunks in memory.

You could also try loading the map in small stages over time, between frames.

##### Share on other sites

You could also try loading the map in small stages over time, between frames.

The chunk pointers are in a 18x18x18 array
When the map shifts the chunks that need to be loaded are marked as NULL.
The coords of the chunks that are marked NULL are stored to a list.
Each frame if while the list is not empty it performs 16 events from the list weither it be Saving or Loading a chunk.
Doing this I can get 30fps while loading and 60 while maps not moving.

The main problem is when reactions are happening, like water moving. I do need to be able to handle Tital waves, etc.

I cant even get 512 voxels moving while sustaining >30FPS.

Here is what I'm talking about:
[media]
[/media]

##### Share on other sites
I found that video strange because it should go faster as the number of water chunks are absorbed by the water below it, but this does not happen. It seems the slowdown is a constant, so it is work you are doing all the time for some reason. What is this work?

What is this saving or loading of a chunk? Surely you are not loading from disk or saving to disk every frame??????

Working on my own terrain, I recently changed it to be a dynamically loading terrain. The way I implemented it is pretty simple. At first, you simply load all of your terrain into your world. You have an extra ring buffer around the entire viewable area that is beyond what can be viewed. For example, I load a 4096x4096 map. The boarder of it, 1024 units is my staging area. So, if I move 1024 units up, I shift the entire map contents down, recenter myself to be in the middle of the 4096x4096 map, and load in new data to the 1024 area. In my setup, the the data stride is 8 bytes per unit, so the 1024x1024 section is 8 MB. The rest of the map is shifted, resulting in a copy of 72MB. This sounds enormous, however, on a single thread running on my i7-920, I load from the disk, and preform the necessary copy in .08 seconds. Now, this may seem like a long amount of time, but it only occurs if the camera moves outside of the 1024x1024 bounds, then everything is reset. So, in reality, this will happen very infrequently and when it does it is often unnoticeable.

Your problem seems to be that you are preforming a DEEP copy of everything instead of working with large chunks, that hold all the smaller pieces. So, instead of moving 500000000 chunks, you can move a fraction.

Specifically, this
 for (int z=0; z<25*16; z++) { for (int y=0; y<25*16; y++) { for (int x=0; x<24*16; x++) { testWorld(x,y,z) = testWorld(x+16,y,z); } } } 
To put it bluntly, this has no place in any code at all. This will run 64,000,000 times and is veryyyyy scarey. Subdivide your world into manageable chunks that hold all the little blocks. So, when you need to shift your world, you can do it by shifting 1000 of these chunks instead of 64 million of them

##### Share on other sites
-=Map Shifting=-
That is how I'm doing it
The chunk pointers are in a 19x19x19 array[/quote]

Map = 19x19x19 Chunks*
Chunk = 16x16x16 Voxels
Voxel = 1 char BlockID, 1 char Data

This is dealing with moving the map and the structure of the data.

@smasherprog
With a 256x256x256 map,
are you saying allocate 16x16x16 pointers, then in each object 16x16x16 voxels.
Or allocate a 256x256x256 map and only move 16x16x16 sections at a time?

However in the above video I'm talking about the time it takes to update, which is unrelated to moving the map. (Should I make this a new topic now? )
I think the problem is the time it takes to lactate the data for each block.
It has to tell the location to the Map then the map returns the data after finding what Chunk and Voxel it is.

I am not proud of this Water update code as why I am trying to make it more efficient and I still think it's related to the map being split.

I know this is complicated and thanks in advance.

moveLiquid()
int chunk::moveLiquid(unsigned char otherBlock, unsigned char otherBlockAmount, int x, int y, int z) { //this = recieving //other = giving //Move from current pos in array to world Target iVertex3d pos = { x, y, z}; world0->setTarget(pos,arrayPos); if (world0->blockExists()) { unsigned char thisBlock = world0->getBlock(); unsigned char thisBlockAmount = world0->getData(); // /!\ experiment if (thisBlockAmount > otherBlockAmount) { return 0; } //int otherBlockAmount = block[otherBlock].amount; if ( thisBlock == otherBlock) { //return 0; // /!\ Here if (thisBlockAmount!=0 && thisBlockAmount<255) { unsigned char accept=(255-thisBlockAmount); if (accept>=otherBlockAmount-L_MIN) { accept=otherBlockAmount-L_MIN; } if (accept>L_MIN) { accept = L_MIN; } //thisBlockAmount-=accept; world0->setData(thisBlockAmount+accept); //Mix_PlayChannel( -1, sExpand, 0 ); /* unsigned char diff = otherBlockAmount - thisBlockAmount; if (diff <= 10) { unsigned char newDiff = diff/2; world0->setData( thisBlockAmount+newDiff); return diff-newDiff; } */ world0->blockLiquidBufferAdd(); return accept; } else { return 0; } } /*else if (world0->getBlock() == water && otherBlock == lava) { // /!\Water Lava Collision Code world0->setBlock(steam); return 0; }*/ else { return 0; } } else { // ? x,y,z-1 ? short giveAmount= L_MIN; if (giveAmount>otherBlockAmount-L_MIN) { giveAmount = otherBlockAmount-L_MIN; } if (giveAmount<0) { return 0; } world0->setBlock(otherBlock,giveAmount); //if (amount==1) //{ //liquidBufferAdd(x,y,z); //} //Mix_PlayChannel( -1, sExpand, 0 ); world0->blockLiquidBufferAdd(); if (abs(L_MIN - otherBlockAmount) < 20) { world0->setData( otherBlockAmount); return giveAmount; } return giveAmount; } /*if (block[world0->getBlock()].group == water5) { bool stop = true; }*/ }

updateLiquid()
inline void chunk::updateLiquid() { unsigned char thisBlock = position[updateCurrent.x][updateCurrent.y][updateCurrent.z]; int thisBlockAmount = data[updateCurrent.x][updateCurrent.y][updateCurrent.z]; iVertex3d current = updateCurrent; //bool sound=false; if (thisBlockAmount==0 || (position[updateCurrent.x][updateCurrent.y][updateCurrent.z]==air)) { return; //cout << "/!\\ " << space[updateCurrent.x][updateCurrent.y][updateCurrent.z].amount << " in array at pos " << i << "\n"; } current.z-=1; // ^ world0->setTarget(current,arrayPos); if (world0->blockExists()==false) //Move down a block { position[updateCurrent.x][updateCurrent.y][updateCurrent.z]=air; world0->setBlock(thisBlock); world0->setData(thisBlockAmount); //world0->blockLiquidBufferAdd(updatew Current.x,updateCurrent.y,updateCurrent.z+1,arrayPos.x,arrayPos.y,arrayPos.z); //sound=true; world0->setTarget(updateCurrent,arrayPos); world0->blockLiquidBufferAdd(); } else if (world0->getBlock()== ocean) //Delete if on the Ocean { position[updateCurrent.x][updateCurrent.y][updateCurrent.z]=air; world0->setTarget(updateCurrent,arrayPos); world0->blockLiquidBufferAdd(); } else if (world0->blockExists()==true) // Try to add to block Below { unsigned char blockUnder = world0->getBlock(); int blockUnderAmount = world0->getData(); if (blockUnderAmount!=0 && blockUnder == thisBlock) { int accept=(255-blockUnderAmount); if (accept>=thisBlockAmount) { accept=thisBlockAmount; } data[updateCurrent.x][updateCurrent.y][updateCurrent.z]-=accept; world0->setData(blockUnderAmount+=accept); //liquidBufferAdd(updateCurrent.x,updateCurrent.y,updateCurrent.z); //liquidBufferAdd(updateCurrent.x,updateCurrent.y,updateCurrent.z-1); if (data[updateCurrent.x][updateCurrent.y][updateCurrent.z]==0) { //world0->setTarget(updateCurrent.x,updateCurrent.y,updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); //world0->delBlock(); position[updateCurrent.x][updateCurrent.y][updateCurrent.z] = air; } else { } world0->setTarget(updateCurrent,arrayPos); world0->blockLiquidBufferAdd(); } //// Sand under water? If so MERGE ONLY IF WATER! else if (blockUnder == sand && block[position[updateCurrent.x][updateCurrent.y][updateCurrent.z]].group == water5) { if (thisBlockAmount>1) { position[updateCurrent.x][updateCurrent.y][updateCurrent.z]-=1; if (block[position[updateCurrent.x][updateCurrent.y][updateCurrent.z]].amount==0) { position[updateCurrent.x][updateCurrent.y][updateCurrent.z] = air; } } else { position[updateCurrent.x][updateCurrent.y][updateCurrent.z]=air; } world0->setTarget(current,arrayPos); world0->setBlock(dirt); world0->setTarget(updateCurrent.x,updateCurrent.y,updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); world0->blockLiquidBufferAdd(); } } //Refresh Variables thisBlock = position[updateCurrent.x][updateCurrent.y][updateCurrent.z]; thisBlockAmount = data[updateCurrent.x][updateCurrent.y][updateCurrent.z]; current.z+=1; //Current is updateCurrent if (thisBlock!=air) { if (thisBlockAmount>L_MIN) { ////Disperse int iRand = rand()%4; int oldAmount = thisBlockAmount; switch(iRand) { case 0: { world0->setTarget(updateCurrent.x, updateCurrent.y-1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y-1, updateCurrent.z); } world0->setTarget(updateCurrent.x-1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x-1, updateCurrent.y, updateCurrent.z); } world0->setTarget(updateCurrent.x+1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x+1, updateCurrent.y, updateCurrent.z); } world0->setTarget(updateCurrent.x, updateCurrent.y+1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y+1, updateCurrent.z); } data[updateCurrent.x][updateCurrent.y][updateCurrent.z] = thisBlockAmount; if (oldAmount != thisBlockAmount) { world0->setTarget(updateCurrent.x,updateCurrent.y,updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); world0->blockLiquidBufferAdd(); } if (thisBlockAmount<L_MIN) { position[updateCurrent.x][updateCurrent.y][updateCurrent.z] = air; } } case 1: { world0->setTarget(updateCurrent.x, updateCurrent.y+1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y+1, updateCurrent.z); } world0->setTarget(updateCurrent.x, updateCurrent.y-1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y-1, updateCurrent.z); } world0->setTarget(updateCurrent.x-1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x-1, updateCurrent.y, updateCurrent.z); } world0->setTarget(updateCurrent.x+1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x+1, updateCurrent.y, updateCurrent.z); } data[updateCurrent.x][updateCurrent.y][updateCurrent.z] = thisBlockAmount; if (oldAmount != thisBlockAmount) { world0->setTarget(updateCurrent.x,updateCurrent.y,updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); world0->blockLiquidBufferAdd(); } if (thisBlockAmount<L_MIN) { position[updateCurrent.x][updateCurrent.y][updateCurrent.z] = air; } } case 2: { world0->setTarget(updateCurrent.x+1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x+1, updateCurrent.y, updateCurrent.z); } world0->setTarget(updateCurrent.x, updateCurrent.y+1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y+1, updateCurrent.z); } world0->setTarget(updateCurrent.x, updateCurrent.y-1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y-1, updateCurrent.z); } world0->setTarget(updateCurrent.x-1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x-1, updateCurrent.y, updateCurrent.z); } data[updateCurrent.x][updateCurrent.y][updateCurrent.z] = thisBlockAmount; if (oldAmount != thisBlockAmount) { world0->setTarget(updateCurrent.x,updateCurrent.y,updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); world0->blockLiquidBufferAdd(); } if (thisBlockAmount<L_MIN) { position[updateCurrent.x][updateCurrent.y][updateCurrent.z] = air; } } case 3: { world0->setTarget(updateCurrent.x-1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x-1, updateCurrent.y, updateCurrent.z); } world0->setTarget(updateCurrent.x+1, updateCurrent.y, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x+1, updateCurrent.y, updateCurrent.z); } world0->setTarget(updateCurrent.x, updateCurrent.y+1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y+1, updateCurrent.z); } world0->setTarget(updateCurrent.x, updateCurrent.y-1, updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); if (thisBlockAmount>L_MIN && (world0->getBlockAmount()<thisBlockAmount || world0->getBlock() == air)) { thisBlockAmount-=moveLiquid(thisBlock, thisBlockAmount,updateCurrent.x, updateCurrent.y-1, updateCurrent.z); } data[updateCurrent.x][updateCurrent.y][updateCurrent.z] = thisBlockAmount; if (oldAmount != thisBlockAmount) { world0->setTarget(updateCurrent.x,updateCurrent.y,updateCurrent.z,arrayPos.x,arrayPos.y,arrayPos.z); world0->blockLiquidBufferAdd(); } if (thisBlockAmount<L_MIN) { position[updateCurrent.x][updateCurrent.y][updateCurrent.z] = air; } } } } } }