I'm creating a voxel-based game and have been working on it for quite a while.
I've come to a point where I have to load and unload chunks on the fly. I've created a chunk manager class and it's working, but it's quite slow and I would like to improve its speed.
Header file:
#pragma once
#include <d3d11.h>
#include <vector>
#include <unordered_map>
#include <thread>
#include "Chunk.h"
#include "Renderer.h"
#include "Noise.h"
#define WORLD_HEIGHT 4
#define SEED 5000
#define WATER_LEVEL 128
#define MAX_HEIGHT 256
#define BLOCK_BIOME_WEIGHT 1 //less is havier
#define BIOME_MIN 1.4
#define BIOME_MAX 0.7
using namespace std;
class Chunk;
class ChunkManager
{
public:
ChunkManager(void);
Chunk* getChunkAt(int x, int y, int z);
void render(Renderer& renderer);
void initialize(Camera& camera);
void rebuild(double delta, Renderer& renderer);
void update(double delta, Camera& camera, vector<XMFLOAT4>& frustumPlanes);
float getWorldHeight(int x, int y);//TODO: Create new world class and add world related methods there
public:
bool centerLoaded;
private:
int renderDistance;
int oldChunkX, oldChunkY, oldChunkZ;
float oldCamYaw, oldCamPitch;
unordered_map<string, Chunk*> loadedChunks;
vector<Chunk*> rebuildChunks;
vector<Chunk*> removableChunks;
vector<Chunk*> renderChunks;
vector<Chunk*> visibleChunks;
double timer, timer2, removeTimer, tickTimer;
vector<Noise*> noises;
private:
void addChunks(int x, int z);
void cullAABB(vector<XMFLOAT4>& frustumPlanes);
void loadChunks(vector<Chunk*>& tempChunks, int& chunkX, int& chunkZ);
};
Class:
#include "ChunkManager.h"
ChunkManager::ChunkManager() {
renderDistance = 8;//[5, 6, 7, 8, 12, 13, 14, 15, 20, 22]
centerLoaded = false;
timer = 0;
timer2 = 0;
tickTimer = 0;
removeTimer = 0;
srand(SEED);
noises.push_back(new Noise(0, 0, 0, rand()));
noises.push_back(new Noise(1, 0, 0, rand()));
noises.push_back(new Noise(2, 0, 0, rand()));
}
float ChunkManager::getWorldHeight(int x, int z){
return (noises[0]->getNoise(x, z) + (noises[1]->getNoise(x, z) * noises[2]->getNoise(x, z))) * 64 + WATER_LEVEL;
}
void ChunkManager::addChunks(int x, int z){
for(int y = 0; y < WORLD_HEIGHT; y++){
Chunk* chunk = new Chunk(this, x, y, z);
rebuildChunks.push_back(chunk);
string s = to_string(x) + "," + to_string(y) + "," + to_string(z);
loadedChunks.emplace(s, chunk);
}
}
Chunk* ChunkManager::getChunkAt(int x, int y, int z){
string s = to_string(x) + "," + to_string(y) + "," + to_string(z);
auto& it = loadedChunks.find(s);
if(it != loadedChunks.end()){
return it->second;
}
return NULL;
}
void ChunkManager::render(Renderer& renderer){
for(int i = 0; i < visibleChunks.size(); i++){
visibleChunks[i]->render(renderer);
}
}
void ChunkManager::initialize(Camera& camera){
int x = rand() % 48;
int z = rand() % 48;
int y = WATER_LEVEL;
int checks = 1000;
while(y <= WATER_LEVEL){
x = rand() % 48;
z = rand() % 48;
y = getWorldHeight(x, z);
checks--;
if(checks == 0){
throw std::invalid_argument("Too many checks !");
}
}
int centerX = oldChunkX = floor(x / CHUNK_SIZE);
int centerZ = oldChunkZ = floor(z / CHUNK_SIZE);
for(int cx = -renderDistance-1; cx <= renderDistance+1; cx++){
for(int cz = -renderDistance-1; cz <= renderDistance+1; cz++){
addChunks(cx + centerX, cz + centerZ);
}
}
camera.setPosition(BLOCK_RENDER_SIZE * x + BLOCK_RENDER_SIZE / 2, BLOCK_RENDER_SIZE * y + BLOCK_RENDER_SIZE * 2, BLOCK_RENDER_SIZE * z + BLOCK_RENDER_SIZE / 2);
}
void ChunkManager::rebuild(double delta, Renderer& renderer){
if(!removableChunks.empty()){//TODO: THREADED
if(removeTimer >= 0.01){
removeTimer= 0;
Chunk* c = removableChunks.back();
delete c;//very slow
removableChunks.pop_back();
}
removeTimer+=delta;
}else{
removeTimer = 0;
}
if(!centerLoaded){
while(!rebuildChunks.empty()){
Chunk* c = rebuildChunks.back();
c->initialize(renderer);
if((c->x-oldChunkX)*(c->x-oldChunkX) + (c->z-oldChunkZ)*(c->z-oldChunkZ) <= renderDistance*renderDistance + renderDistance * 0.8f){
if(!c->isAirChunk && !c->isMarkedForRemoval()) { renderChunks.push_back(c); }
}
rebuildChunks.pop_back();
}
centerLoaded = true;
}
if(!rebuildChunks.empty()){//TODO: THREADED
if(timer2 >= 0.01){
timer2 = 0;
Chunk* c = rebuildChunks.back();
if(c->isInitialized()){
c->rebuild(renderer);
}else{
c->initialize(renderer);
}
rebuildChunks.pop_back();
}
timer2+=delta;
}
}
void ChunkManager::update(double delta, Camera& camera, vector<XMFLOAT4>& frustumPlanes){
int chunkX = floor(XMVectorGetX(camera.camPosition) / double(BLOCK_RENDER_SIZE * CHUNK_SIZE));
int chunkZ = floor(XMVectorGetZ(camera.camPosition) / double(BLOCK_RENDER_SIZE * CHUNK_SIZE));
bool moved = oldChunkX != chunkX || oldChunkZ != chunkZ;
if(moved){
renderChunks.clear();
vector<Chunk*> tempChunks;
for(pair<const string, Chunk*>& pair : loadedChunks){//Remove chunks from list
Chunk* c = pair.second;
if(oldChunkX>chunkX){
if(c->x == oldChunkX + renderDistance+1){
c->markForRemoval(true);
removableChunks.emplace_back(c);
}else if(c->x ==oldChunkX - renderDistance-1){
rebuildChunks.emplace_back(c);
}
}else{
if(c->x ==oldChunkX - renderDistance-1){
c->markForRemoval(true);
removableChunks.emplace_back(c);
}else if(c->x == oldChunkX + renderDistance+1){
rebuildChunks.emplace_back(c);
}
}
if(oldChunkZ<chunkZ){
if(c->z == oldChunkZ - renderDistance-1){
c->markForRemoval(true);
removableChunks.emplace_back(c);
}else if(c->z == oldChunkZ + renderDistance+1){
rebuildChunks.emplace_back(c);
}
}else{
if(c->z == oldChunkZ + renderDistance+1){
c->markForRemoval(true);
removableChunks.emplace_back(c);
}else if(c->z == oldChunkZ - renderDistance-1){
rebuildChunks.emplace_back(c);
}
}
if(!c->isMarkedForRemoval()){
tempChunks.emplace_back(c);
}
if((c->x-chunkX)*(c->x-chunkX) + (c->z-chunkZ)*(c->z-chunkZ) <= renderDistance*renderDistance + renderDistance * 0.8f){
if(!c->isAirChunk && !c->isMarkedForRemoval()) { renderChunks.emplace_back(c); }
}
}
loadChunks(tempChunks, chunkX, chunkZ);
loadedChunks.clear();
for(Chunk* c : tempChunks){
string s = to_string(c->x) + "," + to_string(c->y) + "," + to_string(c->z);
loadedChunks.emplace(s, c);
}
oldChunkX = chunkX;
oldChunkZ = chunkZ;
}
timer+=delta;
if(timer >= 0.01 && (oldCamYaw != camera.camYaw || oldCamPitch != camera.camPitch || moved)){
cullAABB(frustumPlanes);
timer = 0;
oldCamYaw = camera.camYaw;
oldCamPitch = camera.camPitch;
}
tickTimer+=delta;
if(tickTimer >= 0.05){//0.05
for(pair<const string, Chunk*>& pair : loadedChunks){
pair.second->update(delta);
}
//update
tickTimer = 0;
}
}
void ChunkManager::loadChunks(vector<Chunk*>& tempChunks, int& chunkX, int& chunkZ){
bool left = oldChunkX>chunkX;
bool right = oldChunkX<chunkX;
bool front = oldChunkZ<chunkZ;
bool back = oldChunkZ>chunkZ;
if(left){
for(int z = -renderDistance-1; z <= renderDistance+1; z++){
for(int y = 0; y < WORLD_HEIGHT; y++){
Chunk* chunk = new Chunk(this, chunkX - renderDistance-1, y, z + chunkZ);
rebuildChunks.push_back(chunk);
tempChunks.push_back(chunk);
}
}
}else if(right){
for(int z = -renderDistance-1; z <= renderDistance+1; z++){
for(int y = 0; y < WORLD_HEIGHT; y++){
Chunk* chunk = new Chunk(this, chunkX + renderDistance+1, y, z + chunkZ);
rebuildChunks.push_back(chunk);
tempChunks.push_back(chunk);
}
}
}else if(front){
for(int x = -renderDistance-1; x <= renderDistance+1; x++){
for(int y = 0; y < WORLD_HEIGHT; y++){
Chunk* chunk = new Chunk(this, x + chunkX, y, chunkZ + renderDistance+1);
rebuildChunks.push_back(chunk);
tempChunks.push_back(chunk);
}
}
}else if(back){
for(int x = -renderDistance-1; x <= renderDistance+1; x++){
for(int y = 0; y < WORLD_HEIGHT; y++){
Chunk* chunk = new Chunk(this, x + chunkX, y, chunkZ - renderDistance-1);
rebuildChunks.push_back(chunk);
tempChunks.push_back(chunk);
}
}
}
}
void ChunkManager::cullAABB(vector<XMFLOAT4>& frustumPlanes){//TODO: Make global
bool cull = false;
visibleChunks.clear();
for(Chunk* c : renderChunks){
cull = false;
for(int planeID = 0; planeID < 6; ++planeID){
XMVECTOR planeNormal = XMVectorSet(frustumPlanes[planeID].x, frustumPlanes[planeID].y, frustumPlanes[planeID].z, 0.0f);
float planeConstant = frustumPlanes[planeID].w;
XMFLOAT3 axisVert;
if(frustumPlanes[planeID].x < 0.0f)
axisVert.x = BLOCK_RENDER_SIZE * CHUNK_SIZE * c->x;
else
axisVert.x = BLOCK_RENDER_SIZE * CHUNK_SIZE * c->x + BLOCK_RENDER_SIZE * CHUNK_SIZE;
if(frustumPlanes[planeID].y < 0.0f)
axisVert.y = BLOCK_RENDER_SIZE * CHUNK_HEIGHT * c->y;
else
axisVert.y = BLOCK_RENDER_SIZE * CHUNK_HEIGHT * c->y + BLOCK_RENDER_SIZE * CHUNK_HEIGHT;
if(frustumPlanes[planeID].z < 0.0f)
axisVert.z = BLOCK_RENDER_SIZE * CHUNK_SIZE * c->z;
else
axisVert.z = BLOCK_RENDER_SIZE * CHUNK_SIZE * c->z + BLOCK_RENDER_SIZE * CHUNK_SIZE;
if(XMVectorGetX(XMVector3Dot(planeNormal, XMLoadFloat3(&axisVert))) + planeConstant < 0.0f){
cull = true;
break;
}
}
if(!cull){
visibleChunks.push_back(c);
}
}
}