#include "WorldFiles.h" #include "files.h" #include "rle.h" #include "binary_io.h" #include "../window/Camera.h" #include "../objects/Player.h" #include "../physics/Hitbox.h" #include "../voxels/voxel.h" #include "../voxels/Chunk.h" #include "../typedefs.h" #include "../maths/voxmaths.h" #include #include #include #include #include #include #define SECTION_POSITION 1 #define SECTION_ROTATION 2 #define SECTION_FLAGS 3 #define PLAYER_FLAG_FLIGHT 0x1 #define PLAYER_FLAG_NOCLIP 0x2 #define WORLD_SECTION_MAIN 1 using glm::ivec2; using glm::vec3; using std::ios; using std::unique_ptr; using std::filesystem::path; int bytes2Int(const ubyte* src, size_t offset){ return (src[offset] << 24) | (src[offset+1] << 16) | (src[offset+2] << 8) | (src[offset+3]); } void int2Bytes(int value, ubyte* dest, size_t offset){ dest[offset] = (char) (value >> 24 & 255); dest[offset+1] = (char) (value >> 16 & 255); dest[offset+2] = (char) (value >> 8 & 255); dest[offset+3] = (char) (value >> 0 & 255); } float bytes2Float(ubyte* src, uint offset){ uint32_t value = ((src[offset] << 24) | (src[offset+1] << 16) | (src[offset+2] << 8) | (src[offset+3])); return *(float*)(&value); } WorldFiles::WorldFiles(path directory, bool generatorTestMode) : directory(directory), generatorTestMode(generatorTestMode) { compressionBuffer = new ubyte[CHUNK_DATA_LEN * 2]; } WorldFiles::~WorldFiles(){ delete[] compressionBuffer; for (auto it = regions.begin(); it != regions.end(); it++){ WorldRegion region = it->second; if (region.chunksData == nullptr) continue; for (size_t i = 0; i < REGION_VOL; i++){ delete[] region.chunksData[i]; } delete[] region.chunksData; } regions.clear(); } void WorldFiles::put(Chunk* chunk){ assert(chunk != nullptr); int regionX = floordiv(chunk->x, REGION_SIZE); int regionY = floordiv(chunk->z, REGION_SIZE); int localX = chunk->x - (regionX * REGION_SIZE); int localY = chunk->z - (regionY * REGION_SIZE); ivec2 key(regionX, regionY); auto found = regions.find(key); if (found == regions.end()) { ubyte** chunksData = new ubyte*[REGION_VOL]; uint32_t* compressedSizes = new uint32_t[REGION_VOL]; for (uint i = 0; i < REGION_VOL; i++) { chunksData[i] = nullptr; } regions[key] = { chunksData, compressedSizes, true }; } WorldRegion& region = regions[key]; region.unsaved = true; size_t chunk_index = localY * REGION_SIZE + localX; ubyte* target_chunk = region.chunksData[chunk_index]; if (target_chunk) { delete[] target_chunk; } ubyte* chunk_data = chunk->encode(); size_t compressedSize = extrle::encode(chunk_data, CHUNK_DATA_LEN, compressionBuffer); delete[] chunk_data; ubyte* data = new ubyte[compressedSize]; for (size_t i = 0; i < compressedSize; i++) { data[i] = compressionBuffer[i]; } region.chunksData[chunk_index] = data; region.compressedSizes[chunk_index] = compressedSize; } path WorldFiles::getRegionFile(int x, int y) const { return directory/path(std::to_string(x) + "_" + std::to_string(y) + ".bin"); } path WorldFiles::getPlayerFile() const { return directory/path("player.bin"); } path WorldFiles::getWorldFile() const { return directory/path("world.bin"); } ubyte* WorldFiles::getChunk(int x, int y){ int regionX = floordiv(x, REGION_SIZE); int regionY = floordiv(y, REGION_SIZE); int localX = x - (regionX * REGION_SIZE); int localY = y - (regionY * REGION_SIZE); int chunkIndex = localY * REGION_SIZE + localX; assert(chunkIndex >= 0 && chunkIndex < REGION_VOL); ivec2 key(regionX, regionY); auto found = regions.find(key); if (found == regions.end()) { ubyte** chunksData = new ubyte * [REGION_VOL]; uint32_t* compressedSizes = new uint32_t[REGION_VOL]; for (uint i = 0; i < REGION_VOL; i++) { chunksData[i] = nullptr; } regions[key] = { chunksData, compressedSizes, true }; } WorldRegion& region = regions[key]; ubyte* data = region.chunksData[chunkIndex]; if (data == nullptr) { data = readChunkData(x, y, region.compressedSizes[chunkIndex]); if (data) { region.chunksData[chunkIndex] = data; } } if (data) { ubyte* decompressed = new ubyte[CHUNK_DATA_LEN]; extrle::decode(data, region.compressedSizes[chunkIndex], decompressed); return decompressed; } return nullptr; } ubyte* WorldFiles::readChunkData(int x, int y, uint32_t& length){ if (generatorTestMode) return nullptr; int regionX = floordiv(x, REGION_SIZE); int regionY = floordiv(y, REGION_SIZE); int localX = x - (regionX * REGION_SIZE); int localY = y - (regionY * REGION_SIZE); int chunkIndex = localY * REGION_SIZE + localX; path filename = getRegionFile(regionX, regionY); std::ifstream input(filename, std::ios::binary); if (!input.is_open()){ return nullptr; } input.seekg(0, ios::end); size_t file_size = input.tellg(); size_t table_offset = file_size - REGION_VOL * 4; uint32_t offset; input.seekg(table_offset + chunkIndex * 4); input.read((char*)(&offset), 4); offset = bytes2Int((const ubyte*)(&offset), 0); if (offset == 0){ input.close(); return nullptr; } input.seekg(offset); input.read((char*)(&offset), 4); length = bytes2Int((const ubyte*)(&offset), 0); ubyte* data = new ubyte[length]; input.read((char*)data, length); input.close(); return data; } void WorldFiles::write(const WorldInfo info){ if (!std::filesystem::is_directory(directory)) { std::filesystem::create_directories(directory); } writeWorldInfo(info); if (generatorTestMode) return; for (auto it = regions.begin(); it != regions.end(); it++){ if (it->second.chunksData == nullptr || !it->second.unsaved) continue; ivec2 key = it->first; writeRegion(key.x, key.y, it->second); } } void WorldFiles::writeWorldInfo(const WorldInfo& info) { BinaryWriter out; out.putCStr(WORLD_FORMAT_MAGIC); out.put(WORLD_FORMAT_VERSION); out.put(WORLD_SECTION_MAIN); out.putInt64(info.seed); out.put(info.name); files::write_bytes(getWorldFile(), (const char*)out.data(), out.size()); } bool WorldFiles::readWorldInfo(WorldInfo& info) { size_t length = 0; ubyte* data = (ubyte*)files::read_bytes(getPlayerFile(), length); if (data == nullptr){ std::cerr << "could not to read world.bin (ignored)" << std::endl; return false; } BinaryReader inp(data, length); inp.checkMagic(WORLD_FORMAT_MAGIC, length); /*ubyte version = */inp.get(); while (inp.hasNext()) { ubyte section = inp.get(); switch (section) { case WORLD_SECTION_MAIN: info.seed = inp.getInt64(); info.name = inp.getString(); break; } } return false; } void WorldFiles::writePlayer(Player* player){ vec3 position = player->hitbox->position; BinaryWriter out; out.put(SECTION_POSITION); out.putFloat32(position.x); out.putFloat32(position.y); out.putFloat32(position.z); out.put(SECTION_ROTATION); out.putFloat32(player->camX); out.putFloat32(player->camY); out.put(SECTION_FLAGS); out.put(player->flight * PLAYER_FLAG_FLIGHT | player->noclip * PLAYER_FLAG_NOCLIP); files::write_bytes(getPlayerFile(), (const char*)out.data(), out.size()); } bool WorldFiles::readPlayer(Player* player) { size_t length = 0; ubyte* data = (ubyte*)files::read_bytes(getPlayerFile(), length); if (data == nullptr){ std::cerr << "could not to read player.bin (ignored)" << std::endl; return false; } vec3 position = player->hitbox->position; BinaryReader inp(data, length); while (inp.hasNext()) { ubyte section = inp.get(); switch (section) { case SECTION_POSITION: position.x = inp.getFloat32(); position.y = inp.getFloat32(); position.z = inp.getFloat32(); break; case SECTION_ROTATION: player->camX = inp.getFloat32(); player->camY = inp.getFloat32(); break; case SECTION_FLAGS: { ubyte flags = inp.get(); player->flight = flags & PLAYER_FLAG_FLIGHT; player->noclip = flags & PLAYER_FLAG_NOCLIP; } break; } } player->hitbox->position = position; player->camera->position = position + vec3(0, 1, 0); return true; } void WorldFiles::writeRegion(int x, int y, WorldRegion& entry){ ubyte** region = entry.chunksData; uint32_t* sizes = entry.compressedSizes; for (size_t i = 0; i < REGION_VOL; i++) { int chunk_x = (i % REGION_SIZE) + x * REGION_SIZE; int chunk_y = (i / REGION_SIZE) + y * REGION_SIZE; if (region[i] == nullptr) { region[i] = readChunkData(chunk_x, chunk_y, sizes[i]); } } char header[10] = REGION_FORMAT_MAGIC; header[8] = REGION_FORMAT_VERSION; header[9] = 0; // flags std::ofstream file(getRegionFile(x, y), ios::out | ios::binary); file.write(header, 10); size_t offset = 10; char intbuf[4]{}; uint offsets[REGION_VOL]{}; for (size_t i = 0; i < REGION_VOL; i++) { ubyte* chunk = region[i]; if (chunk == nullptr){ offsets[i] = 0; } else { offsets[i] = offset; size_t compressedSize = sizes[i]; int2Bytes(compressedSize, (ubyte*)intbuf, 0); offset += 4 + compressedSize; file.write(intbuf, 4); file.write((const char*)chunk, compressedSize); } } for (size_t i = 0; i < REGION_VOL; i++) { int2Bytes(offsets[i], (ubyte*)intbuf, 0); file.write(intbuf, 4); } }