Reputation:
My voxel system uses a flat 3D array dynamically allocated at runtime for each chunk, however generating millions of cubes per chunk isn't feasible so I need to optimize.
The first optimization I intend to implement is of course to not generate mesh data for occluded voxels, this is a good idea on paper but I don't know how to do it.
All my attempts have ended up with hard to debug memory allocation issues and as such I have to throw the towel in and ask more knowledgeable people as I'm at loss.
My current incarnation of this is as such
int8_t x = 0, y= 0, z = 0;
const int MAX = CHUNKSIZE-1;
const int MIN = 0;
int8_t sPosX = (x - 1 < MIN) ? x : x-1;
int8_t sPosY = (y - 1 < MIN) ? y : y-1;
int8_t sPosZ = (z - 1 < MIN) ? z : z-1;
int8_t ePosX = (x + 1 > MAX) ? x : x+1;
int8_t ePosY = (y + 1 > MAX) ? y : y+1;
int8_t ePosZ = (z + 1 > MAX) ? z : z+1;
int8_t a=sPosX, b=sPosY, c=sPosZ;
int8_t add = 0;
BlockType BT = BT_grass;
scene::SMesh* mesh = new scene::SMesh();
for(x = 0; x <= MAX; x++)
{
for(y = 0; y <= MAX; y++)
{
for(z = 0; z <= MAX; z++)
{
cm = b_blocks[x][y][z].material;
//b_blocks[x][y][z].setFlags(0xFE, BT);
if( !b_blocks[x][x][z].isActive() )
{
continue;
}
else
{
if(sPosX == MIN)
{
createCube(x,y,z,c,mesh,cm);
}
else
{
if(a<=ePosX)
{
if(b<=ePosY)
{
if(c<=ePosZ)
{
printf("x %d, y %d, z %d\n", x, y, z);
if(!b_blocks[x][y][z].isActive())
{
add = 1;
}
}
}
}
if(add == 1)
{
createCube(x,y,z,c,mesh,cm);
add = 0;
}
}
}
}
}
}
The if(sPosX == MIN) is a hack I implemented to not segfault on generating the chunk (otherwise it segfaults with a memory access violation on generating block[CHUNKSIZE][CHUNKSIZE][CHUNKSIZE], which isn't very nice. This hack inadvertently makes sure that all cubes are generated however and is just as unappealing.
The concise questions here are as follows: What part of my logic is broken? (presumably all of it) and how would one properly check neighbouring blocks in a fast manner that does not cause an out of bounds error? (I tried by manually coding exceptions for every last corner case but that proved to be unmaintainable and was orders of magnitude slower and prone to segfaulting)
Upvotes: 0
Views: 528
Reputation: 19782
You don't show how the b_blocks variable is declared or initialized, but given that you're getting a segmentation error, it's likely you declared it as a smaller size than your CHUNK_SIZE.
Upvotes: 0
Reputation: 1824
I would use something like:
class BlockChunk final
{
public:
static constexpr int sizeXShift = 4, sizeYshift = 8, sizeZshift = 4;
static constexpr int sizeX = 1 << sizeXShift; // 2 ** sizeXShift
static constexpr int sizeY = 1 << sizeYShift;
static constexpr int sizeZ = 1 << sizeZShift;
static constexpr int sizeXRelativeMask = sizeX - 1; // mask to get position mod sizeX (faster than % because negative inputs to % return negative answers which need more adjusting whereas masking always returns the positive answer)
static constexpr int sizeYRelativeMask = sizeY - 1;
static constexpr int sizeZRelativeMask = sizeZ - 1;
static constexpr int sizeXChunkBaseMask = ~sizeXRelativeMask; // mask to get position - relativePosition (aka chunk base position)
static constexpr int sizeYChunkBaseMask = ~sizeYRelativeMask;
static constexpr int sizeZChunkBaseMask = ~sizeZRelativeMask;
private:
Block blocks[sizeX][sizeY][sizeZ];
public:
const PositionI basePosition;
BlockChunk(PositionI basePosition)
: basePosition(basePosition)
{
}
Block &at(PositionI relative)
{
assert(relative.x >= 0 && relative.x < sizeX);
assert(relative.y >= 0 && relative.y < sizeY);
assert(relative.z >= 0 && relative.z < sizeZ); // these asserts are important for finding out-of-bounds bugs
return blocks[relative.x][relative.y][relative.z];
}
static PositionI getRelativePosition(PositionI p)
{
p.x &= sizeXRelativeMask;
p.y &= sizeYRelativeMask;
p.z &= sizeZRelativeMask;
return p;
}
static PositionI getChunkBasePosition(PositionI p)
{
p.x &= sizeXChunkBaseMask;
p.y &= sizeYChunkBaseMask;
p.z &= sizeZChunkBaseMask;
return p;
}
};
class BlockIterator;
class BlockWorldBase
{
friend class BlockIterator;
private:
std::unordered_map<PositionI, std::shared_ptr<BlockChunk>> chunks;
BlockChunk *getOrMakeChunk(PositionI chunkBasePosition)
{
std::shared_ptr<BlockChunk> &chunk = chunks[chunkBasePosition];
if(chunk == nullptr)
chunk = std::make_shared<BlockChunk>(chunkBasePosition);
return chunk.get();
}
};
class BlockWorld;
class BlockIterator final
{
friend class BlockWorld;
private:
BlockChunk *chunk;
BlockWorldBase *world;
PositionI chunkBasePosition, relativePosition;
void updateChunk()
{
chunk = world->getOrMakeChunk(chunkBasePosition);
}
BlockIterator(BlockWorldBase *world, PositionI position)
: chunk(),
world(world),
chunkBasePosition(BlockChunk::getChunkBasePosition(position)),
relativePosition(BlockChunk::getRelativePosition(position))
{
updateChunk();
}
public:
PositionI getPosition() const
{
return relativePosition + chunkBasePosition;
}
Block &get()
{
return chunk->at(relativePosition);
}
BlockIterator &operator +=(PositionI deltaPosition) // move to point to a new block
{
PositionI newRelativePosition = relativePosition + deltaPosition;
if(BlockChunk::getRelativePosition(newRelativePosition) != newRelativePosition) // if the new position is outside of this chunk
{
relativePosition = BlockChunk::getRelativePosition(newRelativePosition);
chunkBasePosition += BlockChunk::getChunkBasePosition(newRelativePosition);
updateChunk();
}
else
{
relativePosition = newRelativePosition;
}
}
friend BlockIterator operator +(PositionI p, BlockIterator bi)
{
bi += p;
return bi;
}
friend BlockIterator operator +(BlockIterator bi, PositionI p)
{
bi += p;
return bi;
}
};
class BlockWorld final : public BlockWorldBase
{
public:
BlockIterator getIterator(PositionI p)
{
return BlockIterator(this, p);
}
};
If you leave the asserts in and access thru BlockIterator you shouldn't ever seg-fault
void drawBlock(Renderer &renderer, BlockIterator bi)
{
BlockIterator nxBlockIterator = bi + PositionI(-1, 0, 0);
BlockIterator pxBlockIterator = bi + PositionI(1, 0, 0);
BlockIterator nyBlockIterator = bi + PositionI(0, -1, 0);
BlockIterator pyBlockIterator = bi + PositionI(0, 1, 0);
BlockIterator nzBlockIterator = bi + PositionI(0, 0, -1);
BlockIterator pzBlockIterator = bi + PositionI(0, 0, 1);
if(nxBlockIterator.get().isPXFaceBlocked())
bi.get().renderNXFace(renderer, bi);
if(pxBlockIterator.get().isNXFaceBlocked())
bi.get().renderPXFace(renderer, bi);
if(nyBlockIterator.get().isPYFaceBlocked())
bi.get().renderNYFace(renderer, bi);
if(pyBlockIterator.get().isNYFaceBlocked())
bi.get().renderPYFace(renderer, bi);
if(nzBlockIterator.get().isPZFaceBlocked())
bi.get().renderNZFace(renderer, bi);
if(pzBlockIterator.get().isNZFaceBlocked())
bi.get().renderPZFace(renderer, bi);
bi.get().renderCenter(renderer, bi);
}
Upvotes: 1