Implement chunk compression and decompression methods

This commit is contained in:
mrkubax10 2023-08-12 22:23:44 +02:00
parent b02d83e72d
commit 21554417d1
9 changed files with 303 additions and 110 deletions

View File

@ -140,7 +140,6 @@ if(BUILD_CLIENT)
src/game/window/keyboard.cpp
src/game/window/mouse.cpp
src/game/window/window.cpp
src/game/world/chunk.cpp
)
if(RENDERER_GL)
list(APPEND CLIENT_SOURCES

View File

@ -24,7 +24,7 @@ SOFTWARE.
#include "common/binary_utils.hpp"
static bool is_little_endian(){
bool polygun::utils::is_little_endian(){
unsigned x=0x76543210;
char* c=(char*)&x;
return *c==0x10;
@ -86,6 +86,86 @@ void polygun::utils::uint32_to_bytes(uint32_t data,char* output){
}
}
int32_t polygun::utils::bytes_to_int32(const char* data){
int32_t output;
if(is_little_endian()){
((char*)&output)[0]=data[0];
((char*)&output)[1]=data[1];
((char*)&output)[2]=data[2];
((char*)&output)[3]=data[3];
}
else{
((char*)&output)[3]=data[0];
((char*)&output)[2]=data[1];
((char*)&output)[1]=data[2];
((char*)&output)[0]=data[3];
}
return output;
}
void polygun::utils::int32_to_bytes(int32_t data,char* output){
if(is_little_endian()){
output[0]=((char*)&data)[0];
output[1]=((char*)&data)[1];
output[2]=((char*)&data)[2];
output[3]=((char*)&data)[3];
}
else{
output[3]=((char*)&data)[0];
output[2]=((char*)&data)[1];
output[1]=((char*)&data)[2];
output[0]=((char*)&data)[3];
}
}
uint64_t polygun::utils::bytes_to_uint64(const char* data){
uint64_t output;
if(is_little_endian()){
((char*)&output)[0]=data[0];
((char*)&output)[1]=data[1];
((char*)&output)[2]=data[2];
((char*)&output)[3]=data[3];
((char*)&output)[4]=data[4];
((char*)&output)[5]=data[5];
((char*)&output)[6]=data[6];
((char*)&output)[7]=data[7];
}
else{
((char*)&output)[7]=data[0];
((char*)&output)[6]=data[1];
((char*)&output)[5]=data[2];
((char*)&output)[4]=data[3];
((char*)&output)[3]=data[4];
((char*)&output)[2]=data[5];
((char*)&output)[1]=data[6];
((char*)&output)[0]=data[7];
}
return output;
}
void polygun::utils::uint64_to_bytes(uint64_t data,char* output){
if(is_little_endian()){
output[0]=((char*)&data)[0];
output[1]=((char*)&data)[1];
output[2]=((char*)&data)[2];
output[3]=((char*)&data)[3];
output[4]=((char*)&data)[4];
output[5]=((char*)&data)[5];
output[6]=((char*)&data)[6];
output[7]=((char*)&data)[7];
}
else{
output[7]=((char*)&data)[0];
output[6]=((char*)&data)[1];
output[5]=((char*)&data)[2];
output[4]=((char*)&data)[3];
output[3]=((char*)&data)[4];
output[2]=((char*)&data)[5];
output[1]=((char*)&data)[6];
output[0]=((char*)&data)[7];
}
}
float polygun::utils::bytes_to_float(const char* data) {
float output;
if(is_little_endian()){

View File

@ -28,10 +28,15 @@ SOFTWARE.
#include <cstdint>
namespace polygun::utils {
bool is_little_endian();
uint16_t bytes_to_uint16(const char* data);
void uint16_to_bytes(uint16_t data, char* output);
uint32_t bytes_to_uint32(const char* data);
void uint32_to_bytes(uint32_t data, char* output);
int32_t bytes_to_int32(const char* data);
void int32_to_bytes(int32_t data, char* output);
uint64_t bytes_to_uint64(const char* data);
void uint64_to_bytes(uint64_t data, char* output);
float bytes_to_float(const char* data);
void float_to_bytes(float data, char* output);
}

View File

@ -29,6 +29,8 @@ SOFTWARE.
namespace glm {
typedef vec<4, uint8_t, packed_highp> vec4ub;
typedef vec<3, uint8_t, packed_highp> vec3ub;
typedef vec<3, int, packed_highp> vec3i;
}
#endif // POLYGUN_UTILS_GLM_EXT_HPP

192
src/common/world/chunk.cpp Normal file
View File

@ -0,0 +1,192 @@
/*
PolyGun
Copyright (c) 2023 kacperks https://kacperks.cubesoftware.xyz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "common/world/chunk.hpp"
#include <cstring>
#include "common/binary_utils.hpp"
#include "common/logger.hpp"
static void huffman_encode(uint16_t num, std::vector<uint8_t>& output) {
// FIXME: Make this code scalable because currently it can only encode number which takes 3 bytes maximum.
uint8_t first_byte = num%0x80;
output.push_back(first_byte|((num>0x7F)<<7));
if(num>0x7F) {
num-=first_byte;
const uint16_t second_byte = num/0x80;
output.push_back(static_cast<uint8_t>(second_byte)|((second_byte>0x7F)<<7));
if(second_byte>0x7F)
output.push_back(second_byte/0x80);
}
}
namespace polygun::world {
Chunk::Chunk() :
m_chunk_data()
{
memset(m_chunk_data, 0, sizeof(m_chunk_data));
}
uint8_t Chunk::to_compressed_data(std::vector<uint8_t>& output) {
// Try CHUNK_COMPRESSION_MODE_FILL
const uint16_t match = get_node(glm::vec3ub(0, 0, 0));
bool fill_success = true;
for(unsigned char x = 0; x<CHUNK_SIZE; x++) {
for(unsigned char y = 0; y<CHUNK_SIZE; y++) {
for(unsigned char z = 0; z<CHUNK_SIZE; z++) {
if(get_node(glm::vec3ub(x, y, z))!=match) {
fill_success = false;
break;
}
}
}
}
if(fill_success) {
char temp[2];
utils::uint16_to_bytes(match, temp);
output.push_back(temp[0]);
output.push_back(temp[1]);
return ChunkCompressionMode::CHUNK_COMPRESSION_MODE_FILL;
}
// Try CHUNK_COMPRESSION_MODE_HUFFMAN
uint16_t current_count = 0;
uint16_t current_id = 0;
for(unsigned i = 0; i<sizeof(m_chunk_data)/2; i++) {
if(m_chunk_data[i]!=current_id || i==sizeof(m_chunk_data)/2-1) {
if(current_count>0) {
huffman_encode(current_count, output);
huffman_encode(current_id, output);
current_count = 0;
}
current_id = m_chunk_data[i];
}
current_count++;
}
if(output.size()<sizeof(m_chunk_data))
return ChunkCompressionMode::CHUNK_COMPRESSION_MODE_HUFFMAN;
// If none succeeded use CHUNK_COMPRESSION_MODE_NONE
if(utils::is_little_endian())
output.assign(reinterpret_cast<uint8_t*>(m_chunk_data), reinterpret_cast<uint8_t*>(m_chunk_data)+sizeof(m_chunk_data));
else {
output.resize(sizeof(m_chunk_data));
for(uint16_t i = 0; i<sizeof(m_chunk_data)/2; i++)
utils::uint16_to_bytes(m_chunk_data[i], reinterpret_cast<char*>(&output[i*2]));
}
return ChunkCompressionMode::CHUNK_COMPRESSION_MODE_NONE;
}
void Chunk::load_from_compressed_data(const char* data, uint16_t data_size, uint8_t compression_mode) {
switch(compression_mode) {
default:
LOG_ERROR("Unknown chunk compression mode, assuming none");
case ChunkCompressionMode::CHUNK_COMPRESSION_MODE_NONE:
for(uint16_t i = 0; i<data_size/2 && i<sizeof(m_chunk_data); i++)
m_chunk_data[i] = utils::bytes_to_uint16(&data[i*2]);
break;
case ChunkCompressionMode::CHUNK_COMPRESSION_MODE_HUFFMAN: {
uint16_t index = 0;
uint16_t node_index = 0;
while(index<data_size) {
// Get 7 first bits of first count byte: 0xxxxxxx
uint16_t count = data[index]&0x7F;
unsigned char offset = 0;
// Loop until last byte x0000000 is 1
while(data[index++]&0x80 && index<data_size)
count+=(++offset)*(data[index]&0x7F);
// Same as above
uint16_t id = data[index]&0x7F;
offset = 1;
while(data[index++]&0x80 && index<data_size)
id+=(offset++)*(data[index]&0x7F);
for(uint16_t i = 0; i<count; i++)
m_chunk_data[node_index++] = id;
}
break;
}
case ChunkCompressionMode::CHUNK_COMPRESSION_MODE_FILL: {
const uint16_t id = utils::bytes_to_uint16(data);
for(unsigned i = 0; i<sizeof(m_chunk_data)/2; i++)
m_chunk_data[i] = id;
break;
}
}
}
void Chunk::add_node(uint16_t node_to_add, const glm::vec3ub& pos) {
m_chunk_data[pos.z*CHUNK_SIZE*CHUNK_SIZE+pos.y*CHUNK_SIZE+pos.x] = node_to_add;
}
bool Chunk::is_air(const glm::vec3ub& pos) {
return get_node(pos) == 0;
}
uint16_t Chunk::get_node(const glm::vec3ub& pos) {
return m_chunk_data[pos.z*CHUNK_SIZE*CHUNK_SIZE+pos.y*CHUNK_SIZE+pos.x];
}
bool Chunk::is_neighbour_air(const glm::vec3ub& pos) {
if (pos.x+1 == CHUNK_SIZE) {
return true;
}
if (pos.x-1 == CHUNK_SIZE) {
return true;
}
if (pos.y+1 == CHUNK_SIZE) {
return true;
}
if (pos.y-1 == CHUNK_SIZE) {
return true;
}
if (pos.z+1 == CHUNK_SIZE) {
return true;
}
if (pos.z-1 == CHUNK_SIZE) {
return true;
}
if (pos.x+1 == 0) {
return true;
}
if (pos.x-1 == 0) {
return true;
}
if (pos.y+1 == 0) {
return true;
}
if (pos.y-1 == 0) {
return true;
}
if (pos.z+1 == 0) {
return true;
}
if (pos.z-1 == 0) {
return true;
}
return false;
}
}

View File

@ -24,25 +24,33 @@ SOFTWARE.
#ifndef POLYGUN_WORLD_CHUNK_HPP
#define POLYGUN_WORLD_CHUNK_HPP
#include <glm/glm.hpp>
#include <vector>
#include "common/glm_ext.hpp"
namespace polygun::world {
class Chunk {
enum ChunkCompressionMode {
CHUNK_COMPRESSION_MODE_NONE,
CHUNK_COMPRESSION_MODE_HUFFMAN,
CHUNK_COMPRESSION_MODE_FILL
};
class Chunk final {
public:
Chunk();
~Chunk() = default;
void add_node(int node_to_add, glm::vec3 pos);
bool is_air(glm::vec3 pos);
int get_node(glm::vec3 pos);
bool is_neighbour_air(glm::vec3 pos);
uint8_t to_compressed_data(std::vector<uint8_t>& output);
void load_from_compressed_data(const char* data, uint16_t data_size, uint8_t compression_mode);
void add_node(uint16_t node_to_add, const glm::vec3ub& pos);
bool is_air(const glm::vec3ub& pos);
uint16_t get_node(const glm::vec3ub& pos);
bool is_neighbour_air(const glm::vec3ub& pos);
static const int CHUNK_SIZE = 32;
glm::vec3 chunk_pos;
private:
int chunk_data[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
uint16_t m_chunk_data[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE];
};
}

View File

@ -24,7 +24,7 @@ SOFTWARE.
*/
#include "greedy_meshing.hpp"
#include "game/world/chunk.hpp"
#include "common/world/chunk.hpp"
namespace polygun::engine {
@ -146,7 +146,7 @@ namespace polygun::engine {
}
m_last_material = chunk_copy.get_node(glm::vec3(m_x, m_y, m_z));
}
}
}
@ -206,7 +206,7 @@ namespace polygun::engine {
{1, 5, 7, 1, 7, 3}, // right
{2, 7, 6, 2, 3, 7}, // front
};
static unsigned int slice_axises_indices[3][6] {
{0, 3, 2, 5, 1, 4}, // bottom/top face check
{1, 4, 2, 5, 0, 3}, // left/right face check
@ -294,9 +294,9 @@ namespace polygun::engine {
}
}
}
return {m_vertices, m_indices};
}
}

View File

@ -33,7 +33,7 @@ SOFTWARE.
#include <config.hpp>
#include "game/engine/player_camera.hpp"
#include "game/world/chunk.hpp"
#include "common/world/chunk.hpp"
#include "game/engine/greedy_meshing.hpp"
#include "game/engine/resource_manager.hpp"
#include "game/engine/content_registry.hpp"

View File

@ -1,93 +0,0 @@
/*
PolyGun
Copyright (c) 2023 kacperks https://kacperks.cubesoftware.xyz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "chunk.hpp"
#include "../engine/greedy_meshing.hpp"
namespace polygun::world {
Chunk::Chunk() {
for (int z = 0; z < CHUNK_SIZE; z++) {
for (int y = 0; y < CHUNK_SIZE; y++) {
for (int x = 0; x < CHUNK_SIZE; x++) {
chunk_data[z][y][x] = 0;
}
}
}
}
void Chunk::add_node(int node_to_add, glm::vec3 pos) {
chunk_data[(int)pos.z][(int)pos.y][(int)pos.x] = node_to_add;
}
bool Chunk::is_air(glm::vec3 pos) {
return chunk_data[(int)pos.z][(int)pos.y][(int)pos.x] == 0;
}
int Chunk::get_node(glm::vec3 pos) {
return chunk_data[(int)pos.z][(int)pos.y][(int)pos.x];
}
bool Chunk::is_neighbour_air(glm::vec3 pos) {
if (pos.x+1 == CHUNK_SIZE) {
return true;
}
if (pos.x-1 == CHUNK_SIZE) {
return true;
}
if (pos.y+1 == CHUNK_SIZE) {
return true;
}
if (pos.y-1 == CHUNK_SIZE) {
return true;
}
if (pos.z+1 == CHUNK_SIZE) {
return true;
}
if (pos.z-1 == CHUNK_SIZE) {
return true;
}
if (pos.x+1 == 0) {
return true;
}
if (pos.x-1 == 0) {
return true;
}
if (pos.y+1 == 0) {
return true;
}
if (pos.y-1 == 0) {
return true;
}
if (pos.z+1 == 0) {
return true;
}
if (pos.z-1 == 0) {
return true;
}
return false;
}
}