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

Started by
17 comments, last by coderWalker 12 years, 9 months ago

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?
Advertisement
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.
If this post was helpful please +1 or like it !

Webstrand
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!
If this post was helpful please +1 or like it !

Webstrand
Don't give up on the thread. I will post again later on when I have time. :)
Sappharos,
I'm not giving up, and I appreciate all of the ideas presented. Thanks
Awaiting your next post.
If this post was helpful please +1 or like it !

Webstrand
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.

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]
If this post was helpful please +1 or like it !

Webstrand
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
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
-=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?

-=Voxel Updates=-
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;
}
}
}
}
}
}
If this post was helpful please +1 or like it !

Webstrand

This topic is closed to new replies.

Advertisement