├── textures ├── font.png ├── sign.png ├── sky.png ├── damage.png ├── player.png ├── inventory.png └── health_bar.png ├── snap └── gui │ ├── konstructs-client.png │ └── konstructs-client.desktop ├── package ├── konstructs-client.desktop ├── README.md ├── konstructs-snap-wrapper └── Makefile ├── lib ├── include │ ├── compress.h │ ├── item.h │ ├── crosshair_shader.h │ ├── textures.h │ ├── selection_shader.h │ ├── util.h │ ├── sky_shader.h │ ├── world.h │ ├── gl_includes.h │ ├── cube.h │ ├── player_shader.h │ ├── player.h │ ├── hud.h │ ├── settings.h │ ├── matrix.h │ ├── block.h │ ├── chunk_shader.h │ ├── chunk_factory.h │ ├── hud_shader.h │ ├── chunk.h │ ├── client.h │ └── shader.h ├── src │ ├── compress.cpp │ ├── crosshair_shader.cpp │ ├── world.cpp │ ├── selection_shader.cpp │ ├── sky_shader.cpp │ ├── util.cpp │ ├── hud.cpp │ ├── player_shader.cpp │ ├── block.cpp │ ├── textures.cpp │ ├── settings.cpp │ ├── chunk_shader.cpp │ ├── matrix.cpp │ ├── chunk.cpp │ ├── shader.cpp │ ├── player.cpp │ ├── hud_shader.cpp │ └── client.cpp └── CMakeLists.txt ├── .gitignore ├── Toolchain-mingw32.cmake ├── .gitmodules ├── snapcraft.yaml ├── models └── player.obj ├── shaders ├── chunk.frag └── chunk.vert ├── LICENSE ├── BUILD.md ├── README.md ├── .travis.yml └── CMakeLists.txt /textures/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/font.png -------------------------------------------------------------------------------- /textures/sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/sign.png -------------------------------------------------------------------------------- /textures/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/sky.png -------------------------------------------------------------------------------- /textures/damage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/damage.png -------------------------------------------------------------------------------- /textures/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/player.png -------------------------------------------------------------------------------- /textures/inventory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/inventory.png -------------------------------------------------------------------------------- /textures/health_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/textures/health_bar.png -------------------------------------------------------------------------------- /snap/gui/konstructs-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konstructs/client/HEAD/snap/gui/konstructs-client.png -------------------------------------------------------------------------------- /package/konstructs-client.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Konstructs 3 | Exec=/usr/local/bin/konstructs-client 4 | Type=Application 5 | Categories=Game; 6 | -------------------------------------------------------------------------------- /lib/include/compress.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMPRESS_H__ 2 | #define __COMPRESS_H__ 3 | 4 | int inflate_data(char *in, int in_size, char *out, int out_size); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /lib/include/item.h: -------------------------------------------------------------------------------- 1 | #ifndef _ITEM_H_ 2 | #define _ITEM_H_ 3 | 4 | #include 5 | 6 | namespace konstructs { 7 | struct ItemStack { 8 | uint32_t amount; 9 | uint16_t type; 10 | uint16_t health; 11 | }; 12 | }; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /package/build/ 3 | /package/build-windows/ 4 | /parts/ 5 | /stage/ 6 | /prime/ 7 | /docs/html 8 | /docs/latex 9 | /docs/*.db 10 | *.o 11 | *.exe 12 | *.dll 13 | *.bat 14 | *.opensdf 15 | *.sdf 16 | *.sln 17 | *.vcxproj 18 | *.filters 19 | *.cproject 20 | *.project 21 | *.snap 22 | -------------------------------------------------------------------------------- /snap/gui/konstructs-client.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Name=Konstructs 4 | GenericName=Konstructs Client 5 | Comment=Konstructs Game Client 6 | Exec=konstructs-client 7 | Icon=${SNAP}/meta/gui/konstructs-client.png 8 | Terminal=false 9 | Type=Application 10 | StartupNotify=false 11 | Categories=Application;Game 12 | -------------------------------------------------------------------------------- /Toolchain-mingw32.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 4 | set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 5 | set(CMAKE_RC_COMPILER i686-w64-mingw32-windres) 6 | 7 | set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32/ /tmp/build-deps/zlib) 8 | 9 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 10 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 11 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 12 | -------------------------------------------------------------------------------- /lib/include/crosshair_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __CROSSHAIR_SHADER_H__ 2 | #define __CROSSHAIR_SHADER_H__ 3 | #include "shader.h" 4 | 5 | namespace konstructs { 6 | 7 | class CrosshairShader : private ShaderProgram { 8 | public: 9 | CrosshairShader(); 10 | void render(const int width, const int height); 11 | private: 12 | const GLuint projection; 13 | const GLuint position; 14 | const GLuint color; 15 | }; 16 | }; 17 | #endif 18 | -------------------------------------------------------------------------------- /lib/include/textures.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEXTURES_H__ 2 | #define __TEXTURES_H__ 3 | 4 | #include "tiny_obj_loader.h" 5 | 6 | namespace konstructs { 7 | #define BLOCK_TEXTURES 5 8 | #define SKY_TEXTURE 2 9 | #define FONT_TEXTURE 3 10 | #define INVENTORY_TEXTURE 4 11 | #define PLAYER_TEXTURE 6 12 | #define DAMAGE_TEXTURE 7 13 | #define HEALTH_BAR_TEXTURE 8 14 | void load_textures(); 15 | tinyobj::shape_t load_player(); 16 | std::string load_chunk_vertex_shader(); 17 | std::string load_chunk_fragment_shader(); 18 | }; 19 | #endif 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/nanogui"] 2 | path = dependencies/nanogui 3 | url = https://github.com/konstructs/nanogui.git 4 | [submodule "dependencies/lodepng"] 5 | path = dependencies/lodepng 6 | url = https://github.com/lvandeve/lodepng.git 7 | [submodule "dependencies/optional-lite"] 8 | path = dependencies/optional-lite 9 | url = https://github.com/martinmoene/optional-lite.git 10 | [submodule "dependencies/tinyobjloader"] 11 | path = dependencies/tinyobjloader 12 | url = https://github.com/syoyo/tinyobjloader.git 13 | [submodule "dependencies/simpleini"] 14 | path = dependencies/simpleini 15 | url = https://github.com/brofield/simpleini.git 16 | -------------------------------------------------------------------------------- /lib/src/compress.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "compress.h" 4 | 5 | int inflate_data(char *in, int in_size, char *out, int out_size) { 6 | z_stream strm; 7 | 8 | strm.zalloc = Z_NULL; 9 | strm.zfree = Z_NULL; 10 | strm.opaque = Z_NULL; 11 | 12 | strm.avail_in = in_size; 13 | strm.next_in = (Bytef*)in; 14 | strm.avail_out = out_size; 15 | strm.next_out = (Bytef*)out; 16 | 17 | inflateInit(&strm); 18 | int ret; 19 | if ((ret = inflate(&strm, Z_FINISH)) != Z_STREAM_END) { 20 | printf("inflate: return code %d\n", ret); 21 | } 22 | inflateEnd(&strm); 23 | 24 | return strm.total_out; 25 | } 26 | -------------------------------------------------------------------------------- /lib/include/selection_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __SELECTION_SHADER_H__ 2 | #define __SELECTION_SHADER_H__ 3 | #include 4 | #include "shader.h" 5 | #include "player.h" 6 | #include "matrix.h" 7 | 8 | namespace konstructs { 9 | 10 | class SelectionShader : public ShaderProgram { 11 | public: 12 | SelectionShader(const float _fov, 13 | const float _near_distance, const float scale); 14 | void render(const Player &p, const int width, const int height, 15 | const Vector3i &selected, const float view_distance); 16 | const GLuint position_attr; 17 | const GLuint matrix; 18 | const GLuint translation; 19 | const float near_distance; 20 | private: 21 | EigenModel model; 22 | const float fov; 23 | }; 24 | 25 | bool chunk_visible(const float planes[6][4], const Vector3i &position); 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: konstructs-client 4 | version: git 5 | summary: A voxel based game client 6 | description: An open source Infiniminer/Minecraft inspired multiplayer game. 7 | confinement: strict 8 | grade: stable 9 | 10 | apps: 11 | konstructs-client: 12 | command: desktop-launch $SNAP/konstructs-snap-wrapper 13 | plugs: 14 | - x11 15 | - network 16 | - opengl 17 | - unity7 18 | 19 | parts: 20 | launcher: 21 | plugin: dump 22 | source: package 23 | snap: ["konstructs-snap-wrapper"] 24 | gui: 25 | plugin: nil 26 | after: [desktop-gtk3] 27 | client: 28 | plugin: cmake 29 | source: . 30 | configflags: [-DCMAKE_INSTALL_PREFIX=/] 31 | build-packages: 32 | - build-essential 33 | - xorg-dev 34 | - cmake 35 | - libx11-dev 36 | - zlib1g-dev 37 | - libgl1-mesa-dev 38 | stage-packages: 39 | - libgl1-mesa-dri 40 | -------------------------------------------------------------------------------- /models/player.obj: -------------------------------------------------------------------------------- 1 | o Cube 2 | v 0.500000 -0.500000 -0.500000 3 | v 0.500000 -0.500000 0.500000 4 | v -0.500000 -0.500000 0.500000 5 | v -0.500000 -0.500000 -0.500000 6 | v 0.500000 0.500000 -0.500000 7 | v 0.500000 0.500000 0.500000 8 | v -0.500000 0.500000 0.500000 9 | v -0.500000 0.500000 -0.500000 10 | 11 | vt 0.000000 0.000000 12 | vt 0.000000 1.000000 13 | vt 0.166667 0.000000 14 | vt 0.166667 1.000000 15 | vt 0.333333 0.000000 16 | vt 0.333333 1.000000 17 | vt 0.500000 0.000000 18 | vt 0.500000 1.000000 19 | vt 0.666667 0.000000 20 | vt 0.666667 1.000000 21 | vt 0.833333 0.000000 22 | vt 0.833333 1.000000 23 | vt 1.000000 0.000000 24 | vt 1.000000 1.000000 25 | 26 | vn 0.000000 -1.000000 0.000000 27 | vn 0.000000 1.000000 0.000000 28 | vn 1.000000 0.000000 0.000000 29 | vn -0.000000 -0.000000 1.000000 30 | vn -1.000000 -0.000000 -0.000000 31 | vn 0.000000 0.000000 -1.000000 32 | s off 33 | f 1/13/1 2/14/1 3/12/1 4/11/1 34 | f 5/9/2 8/11/2 7/12/2 6/10/2 35 | f 1/7/3 5/8/3 6/6/3 2/5/3 36 | f 2/9/4 6/10/4 7/8/4 3/7/4 37 | f 3/3/5 7/4/5 8/2/5 4/1/5 38 | f 5/4/6 1/3/6 4/5/6 8/6/6 39 | -------------------------------------------------------------------------------- /shaders/chunk.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D sampler; 3 | uniform sampler2D sky_sampler; 4 | uniform sampler2D damage_sampler; 5 | uniform float timer; 6 | uniform vec3 ambient_color; 7 | uniform vec3 ambient_light; 8 | 9 | in vec2 fragment_uv; 10 | in vec2 damage_uv; 11 | flat in float damage_factor; 12 | in float fragment_ao; 13 | in float ambient; 14 | in float fog_factor; 15 | in float fog_height; 16 | in float diffuse; 17 | in vec3 light; 18 | out vec4 frag_color; 19 | 20 | const vec3 damage_color = vec3(0,0,0); 21 | 22 | void main() { 23 | vec3 color = vec3(texture(sampler, fragment_uv)); 24 | if (color == vec3(1.0, 0.0, 1.0)) { 25 | discard; 26 | } 27 | float damage = texture(damage_sampler, damage_uv).y; 28 | color = mix(color, damage_color, damage * damage_factor); 29 | vec3 light_sum = (ambient_light + ambient_color * diffuse) * ambient + light; 30 | color = clamp(color * light_sum * fragment_ao, vec3(0.0), vec3(1.0)); 31 | vec3 sky_color = vec3(texture(sky_sampler, vec2(timer, fog_height))); 32 | color = mix(color, sky_color, fog_factor); 33 | frag_color = vec4(color, 1.0); 34 | } 35 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # Build it like we do at Travis 2 | 3 | This file has a `Makefile` that we use to build the project. It uses normal make dependencies to resolve the needed steps. This requires Docker and is primary used on Travis, if you like to build a local build see BUILD.md at the repository root. 4 | 5 | ## Build a Linux build 6 | 7 | This step uses Docker to start a Ubuntu 15.10 container to build the project. 8 | 9 | ``` 10 | make linux 11 | ``` 12 | 13 | ## Build a Windows build 14 | This produces a `.zip` file containing everything you need to run the game under Windows. The files are produces from MinGW and are cross compiled from a Fedora 23 container. 15 | 16 | ``` 17 | make windows 18 | ``` 19 | 20 | ## Build a OSX build 21 | This will plain and simple build the project on OS X, no install packages are generated at the moment. 22 | 23 | ``` 24 | make osx 25 | ``` 26 | 27 | ## Build a zip archive 28 | This build produces a `.zip` with everything you need to run the game under Windows. This build step is primary made for our Travis builds that we [distribute at Bintray](https://bintray.com/konstructs/windows/client/view). 29 | 30 | ``` 31 | make zip 32 | ``` 33 | -------------------------------------------------------------------------------- /lib/include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef _util_h_ 2 | #define _util_h_ 3 | #include 4 | 5 | #define PI 3.14159265359 6 | #define DEGREES(radians) ((radians) * 180 / PI) 7 | #define RADIANS(degrees) ((degrees) * PI / 180) 8 | #define ABS(x) ((x) < 0 ? (-(x)) : (x)) 9 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 10 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 11 | #define SIGN(x) (((x) > 0) - ((x) < 0)) 12 | 13 | #if DEBUG 14 | #define LOG(...) printf(__VA_ARGS__) 15 | #else 16 | #define LOG(...) 17 | #endif 18 | 19 | typedef struct { 20 | unsigned int old_fps; 21 | unsigned int fps; 22 | unsigned int frames; 23 | double since; 24 | } FPS; 25 | 26 | void update_fps(FPS *fps); 27 | 28 | GLuint gen_buffer(GLsizei size, GLfloat *data); 29 | GLuint gen_faces(int components, int faces, GLfloat *data); 30 | void load_png_texture(const char *file_name); 31 | void load_png_texture_from_buffer(const char *in, int size); 32 | int file_exist(const char *filename); 33 | 34 | namespace konstructs { 35 | template< typename T > 36 | struct array_deleter { 37 | void operator ()( T const * p) { 38 | delete[] p; 39 | } 40 | }; 41 | }; 42 | #endif 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014-2017 Petter Arvidsson and Stefan Berggren 2 | Copyright (C) 2013-2014 Michael Fogleman 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/include/sky_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __SKY_SHADER_H__ 2 | #define __SKY_SHADER_H__ 3 | 4 | #include "player.h" 5 | #include "shader.h" 6 | #include "matrix.h" 7 | 8 | namespace konstructs { 9 | 10 | class SkyModel : public BufferModel { 11 | public: 12 | SkyModel(GLuint _position_attr, GLuint _uv_attr); 13 | virtual void bind(); 14 | virtual int vertices(); 15 | private: 16 | const GLuint position_attr; 17 | const GLuint uv_attr; 18 | }; 19 | 20 | class SkyShader : public ShaderProgram { 21 | public: 22 | SkyShader(const float _fov, const GLuint _sky_texture, 23 | const float _near_distance); 24 | void render(const Player &p, const int width, const int height, 25 | const float current_timer, const float view_distance); 26 | const GLuint position_attr; 27 | const GLuint uv_attr; 28 | const GLuint matrix; 29 | const GLuint sampler; 30 | const GLuint timer; 31 | const GLuint sky_texture; 32 | private: 33 | const float fov; 34 | const float near_distance; 35 | SkyModel model; 36 | }; 37 | 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /lib/include/world.h: -------------------------------------------------------------------------------- 1 | #ifndef __WORLD_H__ 2 | #define __WORLD_H__ 3 | 4 | #include 5 | #include 6 | #include "matrix.h" 7 | #include "client.h" 8 | #include "chunk.h" 9 | 10 | namespace konstructs { 11 | using nonstd::optional; 12 | 13 | class World { 14 | public: 15 | int size() const; 16 | void delete_unused_chunks(const Vector3i player_chunk, const int radi); 17 | void insert(const ChunkData data); 18 | const optional get_block(const Vector3i &block_pos) const; 19 | const optional chunk_by_block(const Vector3f &block_pos) const; 20 | const optional chunk_by_block(const Vector3i &block_pos) const; 21 | const optional chunk(const Vector3i &chunk_pos) const; 22 | const std::vector atAndAround(const Vector3i &pos) const; 23 | std::unordered_map>::const_iterator find(const Vector3i &pos) const; 24 | std::unordered_map>::const_iterator end() const; 25 | private: 26 | std::unordered_map> chunks; 27 | }; 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /package/konstructs-snap-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Welcome to Konstructs Client" 4 | echo "----------------------------" 5 | echo 6 | echo "Snap (snapcraft) is still a new package format and there are still" 7 | echo "problems with OpenGL applications, like Konstructs." 8 | echo 9 | echo "The system will output some debug information to help us to track" 10 | echo "down possible problems, please report crashes to:" 11 | echo "https://github.com/konstructs/client/issues" 12 | echo "Check for simular existing issues before you create a new one." 13 | echo 14 | echo "Some of your enviroment:" 15 | echo $LD_LIBRARY_PATH 16 | echo $LIBGL_DRIVERS_PATH 17 | echo $SNAP_LIBRARY_PATH 18 | echo $SNAP_REVISION 19 | echo $SNAP_ARCH 20 | echo $DISPLAY 21 | echo 22 | echo "A little about your system:" 23 | echo $DESKTOP_SESSION 24 | uname -a 25 | echo 26 | echo "List with libGL.so are available:" 27 | ldconfig -p | grep libGL.so 28 | ls $SNAP_LIBRARY_PATH 29 | echo 30 | echo 31 | echo "I will start the client now!" 32 | echo "-------------------------------------------------------" 33 | 34 | export LIBGL_DEBUG=verbose 35 | export LD_LIBRARY_PATH=$SNAP_LIBRARY_PATH:$LD_LIBRARY_PATH 36 | $SNAP/konstructs $@ 37 | 38 | echo "-------------------------------------------------------" 39 | echo "Did the client crash? If yes please report the problem." 40 | -------------------------------------------------------------------------------- /lib/include/gl_includes.h: -------------------------------------------------------------------------------- 1 | #ifndef __GL_INCLUDES_H__ 2 | #define __GL_INCLUDES_H__ 3 | 4 | #if defined(__APPLE__) 5 | #define GLFW_INCLUDE_GLCOREARB 6 | #elif defined(WIN32) 7 | #define GLEW_STATIC 8 | #include 9 | #else 10 | #define GL_GLEXT_PROTOTYPES 11 | #endif 12 | 13 | #include 14 | namespace konstructs { 15 | template struct type_traits; 16 | template <> struct type_traits { 17 | enum { type = GL_UNSIGNED_INT, integral = 1 }; 18 | }; 19 | template <> struct type_traits { 20 | enum { type = GL_INT, integral = 1 }; 21 | }; 22 | template <> struct type_traits { 23 | enum { type = GL_UNSIGNED_SHORT, integral = 1 }; 24 | }; 25 | template <> struct type_traits { 26 | enum { type = GL_SHORT, integral = 1 }; 27 | }; 28 | template <> struct type_traits { 29 | enum { type = GL_UNSIGNED_BYTE, integral = 1 }; 30 | }; 31 | template <> struct type_traits { 32 | enum { type = GL_BYTE, integral = 1 }; 33 | }; 34 | template <> struct type_traits { 35 | enum { type = GL_DOUBLE, integral = 0 }; 36 | }; 37 | template <> struct type_traits { 38 | enum { type = GL_FLOAT, integral = 0 }; 39 | }; 40 | }; 41 | #endif 42 | -------------------------------------------------------------------------------- /lib/include/cube.h: -------------------------------------------------------------------------------- 1 | #ifndef _cube_h_ 2 | #define _cube_h_ 3 | 4 | #include 5 | 6 | #include "block.h" 7 | 8 | using namespace konstructs; 9 | 10 | void make_cube_faces( 11 | float *data, char ao[6][4], 12 | int left, int right, int top, int bottom, int front, int back, 13 | int wleft, int wright, int wtop, int wbottom, int wfront, int wback, 14 | float x, float y, float z, float n); 15 | 16 | void make_cube( 17 | float *data, char ao[6][4], 18 | int left, int right, int top, int bottom, int front, int back, 19 | float x, float y, float z, float n, int w, const int blocks[256][6]); 20 | 21 | void make_cube2( 22 | GLuint *data, char ao[6][4], uint8_t faces[6], RGBAmbient corner_data[8], 23 | int x, int y, int z, const BlockData block, int damage, const int blocks[256][6]); 24 | 25 | void make_rotated_cube(float *data, char ao[6][4], 26 | int left, int right, int top, int bottom, int front, int back, 27 | float x, float y, float z, float n, float rx, float ry, float rz, 28 | int w, const int blocks[256][6]); 29 | 30 | void make_plant( 31 | GLuint *data, char ao, 32 | int x, int y, int z, const BlockData block, const int blocks[256][6]); 33 | 34 | void make_sphere(float *data, float r, int detail); 35 | 36 | void make_character(float *data, float x, float y, float n, float m, char c, float z); 37 | #endif 38 | -------------------------------------------------------------------------------- /lib/include/player_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __PLAYER_SHADER_H__ 2 | #define __PLAYER_SHADER_H__ 3 | 4 | #include 5 | #include 6 | #include "shader.h" 7 | #include "player.h" 8 | #include "player.h" 9 | #include "chunk_factory.h" 10 | #include "matrix.h" 11 | 12 | namespace konstructs { 13 | class PlayerShader : public ShaderProgram { 14 | public: 15 | PlayerShader(const float fov, const GLuint player_texture, 16 | const GLuint sky_texture, const float near_distance, 17 | tinyobj::shape_t &shape); 18 | void add(const Player player); 19 | void remove(const int pid); 20 | int render(const Player &p, const int width, const int height, 21 | const float current_daylight, const float current_timer, const float view_distance); 22 | const GLuint position_attr; 23 | const GLuint normal_attr; 24 | const GLuint uv_attr; 25 | const GLuint matrix; 26 | const GLuint translation; 27 | const GLuint sampler; 28 | const GLuint sky_sampler; 29 | const GLuint fog_distance; 30 | const GLuint timer; 31 | const GLuint daylight; 32 | const GLuint camera; 33 | const GLuint player_texture; 34 | const GLuint sky_texture; 35 | const float near_distance; 36 | private: 37 | std::unordered_map players; 38 | ShapeModel model; 39 | const float fov; 40 | }; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8 FATAL_ERROR) 2 | project(konstructs-lib) 3 | 4 | #----------------------------------------------------------------------- 5 | # Check for C++11 enabled compiler 6 | #----------------------------------------------------------------------- 7 | 8 | include(CheckCXXCompilerFlag) 9 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) 10 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) 11 | if(COMPILER_SUPPORTS_CXX11) 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 13 | elseif(COMPILER_SUPPORTS_CXX0X) 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 15 | else() 16 | message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") 17 | endif() 18 | 19 | if(MINGW) 20 | add_definitions(-DWIN32) 21 | add_definitions(-DEIGEN_DONT_ALIGN) 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") 23 | endif() 24 | 25 | set(CMAKE_BUILD_TYPE Debug) 26 | 27 | find_package(ZLIB REQUIRED) 28 | 29 | include_directories( 30 | ${ZLIB_INCLUDE_DIRS} 31 | ../dependencies/nanogui/ext/glfw/include 32 | ../dependencies/nanogui/ext/glew/include 33 | ../dependencies/nanogui/ext/eigen 34 | ../dependencies/lodepng 35 | ../dependencies/optional-lite 36 | ../dependencies/tinyobjloader 37 | ../dependencies/simpleini 38 | include 39 | ) 40 | 41 | FILE( 42 | GLOB SOURCE_FILES 43 | src/*.cpp 44 | ../dependencies/lodepng/lodepng.cpp 45 | ../dependencies/tinyobjloader/tiny_obj_loader.cc) 46 | 47 | add_library(konstructs-lib ${SOURCE_FILES}) 48 | -------------------------------------------------------------------------------- /lib/src/crosshair_shader.cpp: -------------------------------------------------------------------------------- 1 | #include "matrix.h" 2 | #include "crosshair_shader.h" 3 | 4 | namespace konstructs { 5 | using matrix::projection_2d; 6 | CrosshairShader::CrosshairShader() : 7 | ShaderProgram( 8 | "crosshair", 9 | "#version 330\n" 10 | "uniform mat4 projection;\n" 11 | "in vec4 position;\n" 12 | "void main() {\n" 13 | " gl_Position = projection * position;\n" 14 | "}\n", 15 | "#version 330\n" 16 | "uniform vec4 color;\n" 17 | "out vec4 fragColor;\n" 18 | "void main() {\n" 19 | " fragColor = vec4(color);\n" 20 | "}\n", 21 | GL_LINES), 22 | projection(uniformId("projection")), 23 | position(attributeId("position")), 24 | color(uniformId("color")) { } 25 | 26 | void CrosshairShader::render(const int width, const int height) { 27 | bind([&](Context c) { 28 | float p = 0.03; 29 | MatrixXf segments(2, 4); 30 | segments.col(0) << 0, -p; 31 | segments.col(1) << 0, p; 32 | segments.col(2) << -p, 0; 33 | segments.col(3) << p, 0; 34 | EigenModel data(position, segments); 35 | c.logic_op(GL_INVERT); 36 | c.enable(GL_COLOR_LOGIC_OP); 37 | c.set(projection, projection_2d(width, height)); 38 | c.set(color, Vector4f( 0.0, 0.0, 0.0, 1.0)); 39 | c.draw(data); 40 | c.disable(GL_COLOR_LOGIC_OP); 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /lib/include/player.h: -------------------------------------------------------------------------------- 1 | #ifndef __PLAYER_H__ 2 | #define __PLAYER_H__ 3 | #include 4 | #include 5 | #include "optional.hpp" 6 | #include "world.h" 7 | #include "block.h" 8 | 9 | namespace konstructs { 10 | 11 | using namespace Eigen; 12 | using nonstd::optional; 13 | using std::pair; 14 | 15 | class Player { 16 | public: 17 | Player(const int id, const Vector3f position, 18 | const float rx, const float ry); 19 | Matrix4f direction() const; 20 | Matrix4f translation() const; 21 | Matrix4f view() const; 22 | Vector3f camera() const; 23 | Vector3f camera_direction() const; 24 | Vector3i feet() const; 25 | bool can_place(Vector3i block, const World &world, const BlockTypeInfo &blocks); 26 | Vector3f update_position(int sz, int sx, float dt, 27 | const World &world, const BlockTypeInfo &blocks, 28 | const float near_distance, const bool jump, const bool sneaking); 29 | optional> looking_at(const World &world, 30 | const BlockTypeInfo &blocks) const; 31 | void rotate_x(float speed); 32 | void rotate_y(float speed); 33 | int id; 34 | void fly(); 35 | float rx(); 36 | float ry(); 37 | Vector3f position; 38 | private: 39 | int collide(const World &world, const BlockTypeInfo &blocks, 40 | const float near_distance, const bool sneaking); 41 | float mrx; 42 | float mry; 43 | bool flying; 44 | float dy; 45 | }; 46 | 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /lib/include/hud.h: -------------------------------------------------------------------------------- 1 | #ifndef __HUD_H__ 2 | #define __HUD_H__ 3 | 4 | #include 5 | #include 6 | #include "optional.hpp" 7 | #include "matrix.h" 8 | #include "item.h" 9 | 10 | namespace konstructs { 11 | using nonstd::optional; 12 | class Hud { 13 | public: 14 | Hud(const int columns, const int rows, const int belt_size); 15 | optional held() const; 16 | void set_held(ItemStack stack); 17 | void reset_held(); 18 | bool active(const Vector2i pos) const; 19 | void set_background(const Vector2i pos, const int t); 20 | void reset_background(const Vector2i pos); 21 | void set_stack(const Vector2i pos, const ItemStack stack); 22 | void reset_stack(const Vector2i pos); 23 | void set_belt(const int pos, ItemStack stack); 24 | void reset_belt(const int pos); 25 | optional selected() const; 26 | int scroll(int direction); 27 | void set_selected(int s); 28 | int get_selection() const; 29 | void set_interactive(bool i); 30 | bool get_interactive() const; 31 | std::unordered_map> backgrounds() const; 32 | std::unordered_map> stacks() const; 33 | const int rows; 34 | const int columns; 35 | private: 36 | std::unordered_map> bg; 37 | std::unordered_map> item_stacks; 38 | optional held_stack; 39 | std::vector> belt; 40 | int belt_size; 41 | int selection; 42 | bool interactive; 43 | }; 44 | 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # Install and Dependencies 2 | 3 | Clone the git repository and download/update all submodules. 4 | 5 | git clone https://github.com/konstructs/client.git 6 | git submodule update --init --recursive 7 | 8 | ## Ubuntu Linux 9 | 10 | Install the needed build tools and dependencies 11 | 12 | sudo apt-get install build-essential cmake libglew-dev xorg-dev 13 | sudo apt-get build-dep glfw 14 | 15 | ## macOS 16 | 17 | Download and install [CMake](http://www.cmake.org/cmake/resources/software.html) 18 | if you don\'t already have it. You may use [Homebrew](http://brew.sh) to simplify 19 | the installation: 20 | 21 | brew install cmake 22 | 23 | (You also need to figure out how to enable the C++ compiler, and other dependencies, 24 | if your are a macOS user please make a PR or open a issue and provide instructions) 25 | 26 | ## Windows 27 | 28 | The official Windows build are cross compiled from Docker container, to do that your self 29 | you need Docker installed. 30 | 31 | make -C package windows 32 | 33 | Download and install [CMake](http://www.cmake.org/cmake/resources/software.html) 34 | and [MinGW](http://www.mingw.org/). Add `C:\MinGW\bin` to your `PATH`. Install 35 | the needed dependences. 36 | 37 | Use the following commands in place of the ones described in the next section. 38 | 39 | cmake -G "MinGW Makefiles" 40 | mingw32-make 41 | 42 | The game builds under Virtual Studio on Windows, but that is rarely tested. 43 | 44 | (If your are a Windows user please make a PR or open a issue and provide instructions) 45 | 46 | ## Compile and Run 47 | 48 | Once you have the dependencies, run the following commands in your 49 | terminal. 50 | 51 | mkdir build && cd build 52 | cmake .. && make 53 | ./konstructs -h 54 | -------------------------------------------------------------------------------- /lib/include/settings.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef KONSTRUCTS_SETTINGS_H 4 | #define KONSTRUCTS_SETTINGS_H 5 | 6 | namespace konstructs { 7 | struct Settings { 8 | struct Server { 9 | std::string address; 10 | unsigned int port; 11 | std::string username; 12 | std::string password; 13 | bool password_save; 14 | }; 15 | 16 | struct Client { 17 | bool debug; 18 | int field_of_view; 19 | unsigned int window_width; 20 | unsigned int window_height; 21 | unsigned int radius_start; 22 | unsigned int radius_max; 23 | float frames_per_second; 24 | }; 25 | 26 | struct Keys { 27 | int debug; 28 | int tertiary; 29 | int up; 30 | int down; 31 | int left; 32 | int right; 33 | int jump; 34 | int fly; 35 | int sneak; 36 | }; 37 | 38 | Server server; 39 | Client client; 40 | Keys keys; 41 | }; 42 | 43 | /** 44 | * Find the path to the configuration file 45 | * @param r The path to the config file is returned 46 | * @param size Maximum size allowed 47 | */ 48 | void config_path(char* r, size_t size); 49 | 50 | /** 51 | * Uses simpleini to populate the Settings struct from a ini file 52 | * @param A reference to the settings object 53 | */ 54 | void load_settings(Settings &settings); 55 | 56 | /** 57 | * Uses simpleini to save the Settings struct to a ini file 58 | * @param A reference to the settings object 59 | */ 60 | void save_settings(Settings &settings); 61 | } 62 | 63 | #endif //KONSTRUCTS_SETTINGS_H -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Konstructs 2 | 3 | [![Join the chat at https://gitter.im/konstructs/client](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/konstructs/client?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/konstructs/client.svg?branch=master)](https://travis-ci.org/konstructs/client) [![Snap Status](https://build.snapcraft.io/badge/konstructs/client.svg)](https://build.snapcraft.io/user/konstructs/client) 4 | 5 | This is a game inspired by block building and crafting games like Infiniminer, Minecraft and Minetest. Konstructs is a multiplayer open source game, focused on massive gameplay. 6 | 7 | To play the game, you can connect to the official server at `play.konstructs.org`, or start your own server, even on the same computer for a single player like experience. 8 | 9 | For more information about the game, how to play it and download links see our web page at http://www.konstructs.org 10 | 11 | ### Purpose 12 | 13 | The project aims at building a client that can render and interact with a block-based world. The goal is to keep the client as simple as possible leaving the game logic to the server. 14 | 15 | ### History 16 | 17 | This client is originally based on [Craft](https://github.com/fogleman/Craft) written by Michael Fogleman. The original project was primarily focused on single player. We forked Craft to focus on simplifying the client for a multiplayer-only game. You can still start a local server if you like to play single player. 18 | 19 | ### Compile and/or Install 20 | 21 | You have binary builds over at our website at http://www.konstructs.org/download/ and see [BUILD.md](BUILD.md) for build instructions. 22 | 23 | ### License 24 | 25 | All code and assets are licensed under MIT, see [LICENSE](https://github.com/konstructs/client/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sudo: required 4 | dist: trusty 5 | language: cpp 6 | services: 7 | - docker 8 | 9 | git: 10 | submodules: false 11 | 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - os: linux 16 | env: MAKE_COMMAND="docs upload-docs" 17 | include: 18 | - os: osx 19 | osx_image: xcode7.3 20 | compiler: clang 21 | env: MAKE_COMMAND="osx" 22 | - os: linux 23 | compiler: gcc 24 | env: 25 | - MAKE_COMMAND="test linux" 26 | - UPLOAD_TO_GH=yes 27 | - os: linux 28 | env: 29 | - MAKE_COMMAND="test windows zip" 30 | - UPLOAD_TO_GH=yes 31 | - os: linux 32 | env: MAKE_COMMAND="docs upload-docs" 33 | 34 | before_install: 35 | - git submodule update --init --recursive 36 | - | 37 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 38 | sudo pip install awscli 39 | sudo apt-get install astyle 40 | fi 41 | 42 | notifications: 43 | webhooks: 44 | urls: 45 | - https://webhooks.gitter.im/e/e075a4f5a37b0c115fa8 46 | on_success: always # options: [always|never|change] default: always 47 | on_failure: always # options: [always|never|change] default: always 48 | on_start: always # options: [always|never|change] default: always 49 | 50 | before_script: 51 | - grep $(date +%Y) LICENSE 52 | 53 | script: 54 | - make $MAKE_COMMAND -C package 55 | 56 | before_deploy: 57 | - "mkdir -p upload" 58 | - "mv package/*.zip upload || true" 59 | - "rm -f upload/*source*" 60 | 61 | deploy: 62 | provider: releases 63 | api_key: 64 | secure: fJMRmWSoBrm2hy6FDwhp7flHJhcXPZWE7gPluYT/G4K4uGf/QaFS0yKiTEXYEPWzBPUdidZxOKX427/rlmXyrl7ZfrUfn5k/cctc1aYpV5khtVWFCxlTbltzPefq8B7DkSDlQSnh9qB8e6J4E7xQd8zak6spjRntFBXafdvHM5E= 65 | file: "upload/*" 66 | file_glob: true 67 | on: 68 | repo: konstructs/client 69 | tags: true 70 | condition: "$UPLOAD_TO_GH = yes" 71 | -------------------------------------------------------------------------------- /lib/include/matrix.h: -------------------------------------------------------------------------------- 1 | #ifndef _matrix_h_ 2 | #define _matrix_h_ 3 | #include 4 | 5 | namespace konstructs { 6 | using namespace Eigen; 7 | // Hash function for Eigen matrix and vector. 8 | // The code is from `hash_combine` function of the Boost library. See 9 | // http://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine . 10 | template 11 | struct matrix_hash : std::unary_function { 12 | std::size_t operator()(T const& matrix) const { 13 | // Note that it is oblivious to the storage order of Eigen matrix (column- or 14 | // row-major). It will give you the same hash value for two different matrices if they 15 | // are the transpose of each other in different storage order. 16 | size_t seed = 0; 17 | for (size_t i = 0; i < matrix.size(); ++i) { 18 | auto elem = *(matrix.data() + i); 19 | seed ^= std::hash()(elem) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 20 | } 21 | return seed; 22 | } 23 | }; 24 | namespace matrix { 25 | Matrix4f projection(int width, int height); 26 | Matrix4f projection_2d(const int width, const int height); 27 | Matrix4f projection_frustum(float left, float right, float bottom, 28 | float top, float znear, float zfar); 29 | Matrix4f projection_perspective(float fov, float aspect, 30 | float znear, float zfar); 31 | void ext_frustum_planes(float planes[6][4], int radius, const Matrix4f &m); 32 | }; 33 | }; 34 | 35 | void normalize(float *x, float *y, float *z); 36 | void mat_identity(float *matrix); 37 | void mat_translate(float *matrix, float dx, float dy, float dz); 38 | void mat_rotate(float *matrix, float x, float y, float z, float angle); 39 | void mat_vec_multiply(float *vector, float *a, float *b); 40 | void mat_multiply(float *matrix, float *a, float *b); 41 | void mat_apply(float *data, float *matrix, int count, int offset, int stride); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /lib/include/block.h: -------------------------------------------------------------------------------- 1 | #ifndef __BLOCK_H__ 2 | #define __BLOCK_H__ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #define VACUUM_TYPE 0 9 | #define SOLID_TYPE 65535 10 | #define BLOCK_TYPES 65536 11 | #define MAX_HEALTH 2047 12 | #define AMBIENT_LIGHT_DARK 0 13 | #define AMBIENT_LIGHT_FULL 0xF 14 | 15 | #define DIRECTION_UP 0 16 | #define DIRECTION_DOWN 1 17 | #define DIRECTION_RIGHT 2 18 | #define DIRECTION_LEFT 3 19 | #define DIRECTION_FORWARD 4 20 | #define DIRECTION_BACKWARD 5 21 | 22 | #define ROTATION_IDENTITY 0 23 | #define ROTATION_LEFT 1 24 | #define ROTATION_RIGHT 2 25 | #define ROTATION_HALF 3 26 | 27 | #define STATE_SOLID 0 28 | #define STATE_LIQUID 1 29 | #define STATE_GAS 2 30 | #define STATE_PLASMA 3 31 | 32 | namespace konstructs { 33 | 34 | extern const std::string direction_to_string[6]; 35 | 36 | extern const std::string rotation_to_string[4]; 37 | 38 | using namespace Eigen; 39 | 40 | struct BlockTypeInfo { 41 | int blocks[BLOCK_TYPES][6]; 42 | char is_plant[BLOCK_TYPES]; 43 | char is_obstacle[BLOCK_TYPES]; 44 | char is_transparent[BLOCK_TYPES]; 45 | char is_orientable[BLOCK_TYPES]; 46 | char state[BLOCK_TYPES]; 47 | }; 48 | 49 | struct BlockData { 50 | uint16_t type; 51 | uint16_t health; 52 | uint8_t direction; 53 | uint8_t rotation; 54 | uint8_t ambient; 55 | uint8_t r; 56 | uint8_t g; 57 | uint8_t b; 58 | uint8_t light; 59 | }; 60 | 61 | struct RGBAmbient { 62 | uint8_t r; 63 | uint8_t g; 64 | uint8_t b; 65 | uint8_t light; 66 | uint8_t ambient; 67 | }; 68 | 69 | class Block { 70 | public: 71 | Block(const Vector3i position, const BlockData data); 72 | Vector3i position; 73 | BlockData data; 74 | }; 75 | 76 | const uint8_t direction_from_vector(const Vector3i &unit_vector); 77 | const uint8_t direction_from_vector(const Vector3i &from, const Vector3i &to); 78 | const uint8_t rotation_from_vector(const uint8_t direction, const Vector3f &vector); 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /lib/include/chunk_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __CHUNK_SHADER_H__ 2 | #define __CHUNK_SHADER_H__ 3 | 4 | #include 5 | #include 6 | #include "shader.h" 7 | #include "player.h" 8 | #include "chunk.h" 9 | #include "chunk_factory.h" 10 | #include "matrix.h" 11 | 12 | namespace konstructs { 13 | using std::shared_ptr; 14 | 15 | class ChunkModel : public BufferModel { 16 | public: 17 | ChunkModel(const shared_ptr &data, 18 | GLuint data_attr); 19 | virtual void bind(); 20 | virtual int vertices(); 21 | const Vector3i position; 22 | const int faces; 23 | Matrix4f translation; 24 | private: 25 | const GLuint data_attr; 26 | }; 27 | 28 | class ChunkShader : public ShaderProgram { 29 | public: 30 | ChunkShader(const float fov, const GLuint block_texture, const GLuint damage_texture, 31 | const GLuint sky_texture, const float near_distance, const string &vert_str, 32 | const string &frag_str); 33 | int size() const; 34 | void add(const shared_ptr &data); 35 | int render(const Player &p, const int width, const int height, 36 | const float current_daylight, const float current_timer, 37 | const int radius, const float view_distance, const Vector3i &player_chunk); 38 | const GLuint data_attr; 39 | const GLuint matrix; 40 | const GLuint translation; 41 | const GLuint sampler; 42 | const GLuint sky_sampler; 43 | const GLuint damage_sampler; 44 | const GLuint fog_distance; 45 | const GLuint ambient_color; 46 | const GLuint ambient_light; 47 | const GLuint timer; 48 | const GLuint camera; 49 | const GLuint block_texture; 50 | const GLuint sky_texture; 51 | const GLuint damage_texture; 52 | const float near_distance; 53 | private: 54 | std::unordered_map> models; 55 | const float fov; 56 | }; 57 | 58 | bool chunk_visible(const float planes[6][4], const Vector3i &position); 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /lib/src/world.cpp: -------------------------------------------------------------------------------- 1 | #include "world.h" 2 | 3 | namespace konstructs { 4 | 5 | using nonstd::nullopt; 6 | 7 | int World::size() const { 8 | return chunks.size(); 9 | } 10 | 11 | void World::delete_unused_chunks(const Vector3i player_chunk, const int radi) { 12 | for ( auto it = chunks.begin(); it != chunks.end();) { 13 | if ((it->second.position - player_chunk).norm() > radi) { 14 | it = chunks.erase(it); 15 | } else { 16 | ++it; 17 | } 18 | } 19 | } 20 | 21 | void World::insert(ChunkData data) { 22 | /* Overwrite any existing chunk, we always want the latest data */ 23 | const Vector3i pos = data.position; 24 | chunks.erase(pos); 25 | chunks.insert({pos, data}); 26 | } 27 | 28 | const optional World::get_block(const Vector3i &block_pos) const { 29 | auto chunk = chunk_by_block(block_pos); 30 | if(chunk) { 31 | return (*chunk).get(block_pos); 32 | } else { 33 | return nullopt; 34 | } 35 | } 36 | 37 | const optional World::chunk_by_block(const Vector3f &block_pos) const { 38 | return chunk(chunked_vec(block_pos)); 39 | } 40 | 41 | const optional World::chunk_by_block(const Vector3i &block_pos) const { 42 | return chunk(chunked_vec_int(block_pos)); 43 | } 44 | 45 | const optional World::chunk(const Vector3i &chunk_pos) const { 46 | try { 47 | return chunks.at(chunk_pos); 48 | } catch(std::out_of_range e) { 49 | return nullopt; 50 | } 51 | } 52 | 53 | const std::vector World::atAndAround(const Vector3i &pos) const { 54 | std::vector result; 55 | for(int i = -1; i <= 1; i++) { 56 | for(int j = -1; j <= 1; j++) { 57 | for(int k = -1; k <= 1; k++) { 58 | try { 59 | result.push_back(chunks.at(pos + Vector3i(i, j, k))); 60 | } catch(std::out_of_range e) { 61 | // Not in this chunk 62 | } 63 | } 64 | } 65 | } 66 | return result; 67 | } 68 | 69 | std::unordered_map>::const_iterator World::find(const Vector3i &pos) const { 70 | return chunks.find(pos); 71 | } 72 | 73 | std::unordered_map>::const_iterator World::end() const { 74 | return chunks.end(); 75 | } 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8 FATAL_ERROR) 2 | project(konstructs) 3 | 4 | #----------------------------------------------------------------------- 5 | # Check for C++11 enabled compiler 6 | #----------------------------------------------------------------------- 7 | 8 | include(CheckCXXCompilerFlag) 9 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) 10 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) 11 | if(COMPILER_SUPPORTS_CXX11) 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 13 | elseif(COMPILER_SUPPORTS_CXX0X) 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 15 | else() 16 | message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") 17 | endif() 18 | 19 | if(MINGW) 20 | add_definitions(-DWIN32) 21 | add_definitions(-DEIGEN_DONT_ALIGN) 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") 23 | endif() 24 | 25 | if(MSVC) 26 | add_definitions(-DNOMINMAX) 27 | endif() 28 | 29 | #----------------------------------------------------------------------- 30 | # Nano GUI 31 | #----------------------------------------------------------------------- 32 | SET(GLFW_USE_OPTIMUS_HPG ON CACHE BOOL "Build glfw with HPG (High Performance Graphics) on windows") 33 | option(NANOGUI_BUILD_PYTHON OFF) 34 | option(NANOGUI_BUILD_EXAMPLE OFF) 35 | option(NANOGUI_BUILD_SHARED OFF) 36 | add_subdirectory(dependencies/nanogui) 37 | 38 | #----------------------------------------------------------------------- 39 | # Konstructs lib 40 | #----------------------------------------------------------------------- 41 | add_subdirectory(lib) 42 | 43 | #----------------------------------------------------------------------- 44 | # Misc 45 | #----------------------------------------------------------------------- 46 | 47 | set(CMAKE_BUILD_TYPE Debug) 48 | 49 | find_package(ZLIB REQUIRED) 50 | include_directories( 51 | dependencies/nanogui/include 52 | dependencies/nanogui/ext/glfw/include 53 | dependencies/nanogui/ext/glew/include 54 | dependencies/nanogui/ext/eigen 55 | dependencies/nanogui/ext/nanovg/src 56 | dependencies/optional-lite 57 | dependencies/tinyobjloader 58 | lib/include) 59 | 60 | FILE( 61 | GLOB SOURCE_FILES 62 | src/*.cpp) 63 | 64 | add_executable(konstructs ${SOURCE_FILES}) 65 | 66 | set(konstructs_LIBS 67 | konstructs-lib 68 | ${ZLIB_LIBRARIES} 69 | nanogui ${NANOGUI_EXTRA_LIBS} 70 | glfw 71 | ) 72 | 73 | if(WIN32 OR MINGW) 74 | set(konstructs_LIBS ${konstructs_LIBS} ws2_32.lib) 75 | endif() 76 | 77 | target_link_libraries(konstructs ${konstructs_LIBS}) 78 | 79 | install(TARGETS konstructs DESTINATION .) 80 | install(DIRECTORY textures/ DESTINATION textures) 81 | install(DIRECTORY models/ DESTINATION models) 82 | install(DIRECTORY shaders/ DESTINATION shaders) 83 | -------------------------------------------------------------------------------- /lib/src/selection_shader.cpp: -------------------------------------------------------------------------------- 1 | #include "selection_shader.h" 2 | #include "matrix.h" 3 | #include "util.h" 4 | 5 | namespace konstructs { 6 | using namespace Eigen; 7 | 8 | static Matrix wireframe(const float scale) { 9 | Matrix m; 10 | m.col(0) << -1, -1, -1; 11 | m.col(1) << -1, -1, +1; 12 | m.col(2) << -1, -1, -1; 13 | m.col(3) << -1, +1, -1; 14 | m.col(4) << -1, -1, -1; 15 | m.col(5) << +1, -1, -1; 16 | m.col(6) << -1, -1, +1; 17 | m.col(7) << -1, +1, +1; 18 | m.col(8) << -1, -1, +1; 19 | m.col(9) << +1, -1, +1; 20 | m.col(10) << -1, +1, -1; 21 | m.col(11) << -1, +1, +1; 22 | m.col(12) << -1, +1, -1; 23 | m.col(13) << +1, +1, -1; 24 | m.col(14) << -1, +1, +1; 25 | m.col(15) << +1, +1, +1; 26 | m.col(16) << +1, -1, -1; 27 | m.col(17) << +1, -1, +1; 28 | m.col(18) << +1, -1, -1; 29 | m.col(19) << +1, +1, -1; 30 | m.col(20) << +1, -1, +1; 31 | m.col(21) << +1, +1, +1; 32 | m.col(22) << +1, +1, -1; 33 | m.col(23) << +1, +1, +1; 34 | m *= scale; 35 | return m; 36 | } 37 | 38 | SelectionShader::SelectionShader(const float _fov, 39 | const float _near_distance, const float scale) : 40 | ShaderProgram( 41 | "chunk", 42 | "#version 330\n" 43 | "uniform mat4 matrix;\n" 44 | "uniform mat4 translation;\n" 45 | "in vec4 position;\n" 46 | "void main() {\n" 47 | " vec4 global_position = translation * position;\n" 48 | " gl_Position = matrix * global_position;\n" 49 | "}\n", 50 | "#version 330\n" 51 | "out vec4 frag_color;\n" 52 | "void main() {\n" 53 | " frag_color = vec4(0.1, 0.1, 0.1, 1.0);\n" 54 | "}\n", 55 | GL_LINES), 56 | position_attr(attributeId("position")), 57 | matrix(uniformId("matrix")), 58 | translation(uniformId("translation")), 59 | fov(_fov), 60 | near_distance(_near_distance), 61 | model(position_attr, wireframe(scale)) { 62 | } 63 | 64 | void SelectionShader::render(const Player &p, const int width, const int height, 65 | const Vector3i &selected, const float view_distance) { 66 | bind([&](Context c) { 67 | c.enable(GL_DEPTH_TEST); 68 | float aspect_ratio = (float)width / (float)height; 69 | const Matrix4f m = matrix::projection_perspective(fov, aspect_ratio, near_distance, view_distance) * p.view(); 70 | c.set(matrix, m); 71 | c.set(translation, Affine3f(Translation3f(selected.cast())).matrix()); 72 | c.draw(&model); 73 | c.disable(GL_DEPTH_TEST); 74 | }); 75 | } 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /lib/include/chunk_factory.h: -------------------------------------------------------------------------------- 1 | #ifndef __CHUNK_FACTORY_H__ 2 | #define __CHUNK_FACTORY_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "chunk.h" 11 | #include "world.h" 12 | #include "matrix.h" 13 | 14 | namespace konstructs { 15 | using std::shared_ptr; 16 | struct ChunkModelData { 17 | const Vector3i position; 18 | const ChunkData below; 19 | const ChunkData above; 20 | const ChunkData left; 21 | const ChunkData right; 22 | const ChunkData front; 23 | const ChunkData back; 24 | const ChunkData above_left; 25 | const ChunkData above_right; 26 | const ChunkData above_front; 27 | const ChunkData above_back; 28 | const ChunkData above_left_front; 29 | const ChunkData above_right_front; 30 | const ChunkData above_left_back; 31 | const ChunkData above_right_back; 32 | const ChunkData left_front; 33 | const ChunkData left_back; 34 | const ChunkData right_front; 35 | const ChunkData right_back; 36 | const ChunkData self; 37 | }; 38 | 39 | class ChunkModelResult { 40 | public: 41 | ChunkModelResult(const Vector3i _position, const int components, 42 | const int _faces); 43 | ~ChunkModelResult(); 44 | const Vector3i position; 45 | const int size; 46 | const int faces; 47 | GLuint *data(); 48 | private: 49 | GLuint *mData; 50 | }; 51 | 52 | class ChunkModelFactory { 53 | public: 54 | ChunkModelFactory(const BlockTypeInfo &_block_data); 55 | int waiting(); 56 | int total(); 57 | int total_empty(); 58 | int total_created(); 59 | void update_player_chunk(const Vector3i &chunk); 60 | void create_models(const std::vector &positions, 61 | const World &world); 62 | std::vector> fetch_models(); 63 | private: 64 | int processed; 65 | int empty; 66 | int created; 67 | void worker(); 68 | std::mutex mutex; 69 | std::condition_variable chunks_condition; 70 | Vector3i player_chunk; 71 | std::unordered_set> chunks; 72 | std::unordered_map> model_data; 73 | std::vector> models; 74 | const BlockTypeInfo &block_data; 75 | }; 76 | 77 | std::vector adjacent(const Vector3i position, const World &world); 78 | const ChunkData get_chunk(const Vector3i &position, 79 | const World &world); 80 | const ChunkModelData create_model_data(const Vector3i &position, 81 | const World &world); 82 | 83 | shared_ptr compute_chunk(const ChunkModelData &data, const BlockTypeInfo &block_data); 84 | }; 85 | #endif 86 | -------------------------------------------------------------------------------- /lib/src/sky_shader.cpp: -------------------------------------------------------------------------------- 1 | #include "sky_shader.h" 2 | #include "matrix.h" 3 | #include "chunk.h" 4 | #include "cube.h" 5 | 6 | namespace konstructs { 7 | 8 | SkyModel::SkyModel(const GLuint _position_attr, const GLuint _uv_attr) : 9 | position_attr(_position_attr), 10 | uv_attr(_uv_attr) { 11 | float *data = new float[12288]; 12 | make_sphere(data, 1, 3); 13 | glGenBuffers(1, &buffer); 14 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 15 | glBufferData(GL_ARRAY_BUFFER, 12288 * sizeof(GLfloat), 16 | data, GL_STATIC_DRAW); 17 | delete[] data; 18 | } 19 | 20 | int SkyModel::vertices() { 21 | return 512 * 3; 22 | } 23 | 24 | void SkyModel::bind() { 25 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 26 | glEnableVertexAttribArray(position_attr); 27 | glEnableVertexAttribArray(uv_attr); 28 | glVertexAttribPointer(position_attr, 3, GL_FLOAT, GL_FALSE, 29 | sizeof(GLfloat) * 8, 0); 30 | glVertexAttribPointer(uv_attr, 2, GL_FLOAT, GL_FALSE, 31 | sizeof(GLfloat) * 8, (GLvoid *)(sizeof(GLfloat) * 6)); 32 | } 33 | 34 | SkyShader::SkyShader(const float _fov, const GLuint _sky_texture, 35 | const float _near_distance) : 36 | ShaderProgram( 37 | "sky", 38 | "#version 330\n" 39 | "uniform mat4 matrix;\n" 40 | "in vec4 position;\n" 41 | "in vec2 uv;\n" 42 | "out vec2 fragment_uv;\n" 43 | "void main() {\n" 44 | " gl_Position = matrix * position;\n" 45 | " fragment_uv = uv;\n" 46 | "}\n", 47 | "#version 330\n" 48 | "uniform sampler2D sampler;\n" 49 | "uniform float timer;\n" 50 | "in vec2 fragment_uv;\n" 51 | "out vec4 frag_color;\n" 52 | "void main() {\n" 53 | " vec2 uv = vec2(timer, fragment_uv.t);\n" 54 | " frag_color = texture(sampler, uv);\n" 55 | "}\n"), 56 | position_attr(attributeId("position")), 57 | uv_attr(attributeId("uv")), 58 | matrix(uniformId("matrix")), 59 | sampler(uniformId("sampler")), 60 | timer(uniformId("timer")), 61 | fov(_fov), 62 | sky_texture(_sky_texture), 63 | near_distance(_near_distance), 64 | model(position_attr, uv_attr) {} 65 | 66 | void SkyShader::render(const Player &p, const int width, const int height, 67 | const float current_timer, const float view_distance) { 68 | bind([&](Context c) { 69 | c.enable(GL_DEPTH_TEST); 70 | c.enable(GL_CULL_FACE); 71 | float aspect_ratio = (float)width / (float)height; 72 | const Matrix4f m = matrix::projection_perspective(fov, aspect_ratio, near_distance, view_distance) * p.direction(); 73 | c.set(matrix, m); 74 | c.set(sampler, (int)sky_texture); 75 | c.set(timer, current_timer); 76 | c.draw(&model); 77 | c.disable(GL_CULL_FACE); 78 | c.disable(GL_DEPTH_TEST); 79 | }); 80 | } 81 | 82 | 83 | }; 84 | -------------------------------------------------------------------------------- /lib/src/util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "matrix.h" 7 | #include "util.h" 8 | 9 | void update_fps(FPS *fps) { 10 | fps->frames++; 11 | double now = glfwGetTime(); 12 | double elapsed = now - fps->since; 13 | if (elapsed >= 1) { 14 | fps->old_fps = fps->fps; 15 | fps->fps = round(fps->frames / elapsed); 16 | fps->frames = 0; 17 | fps->since = now; 18 | } 19 | } 20 | 21 | char *load_file(const char *path) { 22 | FILE *file = fopen(path, "rb"); 23 | fseek(file, 0, SEEK_END); 24 | int length = ftell(file); 25 | rewind(file); 26 | char *data = (char*)calloc(length + 1, sizeof(char)); 27 | int read = fread(data, 1, length, file); 28 | fclose(file); 29 | if(read != length) { 30 | fprintf(stderr, "Failed to load file %s\n", path); 31 | exit(1); 32 | } 33 | return data; 34 | } 35 | 36 | GLuint gen_buffer(GLsizei size, GLfloat *data) { 37 | GLuint buffer; 38 | glGenBuffers(1, &buffer); 39 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 40 | glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); 41 | glBindBuffer(GL_ARRAY_BUFFER, 0); 42 | return buffer; 43 | } 44 | 45 | GLuint gen_faces(int components, int faces, GLfloat *data) { 46 | GLuint buffer = gen_buffer( 47 | sizeof(GLfloat) * 6 * components * faces, data); 48 | free(data); 49 | return buffer; 50 | } 51 | 52 | void flip_image_vertical( 53 | unsigned char *data, unsigned int width, unsigned int height) { 54 | unsigned int size = width * height * 4; 55 | unsigned int stride = sizeof(char) * width * 4; 56 | unsigned char *new_data = (unsigned char*)malloc(sizeof(unsigned char) * size); 57 | for (unsigned int i = 0; i < height; i++) { 58 | unsigned int j = height - i - 1; 59 | memcpy(new_data + j * stride, data + i * stride, stride); 60 | } 61 | memcpy(data, new_data, size); 62 | free(new_data); 63 | } 64 | 65 | void load_png_texture(const char *file_name) { 66 | unsigned int error; 67 | unsigned char *data; 68 | unsigned int width, height; 69 | error = lodepng_decode32_file(&data, &width, &height, file_name); 70 | if (error) { 71 | fprintf(stderr, "error %u: %s\n", error, lodepng_error_text(error)); 72 | } 73 | flip_image_vertical(data, width, height); 74 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, 75 | GL_UNSIGNED_BYTE, data); 76 | free(data); 77 | } 78 | 79 | void load_png_texture_from_buffer(const char *in, int size) { 80 | unsigned int error; 81 | unsigned char *data; 82 | unsigned int width, height; 83 | error = lodepng_decode32(&data, &width, &height, (unsigned char *)in, size); 84 | if (error) { 85 | fprintf(stderr, "error %u: %s\n", error, lodepng_error_text(error)); 86 | } 87 | flip_image_vertical(data, width, height); 88 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, 89 | GL_UNSIGNED_BYTE, data); 90 | free(data); 91 | } 92 | 93 | int file_exist(const char *filename) { 94 | struct stat st; 95 | int result = stat(filename, &st); 96 | return result == 0; 97 | } 98 | -------------------------------------------------------------------------------- /lib/include/hud_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __HUDSHADER_H__ 2 | #define __HUDSHADER_H__ 3 | #include 4 | #include 5 | #include "optional.hpp" 6 | #include "shader.h" 7 | #include "item.h" 8 | #include "block.h" 9 | 10 | namespace konstructs { 11 | using Eigen::Vector2i; 12 | using nonstd::optional; 13 | using nonstd::nullopt; 14 | 15 | class BaseModel : public BufferModel { 16 | public: 17 | BaseModel(const GLuint position_attr, const GLuint normal_attr, 18 | const GLuint uv_attr); 19 | virtual void bind(); 20 | virtual int vertices(); 21 | protected: 22 | int verts; 23 | private: 24 | const GLuint position_attr; 25 | const GLuint normal_attr; 26 | const GLuint uv_attr; 27 | }; 28 | 29 | class ItemStackModel : public BaseModel { 30 | public: 31 | ItemStackModel(const GLuint position_attr, const GLuint normal_attr, 32 | const GLuint uv_attr, 33 | const std::unordered_map> &stacks, 34 | const BlockTypeInfo &blocks); 35 | }; 36 | 37 | class HealthBarModel : public BaseModel { 38 | public: 39 | HealthBarModel(const std::unordered_map> &stacks, 40 | const GLuint position_attr, const GLuint normal_attr, 41 | const GLuint uv_attr); 42 | }; 43 | 44 | class AmountModel : public BaseModel { 45 | public: 46 | AmountModel(const GLuint position_attr, const GLuint normal_attr, 47 | const GLuint uv_attr, 48 | const std::unordered_map> &stacks); 49 | }; 50 | 51 | class HudModel : public BaseModel { 52 | public: 53 | HudModel(const std::unordered_map> &background, 54 | const GLuint position_attr, const GLuint normal_attr, 55 | const GLuint uv_attr); 56 | }; 57 | 58 | class BlockModel : public BaseModel { 59 | public: 60 | BlockModel(const GLuint position_attr, const GLuint normal_attr, 61 | const GLuint uv_attr, 62 | const int type, const float x, const float y, 63 | const float size, 64 | const BlockTypeInfo &blocks); 65 | }; 66 | 67 | class HudShader: private ShaderProgram { 68 | public: 69 | HudShader(const int columns, const int rows, const GLuint texture, 70 | const GLuint block_texture, const GLuint font_texture, 71 | const GLuint health_bar_texture); 72 | optional clicked_at(const double x, const double y, 73 | const int width, const int height); 74 | void render(const int width, const int height, 75 | const float mouse_x, const float mouse_y, 76 | const Hud &hud, 77 | const BlockTypeInfo &blocks); 78 | 79 | private: 80 | const GLuint position; 81 | const GLuint normal; 82 | const GLuint uv; 83 | const GLuint matrix; 84 | const GLuint offset; 85 | const GLuint sampler; 86 | const GLuint texture; 87 | const GLuint block_texture; 88 | const GLuint font_texture; 89 | const GLuint health_bar_texture; 90 | const int columns; 91 | const int rows; 92 | const float screen_area; 93 | }; 94 | }; 95 | #endif 96 | -------------------------------------------------------------------------------- /lib/include/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef _chunk_h_ 2 | #define _chunk_h_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "optional.hpp" 9 | #include "shader.h" //TODO: remove 10 | #include "block.h" 11 | 12 | #define BLOCK_SIZE 7 13 | #define CHUNK_SIZE 32 14 | #define BLOCK_BUFFER_SIZE (CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE*BLOCK_SIZE) 15 | #define BLOCKS_HEADER_SIZE 6 16 | 17 | namespace konstructs { 18 | using namespace Eigen; 19 | using nonstd::optional; 20 | using std::pair; 21 | 22 | int chunked_int(int p); 23 | 24 | int chunked(float p); 25 | 26 | Vector3i chunked_vec_int(const Vector3i position); 27 | 28 | Vector3i chunked_vec(const Vector3f position); 29 | 30 | class ChunkData { 31 | public: 32 | ChunkData(const Vector3i _position, char *compressed, const int size, uint8_t *buffer, 33 | std::unordered_map> &cached_data); 34 | ChunkData(const Vector3i position, const uint32_t revision, BlockData *blocks); 35 | ChunkData(const uint16_t type); 36 | BlockData get(const Vector3i &pos) const; 37 | ChunkData set(const Vector3i &pos, const BlockData &data) const; 38 | optional> get(const Vector3f &camera_position, 39 | const Vector3f &camera_direction, 40 | const float max_distance, 41 | const BlockTypeInfo &blocks) const; 42 | Vector3i position; 43 | uint32_t revision; 44 | std::shared_ptr blocks; 45 | }; 46 | 47 | extern ChunkData SOLID_CHUNK; 48 | extern ChunkData VACUUM_CHUNK; 49 | 50 | }; 51 | 52 | #define CHUNK_FOR_EACH(blocks, ex, ey, ez, eb) \ 53 | for(int ex = 0; ex < CHUNK_SIZE; ex++) { \ 54 | for(int ey = 0; ey < CHUNK_SIZE; ey++) { \ 55 | for(int ez = 0; ez < CHUNK_SIZE; ez++) { \ 56 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 57 | 58 | #define END_CHUNK_FOR_EACH \ 59 | } \ 60 | } \ 61 | } 62 | 63 | #define CHUNK_FOR_EACH_YZ(blocks, x, ex, ey, ez, eb) \ 64 | for(int ey = 0; ey < CHUNK_SIZE; ey++) { \ 65 | for(int ez = 0; ez < CHUNK_SIZE; ez++) { \ 66 | int ex = x; \ 67 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 68 | 69 | #define CHUNK_FOR_EACH_XY(blocks, z, ex, ey, ez, eb) \ 70 | for(int ex = 0; ex < CHUNK_SIZE; ex++) { \ 71 | for(int ey = 0; ey < CHUNK_SIZE; ey++) { \ 72 | int ez = z; \ 73 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 74 | 75 | #define CHUNK_FOR_EACH_XZ(blocks, y, ex, ey, ez, eb) \ 76 | for(int ex = 0; ex < CHUNK_SIZE; ex++) { \ 77 | for(int ez = 0; ez < CHUNK_SIZE; ez++) { \ 78 | int ey = y; \ 79 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 80 | 81 | #define END_CHUNK_FOR_EACH_2D \ 82 | } \ 83 | } 84 | 85 | #define CHUNK_FOR_EACH_X(blocks, y, z, ex, ey, ez, eb) \ 86 | for(int ex = 0; ex < CHUNK_SIZE; ex++) { \ 87 | int ey = y; \ 88 | int ez = z; \ 89 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 90 | 91 | #define CHUNK_FOR_EACH_Y(blocks, x, z, ex, ey, ez, eb) \ 92 | for(int ey = 0; ey < CHUNK_SIZE; ey++) { \ 93 | int ez = z; \ 94 | int ex = x; \ 95 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 96 | 97 | #define CHUNK_FOR_EACH_Z(blocks, x, y, ex, ey, ez, eb) \ 98 | for(int ez = 0; ez < CHUNK_SIZE; ez++) { \ 99 | int ex = x; \ 100 | int ey = y; \ 101 | BlockData eb = blocks[ex+ey*CHUNK_SIZE+ez*CHUNK_SIZE*CHUNK_SIZE]; 102 | 103 | #define END_CHUNK_FOR_EACH_1D \ 104 | } 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /lib/src/hud.cpp: -------------------------------------------------------------------------------- 1 | #include "hud.h" 2 | 3 | namespace konstructs { 4 | using nonstd::nullopt; 5 | Hud::Hud(const int columns, const int rows, const int belt_size): 6 | rows(rows), 7 | columns(columns), 8 | belt_size(belt_size), 9 | held_stack(nullopt), 10 | selection(0), 11 | interactive(false) { 12 | for(int i = 0; i < belt_size; i++) { 13 | belt.push_back(nullopt); 14 | } 15 | } 16 | optional Hud::held() const { 17 | return held_stack; 18 | } 19 | void Hud::set_held(ItemStack stack) { 20 | held_stack = optional(stack); 21 | } 22 | void Hud::reset_held() { 23 | held_stack = nullopt; 24 | } 25 | void Hud::set_background(const Vector2i pos, const int t) { 26 | bg.erase(pos); 27 | bg.insert({pos, t}); 28 | } 29 | void Hud::reset_background(const Vector2i pos) { 30 | bg.erase(pos); 31 | } 32 | bool Hud::active(const Vector2i pos) const { 33 | return bg.find(pos) != bg.end() || item_stacks.find(pos) != item_stacks.end(); 34 | } 35 | std::unordered_map> Hud::backgrounds() const { 36 | if(!interactive) { 37 | std::unordered_map> new_bg(bg); 38 | for(int i = 0; i < belt_size; i++) { 39 | Vector2i pos((columns - belt_size) / 2 + i, 0); 40 | new_bg.erase(pos); 41 | if(i == selection) 42 | new_bg.insert({pos, 3}); 43 | else 44 | new_bg.insert({pos, 2}); 45 | } 46 | return new_bg; 47 | } else { 48 | return bg; 49 | } 50 | } 51 | void Hud::set_stack(const Vector2i pos, const ItemStack stack) { 52 | item_stacks.erase(pos); 53 | item_stacks.insert({pos, stack}); 54 | } 55 | void Hud::reset_stack(const Vector2i pos) { 56 | item_stacks.erase(pos); 57 | } 58 | std::unordered_map> Hud::stacks() const { 59 | if(!interactive) { 60 | std::unordered_map> 61 | new_item_stacks(item_stacks); 62 | for(int i = 0; i < belt_size; i++) { 63 | auto stack = belt[i]; 64 | Vector2i pos((columns - belt_size) / 2 + i, 0); 65 | new_item_stacks.erase(pos); 66 | if(stack) { 67 | new_item_stacks.insert({pos, *stack}); 68 | } 69 | } 70 | return new_item_stacks; 71 | } else { 72 | return item_stacks; 73 | } 74 | } 75 | void Hud::set_belt(const int pos, ItemStack stack) { 76 | belt[pos] = stack; 77 | } 78 | void Hud::reset_belt(const int pos) { 79 | belt[pos] = nullopt; 80 | } 81 | optional Hud::selected() const { 82 | return belt[selection]; 83 | } 84 | int Hud::scroll(int direction) { 85 | if (selection <= 0 && direction > 0) { 86 | return selection; 87 | } 88 | if (selection >= (belt_size - 1) && direction < 0) { 89 | return selection; 90 | } 91 | selection -= direction; 92 | return selection; 93 | } 94 | void Hud::set_selected(int s) { 95 | selection = s; 96 | for(int i = 0; i < 9; i++) { 97 | Vector2i pos(i + 4, 0); 98 | if(i == selection) { 99 | set_background(pos, 3); 100 | } else { 101 | set_background(pos, 2); 102 | } 103 | } 104 | 105 | } 106 | int Hud::get_selection() const { 107 | return selection; 108 | } 109 | void Hud::set_interactive(bool i) { 110 | interactive = i; 111 | } 112 | bool Hud::get_interactive() const { 113 | return interactive; 114 | } 115 | }; 116 | -------------------------------------------------------------------------------- /lib/include/client.h: -------------------------------------------------------------------------------- 1 | #ifndef _client_h_ 2 | #define _client_h_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "matrix.h" 14 | #include "optional.hpp" 15 | #include "chunk.h" 16 | #include "settings.h" 17 | 18 | #define KEEP_EXTRA_CHUNKS 2 19 | 20 | namespace konstructs { 21 | using namespace std; 22 | using namespace Eigen; 23 | using nonstd::optional; 24 | 25 | class Packet { 26 | public: 27 | Packet(const char _type, const size_t _size): 28 | type(_type), size(_size) { 29 | mBuffer = new char[size]; 30 | } 31 | ~Packet() { 32 | delete[] mBuffer; 33 | } 34 | const char type; 35 | const size_t size; 36 | char* buffer() { 37 | return mBuffer; 38 | } 39 | string to_string() { 40 | string str(mBuffer, size); 41 | return str; 42 | } 43 | private: 44 | char *mBuffer; 45 | }; 46 | 47 | struct ChunkToFetch { 48 | int score; 49 | Vector3i chunk; 50 | }; 51 | 52 | struct LessThanByScore { 53 | bool operator()(const ChunkToFetch& lhs, const ChunkToFetch& rhs) const { 54 | return lhs.score > rhs.score; 55 | } 56 | }; 57 | 58 | class Client { 59 | public: 60 | Client(bool debug_mode); 61 | void open_connection(Settings::Server server); 62 | void version(const int version, const string &nick, const string &hash); 63 | void position(const Vector3f position, 64 | const float rx, const float ry); 65 | void chunk(const Vector3i position); 66 | void konstruct(); 67 | void click_inventory(const int item, const int button); 68 | void close_inventory(); 69 | void talk(const string &text); 70 | void update_radius(const int radius); 71 | void click_at(const int hit, const Vector3i pos, const int button, const int active, 72 | const uint8_t direction, const uint8_t rotation); 73 | string get_error_message(); 74 | void set_connected(bool state); 75 | bool is_connected(); 76 | void set_logged_in(bool state); 77 | bool is_logged_in(); 78 | vector> receive(const int max); 79 | optional receive_prio_chunk(const Vector3i pos); 80 | vector receive_chunks(const int max); 81 | void set_player_chunk(const Vector3i &chunk); 82 | void set_radius(int r); 83 | void set_loaded_radius(int r); 84 | int get_loaded_radius(); 85 | private: 86 | int send_all(const char *data, const int length); 87 | void send_string(const string &str); 88 | size_t recv_all(char* out_buf, const size_t size); 89 | void process_error(Packet *packet); 90 | void process_chunk(Packet *packet); 91 | void process_chunk_updated(Packet *packet); 92 | void recv_worker(); 93 | void send_worker(); 94 | bool is_empty_chunk(Vector3i pos); 95 | bool is_updated_chunk(Vector3i pos); 96 | bool is_requested_chunk(Vector3i pos); 97 | void request_chunk_and_sleep(Vector3i pos, int msec); 98 | void chunk_worker(); 99 | void force_close(); 100 | void received_chunk(const Vector3i &pos); 101 | void chunk_updated(const Vector3i &pos); 102 | int bytes_sent; 103 | int sock; 104 | std::mutex mutex_send; 105 | std::condition_variable cv_send; 106 | std::queue send_queue; 107 | std::mutex packets_mutex; 108 | std::mutex mutex_connected; 109 | std::condition_variable cv_connected; 110 | std::thread *recv_thread; 111 | std::thread *send_thread; 112 | std::thread *chunk_thread; 113 | std::queue> packets; 114 | std::deque chunks; 115 | bool connected; 116 | bool debug_mode; 117 | bool logged_in; 118 | std::string error_message; 119 | char *inflation_buffer; 120 | std::unordered_map> cached_data; 121 | 122 | /* Chunk worker */ 123 | Vector3i player_chunk; 124 | int radius; 125 | int loaded_radius; 126 | std::unordered_set> updated; 127 | std::unordered_set> requested; 128 | std::unordered_set> received; 129 | std::vector received_queue; 130 | std::vector updated_queue; 131 | std::mutex mutex_chunk; 132 | }; 133 | }; 134 | 135 | 136 | #define SHOWERROR(ErrMsg) { char aBuf[256]; snprintf(aBuf, 256, "At '%s:%d' in function '%s' occurred error '%s'",__FILE__,__LINE__,__FUNCTION__,ErrMsg); perror(aBuf); }; 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /lib/src/player_shader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "player_shader.h" 4 | #include "matrix.h" 5 | 6 | namespace konstructs { 7 | 8 | void PlayerShader::add(const Player player) { 9 | players.erase(player.id); 10 | players.insert({player.id, player}); 11 | } 12 | 13 | void PlayerShader::remove(const int pid) { 14 | players.erase(pid); 15 | } 16 | 17 | 18 | PlayerShader::PlayerShader(const float fov, const GLuint player_texture, 19 | const GLuint sky_texture, const float near_distance, 20 | tinyobj::shape_t &shape) : 21 | ShaderProgram( 22 | "player", 23 | "#version 330\n" 24 | "uniform mat4 matrix;\n" 25 | "uniform vec3 camera;\n" 26 | "uniform float fog_distance;\n" 27 | "uniform mat4 translation;\n" 28 | "in vec4 position;\n" 29 | "in vec3 normal;\n" 30 | "in vec2 uv;\n" 31 | "out vec2 fragment_uv;\n" 32 | "out float fog_factor;\n" 33 | "out float fog_height;\n" 34 | "out float diffuse;\n" 35 | "const float pi = 3.14159265;\n" 36 | "const vec3 light_direction = normalize(vec3(-1.0, 1.0, -1.0));\n" 37 | "void main() {\n" 38 | " vec4 global_position = translation * position;\n" 39 | " gl_Position = matrix * global_position;\n" 40 | " fragment_uv = uv.xy;\n" 41 | " diffuse = max(0.0, dot(normal, light_direction));\n" 42 | " float camera_distance = distance(camera, vec3(global_position));\n" 43 | " fog_factor = pow(clamp(camera_distance / fog_distance, 0.0, 1.0), 4.0);\n" 44 | " float dy = global_position.y - camera.y;\n" 45 | " float dx = distance(global_position.xz, camera.xz);\n" 46 | " fog_height = (atan(dy, dx) + pi / 2) / pi;\n" 47 | "}\n", 48 | "#version 330\n" 49 | "uniform sampler2D sampler;\n" 50 | "uniform sampler2D sky_sampler;\n" 51 | "uniform float timer;\n" 52 | "uniform float daylight;\n" 53 | "in vec2 fragment_uv;\n" 54 | "in float fog_factor;\n" 55 | "in float fog_height;\n" 56 | "in float diffuse;\n" 57 | "out vec4 frag_color;\n" 58 | "const float pi = 3.14159265;\n" 59 | "void main() {\n" 60 | " vec3 color = vec3(texture(sampler, fragment_uv));\n" 61 | " if (color == vec3(1.0, 0.0, 1.0)) {\n" 62 | " discard;\n" 63 | " }\n" 64 | " float df = diffuse;\n" 65 | " float value = min(1.0, daylight);\n" 66 | " vec3 light_color = vec3(value * 0.3 + 0.2);\n" 67 | " vec3 ambient = vec3(value * 0.3 + 0.2) + vec3(sin(pi*daylight)/2, sin(pi*daylight)/4, 0.0);\n" 68 | " vec3 light = ambient + light_color * df;\n" 69 | " color = clamp(color * light, vec3(0.0), vec3(1.0));\n" 70 | " vec3 sky_color = vec3(texture(sky_sampler, vec2(timer, fog_height)));\n" 71 | " color = mix(color, sky_color, fog_factor);\n" 72 | " frag_color = vec4(color, 1.0);\n" 73 | "}\n"), 74 | position_attr(attributeId("position")), 75 | normal_attr(attributeId("normal")), 76 | uv_attr(attributeId("uv")), 77 | matrix(uniformId("matrix")), 78 | translation(uniformId("translation")), 79 | sampler(uniformId("sampler")), 80 | sky_sampler(uniformId("sky_sampler")), 81 | fog_distance(uniformId("fog_distance")), 82 | timer(uniformId("timer")), 83 | daylight(uniformId("daylight")), 84 | camera(uniformId("camera")), 85 | fov(fov), 86 | player_texture(player_texture), 87 | sky_texture(sky_texture), 88 | near_distance(near_distance), 89 | model(position_attr, normal_attr, uv_attr, shape) { 90 | } 91 | 92 | int PlayerShader::render(const Player &p, const int width, const int height, 93 | const float current_daylight, const float current_timer, const float view_distance) { 94 | int faces = 0; 95 | int visible = 0; 96 | bind([&](Context c) { 97 | c.enable(GL_DEPTH_TEST); 98 | c.enable(GL_CULL_FACE); 99 | float aspect_ratio = (float)width / (float)height; 100 | const Matrix4f m = matrix::projection_perspective(fov, aspect_ratio, near_distance, view_distance) * p.view(); 101 | c.set(matrix, m); 102 | c.set(sampler, (int)player_texture); 103 | c.set(sky_sampler, (int)sky_texture); 104 | c.set(fog_distance, view_distance); 105 | c.set(daylight, current_daylight); 106 | c.set(timer, current_timer); 107 | c.set(camera, p.camera()); 108 | for(const auto &pair: players) { 109 | const auto &p = pair.second; 110 | c.set(translation, p.translation()); 111 | c.draw(model); 112 | } 113 | c.disable(GL_CULL_FACE); 114 | c.disable(GL_DEPTH_TEST); 115 | }); 116 | return faces; 117 | } 118 | 119 | 120 | }; 121 | -------------------------------------------------------------------------------- /lib/src/block.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "block.h" 4 | #include "matrix.h" 5 | namespace konstructs { 6 | Block::Block(const Vector3i position, const BlockData data): 7 | position(position), data(data) {} 8 | 9 | const std::string direction_to_string[6] = { 10 | "UP", 11 | "DOWN", 12 | "RIGHT", 13 | "LEFT", 14 | "FORWARD", 15 | "BACKWARD" 16 | }; 17 | 18 | const std::string rotation_to_string[4] = { 19 | "IDENTITY (none)", 20 | "LEFT", 21 | "RIGHT", 22 | "HALF (180 degrees)" 23 | }; 24 | 25 | const std::unordered_map> vector_to_direction = { 26 | {Vector3i(0,1,0), DIRECTION_UP}, 27 | {Vector3i(0,-1,0), DIRECTION_DOWN}, 28 | {Vector3i(1,0,0), DIRECTION_RIGHT}, 29 | {Vector3i(-1,0,0), DIRECTION_LEFT}, 30 | {Vector3i(0,0,1), DIRECTION_BACKWARD}, 31 | {Vector3i(0,0,-1), DIRECTION_FORWARD}, 32 | 33 | // Edge cases (pointing on an edge or a corner) 34 | {Vector3i(1,1,0), DIRECTION_RIGHT}, 35 | {Vector3i(1,-1,0), DIRECTION_RIGHT}, 36 | {Vector3i(1,1,1), DIRECTION_RIGHT}, 37 | {Vector3i(1,-1,1), DIRECTION_RIGHT}, 38 | {Vector3i(1,1,-1), DIRECTION_RIGHT}, 39 | {Vector3i(1,-1,-1), DIRECTION_RIGHT}, 40 | {Vector3i(1,0,-1), DIRECTION_RIGHT}, 41 | {Vector3i(1,0,1), DIRECTION_RIGHT}, 42 | 43 | {Vector3i(-1,1,0), DIRECTION_LEFT}, 44 | {Vector3i(-1,-1,0), DIRECTION_LEFT}, 45 | {Vector3i(-1,1,1), DIRECTION_LEFT}, 46 | {Vector3i(-1,-1,1), DIRECTION_LEFT}, 47 | {Vector3i(-1,1,-1), DIRECTION_LEFT}, 48 | {Vector3i(-1,-1,-1), DIRECTION_LEFT}, 49 | {Vector3i(-1,0,-1), DIRECTION_LEFT}, 50 | {Vector3i(-1,0,1), DIRECTION_LEFT}, 51 | 52 | {Vector3i(0,1,1), DIRECTION_UP}, 53 | {Vector3i(0,1,-1), DIRECTION_UP}, 54 | 55 | {Vector3i(0,-1,1), DIRECTION_DOWN}, 56 | {Vector3i(0,-1,-1), DIRECTION_DOWN} 57 | }; 58 | 59 | const uint8_t direction_from_vector(const Vector3i &unit_vector) { 60 | return vector_to_direction.at(unit_vector); 61 | } 62 | 63 | const uint8_t direction_from_vector(const Vector3i &from, const Vector3i &to) { 64 | return direction_from_vector(from - to); 65 | } 66 | 67 | const uint8_t rotation_from_vector(const uint8_t direction, const Vector3f &vector) { 68 | switch(direction) { 69 | case DIRECTION_UP: 70 | if(fabs(vector(0)) > fabs(vector(2))) { 71 | if(vector(0) > 0) { 72 | return ROTATION_RIGHT; 73 | } else { 74 | return ROTATION_LEFT; 75 | } 76 | } else { 77 | if(vector(2) > 0) { 78 | return ROTATION_IDENTITY; 79 | } else { 80 | return ROTATION_HALF; 81 | } 82 | } 83 | case DIRECTION_DOWN: 84 | if(fabs(vector(0)) > fabs(vector(2))) { 85 | if(vector(0) <= 0) { 86 | return ROTATION_RIGHT; 87 | } else { 88 | return ROTATION_LEFT; 89 | } 90 | } else { 91 | if(vector(2) <= 0) { 92 | return ROTATION_HALF; 93 | } else { 94 | return ROTATION_IDENTITY; 95 | } 96 | } 97 | case DIRECTION_LEFT: 98 | if(fabs(vector(1)) > fabs(vector(2))) { 99 | if(vector(1) > 0) { 100 | return ROTATION_RIGHT; 101 | } else { 102 | return ROTATION_LEFT; 103 | } 104 | 105 | } else { 106 | if(vector(2) > 0) { 107 | return ROTATION_IDENTITY; 108 | } else { 109 | return ROTATION_HALF; 110 | } 111 | } 112 | case DIRECTION_RIGHT: 113 | if(fabs(vector(1)) > fabs(vector(2))) { 114 | if(vector(1) <= 0) { 115 | return ROTATION_RIGHT; 116 | } else { 117 | return ROTATION_LEFT; 118 | } 119 | } else { 120 | if(vector(2) <= 0) { 121 | return ROTATION_HALF; 122 | } else { 123 | return ROTATION_IDENTITY; 124 | } 125 | } 126 | case DIRECTION_FORWARD: 127 | if(fabs(vector(0)) > fabs(vector(1))) { 128 | if(vector(0) > 0) { 129 | return ROTATION_RIGHT; 130 | } else { 131 | return ROTATION_LEFT; 132 | } 133 | } else { 134 | if(vector(1) > 0) { 135 | return ROTATION_IDENTITY; 136 | } else { 137 | return ROTATION_HALF; 138 | } 139 | } 140 | case DIRECTION_BACKWARD: 141 | if(fabs(vector(0)) > fabs(vector(1))) { 142 | if(vector(0) <= 0) { 143 | return ROTATION_LEFT; 144 | } else { 145 | return ROTATION_RIGHT; 146 | } 147 | } else { 148 | if(vector(1) <= 0) { 149 | return ROTATION_IDENTITY; 150 | } else { 151 | return ROTATION_HALF; 152 | } 153 | } 154 | } 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /lib/src/textures.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "textures.h" 7 | #include "util.h" 8 | #define KONSTRUCTS_PATH_SIZE 256 9 | 10 | namespace konstructs { 11 | void shtxt_path(const char *name, const char *type, char *path, size_t max_len) { 12 | snprintf(path, max_len, "%s/%s", type, name); 13 | 14 | if (!file_exist(path)) { 15 | if(const char* env_p = std::getenv("SNAP")) { 16 | snprintf(path, max_len, "%s/%s/%s", env_p, type, name); 17 | } 18 | } 19 | 20 | if (!file_exist(path)) { 21 | snprintf(path, max_len, "../%s/%s", type, name); 22 | } 23 | 24 | if (!file_exist(path)) { 25 | snprintf(path, max_len, "/usr/local/share/konstructs-client/%s/%s", type, name); 26 | } 27 | 28 | if (!file_exist(path)) { 29 | printf("Error, no %s for %s found.\n", type, name); 30 | exit(1); 31 | } 32 | } 33 | 34 | void texture_path(const char *name, char *path, size_t max_len) { 35 | shtxt_path(name, "textures", path, max_len); 36 | } 37 | 38 | void model_path(const char *name, char *path, size_t max_len) { 39 | shtxt_path(name, "models", path, max_len); 40 | } 41 | 42 | void shader_path(const char *name, char *path, size_t max_len) { 43 | shtxt_path(name, "shaders", path, max_len); 44 | } 45 | 46 | void load_textures() { 47 | char txtpth[KONSTRUCTS_PATH_SIZE]; 48 | 49 | GLuint sky; 50 | glGenTextures(1, &sky); 51 | glActiveTexture(GL_TEXTURE0 + SKY_TEXTURE); 52 | glBindTexture(GL_TEXTURE_2D, sky); 53 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 54 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 55 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 56 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 57 | texture_path("sky.png", txtpth, KONSTRUCTS_PATH_SIZE); 58 | load_png_texture(txtpth); 59 | 60 | GLuint font; 61 | glGenTextures(1, &font); 62 | glActiveTexture(GL_TEXTURE0 + FONT_TEXTURE); 63 | glBindTexture(GL_TEXTURE_2D, font); 64 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 65 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 66 | texture_path("font.png", txtpth, KONSTRUCTS_PATH_SIZE); 67 | load_png_texture(txtpth); 68 | 69 | GLuint inventory_texture; 70 | glGenTextures(1, &inventory_texture); 71 | glActiveTexture(GL_TEXTURE0 + INVENTORY_TEXTURE); 72 | glBindTexture(GL_TEXTURE_2D, inventory_texture); 73 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 74 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 75 | texture_path("inventory.png", txtpth, KONSTRUCTS_PATH_SIZE); 76 | load_png_texture(txtpth); 77 | 78 | GLuint player_texture; 79 | glGenTextures(1, &player_texture); 80 | glActiveTexture(GL_TEXTURE0 + PLAYER_TEXTURE); 81 | glBindTexture(GL_TEXTURE_2D, player_texture); 82 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 83 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 84 | texture_path("player.png", txtpth, KONSTRUCTS_PATH_SIZE); 85 | load_png_texture(txtpth); 86 | 87 | GLuint damage_texture; 88 | glGenTextures(1, &damage_texture); 89 | glActiveTexture(GL_TEXTURE0 + DAMAGE_TEXTURE); 90 | glBindTexture(GL_TEXTURE_2D, damage_texture); 91 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 92 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 93 | texture_path("damage.png", txtpth, KONSTRUCTS_PATH_SIZE); 94 | load_png_texture(txtpth); 95 | 96 | GLuint health_bar_texture; 97 | glGenTextures(1, &health_bar_texture); 98 | glActiveTexture(GL_TEXTURE0 + HEALTH_BAR_TEXTURE); 99 | glBindTexture(GL_TEXTURE_2D, health_bar_texture); 100 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 101 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 102 | texture_path("health_bar.png", txtpth, KONSTRUCTS_PATH_SIZE); 103 | load_png_texture(txtpth); 104 | 105 | // Set Active texture to GL_TEXTURE0, nanogui will use the active texture 106 | glActiveTexture(GL_TEXTURE0); 107 | } 108 | 109 | tinyobj::shape_t load_player() { 110 | char objpth[KONSTRUCTS_PATH_SIZE]; 111 | model_path("player.obj", objpth, KONSTRUCTS_PATH_SIZE); 112 | 113 | std::vector shapes; 114 | std::vector materials; 115 | 116 | std::string err; 117 | tinyobj::LoadObj(shapes, materials, err, objpth); 118 | 119 | return shapes[0]; 120 | } 121 | 122 | std::string load_shader(const char* name) { 123 | char shader_pth[KONSTRUCTS_PATH_SIZE]; 124 | shader_path(name, shader_pth, KONSTRUCTS_PATH_SIZE); 125 | std::ifstream shader(shader_pth); 126 | std::string shader_str((std::istreambuf_iterator(shader)), 127 | std::istreambuf_iterator()); 128 | return shader_str; 129 | } 130 | 131 | std::string load_chunk_vertex_shader() { 132 | return load_shader("chunk.vert"); 133 | } 134 | 135 | std::string load_chunk_fragment_shader() { 136 | return load_shader("chunk.frag"); 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /lib/src/settings.cpp: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | #include "SimpleIni.h" 3 | #include "GLFW/glfw3.h" 4 | #include 5 | 6 | namespace konstructs { 7 | 8 | void config_path(char* r, size_t size) { 9 | if(const char* path = std::getenv("LOCALAPPDATA")) { 10 | // Looks like Windows, C:\Users\(user-name)\AppData\Local 11 | snprintf(r, size, "%s\\%s", path, "konstructs.ini"); 12 | } else if (const char* path = std::getenv("SNAP_USER_DATA")) { 13 | // Looks like Linux and inside a snap, $HOME/snap/konstructs-client/(version) 14 | snprintf(r, size, "%s/%s", path, "konstructs.conf"); 15 | } else if (const char* path = std::getenv("HOME")) { 16 | char _path[4096]; 17 | snprintf(_path, 4096, "%s/%s", path, ".config"); 18 | struct stat info; 19 | if (stat(_path, &info) == 0) { 20 | // Looks like we have a $HOME/.config folder, use it 21 | snprintf(r, size, "%s/%s", _path, "konstructs.conf"); 22 | } else { 23 | // Just use a classic dotfile in HOME 24 | snprintf(r, size, "%s/%s", path, ".konstructs.conf"); 25 | } 26 | } 27 | } 28 | 29 | void load_settings(Settings &settings) { 30 | char settings_path[4096]; 31 | config_path(settings_path, 4096); 32 | 33 | CSimpleIniA ini; 34 | ini.SetUnicode(true); 35 | if (ini.LoadFile(settings_path) < 0) { 36 | std::cout << "Failed to load config file " << settings_path << std::endl; 37 | std::cout << "This is normal if this is your first run." << std::endl; 38 | } 39 | 40 | // Load default values (and set defaults) 41 | settings.server.address = ini.GetValue("server", "address", "play.konstructs.org"); 42 | settings.server.port = (unsigned int)ini.GetLongValue("server", "port", 4080); 43 | settings.server.username = ini.GetValue("server", "username", ""); 44 | settings.server.password = ini.GetValue("server", "password", ""); 45 | settings.server.password_save = !settings.server.password.empty(); 46 | settings.client.debug = ini.GetBoolValue("client", "debug", false); 47 | settings.client.field_of_view = (int)ini.GetLongValue("client", "field_of_view", 70); 48 | settings.client.window_width = (unsigned int)ini.GetLongValue("client", "window_width", 854); 49 | settings.client.window_height = (unsigned int)ini.GetLongValue("client", "window_height", 480); 50 | settings.client.radius_start = (unsigned int)ini.GetLongValue("client", "radius_start", 5); 51 | settings.client.radius_max = (unsigned int)ini.GetLongValue("client", "radius_max", 20); 52 | settings.client.frames_per_second = (float)ini.GetLongValue("client", "frames_per_second", 60); 53 | settings.keys.up = (int)ini.GetLongValue("keys", "up", GLFW_KEY_W); 54 | settings.keys.down = (int)ini.GetLongValue("keys", "down", GLFW_KEY_S); 55 | settings.keys.left = (int)ini.GetLongValue("keys", "left", GLFW_KEY_A); 56 | settings.keys.right = (int)ini.GetLongValue("keys", "right", GLFW_KEY_D); 57 | settings.keys.jump = (int)ini.GetLongValue("keys", "jump", GLFW_KEY_SPACE); 58 | settings.keys.fly = (int)ini.GetLongValue("keys", "fly", GLFW_KEY_TAB); 59 | settings.keys.sneak = (int)ini.GetLongValue("keys", "sneak", GLFW_KEY_LEFT_SHIFT); 60 | settings.keys.tertiary = (int)ini.GetLongValue("keys", "tertiary", GLFW_KEY_E); 61 | settings.keys.debug = (int)ini.GetLongValue("keys", "debug", GLFW_KEY_F3); 62 | } 63 | 64 | void save_settings(Settings &settings) { 65 | char settings_path[4096]; 66 | config_path(settings_path, 4096); 67 | 68 | CSimpleIniA ini; 69 | ini.SetUnicode(true); 70 | 71 | ini.SetValue("server", "address", settings.server.address.c_str()); 72 | ini.SetLongValue("server", "port", settings.server.port); 73 | ini.SetValue("server", "username", settings.server.username.c_str()); 74 | 75 | // Only save the password back to the file if it's already there. 76 | // This allows users to save the password, but it's an opt-in. 77 | if (settings.server.password_save) { 78 | ini.SetValue("server", "password", settings.server.password.c_str()); 79 | } else { 80 | ini.SetValue("server", "password", ""); 81 | } 82 | 83 | ini.SetBoolValue("client", "debug", settings.client.debug); 84 | ini.SetLongValue("client", "field_of_view", settings.client.field_of_view); 85 | ini.SetLongValue("client", "window_width", settings.client.window_width); 86 | ini.SetLongValue("client", "window_height", settings.client.window_height); 87 | ini.SetLongValue("client", "radius_start", settings.client.radius_start); 88 | ini.SetLongValue("client", "radius_max", settings.client.radius_max); 89 | ini.SetLongValue("client", "frames_per_second", (long)settings.client.frames_per_second); 90 | ini.SetLongValue("keys", "up", settings.keys.up); 91 | ini.SetLongValue("keys", "down", settings.keys.down); 92 | ini.SetLongValue("keys", "left", settings.keys.left); 93 | ini.SetLongValue("keys", "right", settings.keys.right); 94 | ini.SetLongValue("keys", "jump", settings.keys.jump); 95 | ini.SetLongValue("keys", "fly", settings.keys.fly); 96 | ini.SetLongValue("keys", "sneak", settings.keys.sneak); 97 | ini.SetLongValue("keys", "tertiary", settings.keys.tertiary); 98 | ini.SetLongValue("keys", "debug", settings.keys.debug); 99 | if (ini.SaveFile(settings_path) < 0) { 100 | std::cout << "Failed to save config file " << settings_path << std::endl; 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /package/Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile is used by travis to trigger the different build steps 2 | # vim: ts=4 sts=4 sw=4 noexpandtab 3 | 4 | SHELL=/bin/bash 5 | PROJECT_DIR=$$PWD/.. 6 | DOCKER ?= docker 7 | 8 | linux: build/konstructs 9 | 10 | build/konstructs: 11 | @echo -e "\033[1m" 12 | @echo "# Use Docker to build a Linux build" 13 | @echo -e "\033[0m" 14 | 15 | ${DOCKER} run -ti -v ${PROJECT_DIR}:/build \ 16 | -e HOST_UID=$$UID \ 17 | nsgb/konstructs-build-image-linux \ 18 | make docker-linux -C package 19 | 20 | osx: 21 | @echo -e "\033[1m" 22 | @echo "# Build on OSX" 23 | @echo -e "\033[0m" 24 | 25 | cd .. \ 26 | && sudo cmake . \ 27 | && sudo make 28 | 29 | docs: 30 | @echo -e "\033[1m" 31 | @echo "# Use Docker to build docs with Doxygen" 32 | @echo -e "\033[0m" 33 | 34 | ${DOCKER} run -ti -v ${PROJECT_DIR}:/build \ 35 | -e HOST_UID=$$UID \ 36 | nsgb/konstructs-build-image-docs \ 37 | doxygen 38 | 39 | /usr/local/bin/bin2c: 40 | @echo -e "\033[1m" 41 | @echo "# Build a Linux version of bin2c for the cross compile" 42 | @echo -e "\033[0m" 43 | 44 | gcc -o bin2c ../dependencies/nanogui/resources/bin2c.c 45 | mv bin2c /usr/local/bin/bin2c 46 | 47 | windows: 48 | @echo -e "\033[1m" 49 | @echo "# Use Docker to cross compile a Windows build" 50 | @echo -e "\033[0m" 51 | 52 | ${DOCKER} run -ti -v ${PROJECT_DIR}:/build \ 53 | -e HOST_UID=$$UID \ 54 | nsgb/konstructs-build-image-windows \ 55 | make docker-windows -C package 56 | 57 | /tmp/build-deps/zlib: 58 | @echo -e "\033[1m" 59 | @echo "# ERROR: Place zlib lib for Windows in /tmp/build-deps/zlib" 60 | @echo -e "\033[0m" 61 | exit 1 62 | 63 | /tmp/build-deps/zlib-bin: 64 | @echo -e "\033[1m" 65 | @echo "# ERROR: Place zlib bin for Windows in /tmp/build-deps/zlib-bin" 66 | @echo -e "\033[0m" 67 | exit 1 68 | 69 | docker-windows: export CC = /usr/bin/x86_64-w64-mingw32-gcc 70 | docker-windows: export CXX = /usr/bin/x86_64-w64-mingw32-g++ 71 | docker-windows: export CPP = /usr/bin/x86_64-w64-mingw32-cpp 72 | docker-windows: export RANLIB = /usr/bin/x86_64-w64-mingw32-ranlib 73 | docker-windows: /tmp/build-deps/zlib /usr/local/bin/bin2c 74 | @echo -e "\033[1m" 75 | @echo "# Build the Windows build inside Docker" 76 | @echo -e "\033[0m" 77 | 78 | mkdir -p build-windows 79 | cd build-windows && \ 80 | cmake -DCMAKE_TOOLCHAIN_FILE=./Toolchain-mingw32.cmake ../.. 81 | cd build-windows && make 82 | chown -R $$HOST_UID build-windows 83 | 84 | 85 | docker-linux: 86 | @echo -e "\033[1m" 87 | @echo "# Build the Linux build inside Docker" 88 | @echo -e "\033[0m" 89 | 90 | mkdir -p build 91 | cd build && cmake ../.. && make 92 | chown -R $$HOST_UID build 93 | 94 | package-prep: 95 | @echo -e "\033[1m" 96 | @echo "# Fetch remote tags and unshallow the repo (if needed)" 97 | @echo -e "\033[0m" 98 | 99 | # Fetch the remote tags 100 | git fetch --tags 101 | 102 | # Travis only fetch the 50 last commits, this fixes that. 103 | git fetch --unshallow || : 104 | 105 | zip: windows 106 | @echo -e "\033[1m" 107 | @echo "# Use Docker to make the zip package" 108 | @echo -e "\033[0m" 109 | 110 | ${DOCKER} run -ti -v ${PROJECT_DIR}:/build \ 111 | -e TRAVIS_BUILD_NUMBER \ 112 | -e HOST_UID=$$UID \ 113 | nsgb/konstructs-build-image-windows \ 114 | make docker-zip -C package 115 | 116 | docker-package-prep: 117 | @echo -e "\033[1m" 118 | @echo "# Move files in place" 119 | @echo -e "\033[0m" 120 | 121 | mkdir -p konstructs-client-package/usr/local/bin/ 122 | mkdir -p konstructs-client-package/usr/local/share/konstructs-client 123 | mkdir -p konstructs-client-package/usr/share/applications 124 | cp -r ../textures konstructs-client-package/usr/local/share/konstructs-client 125 | cp -r ../models konstructs-client-package/usr/local/share/konstructs-client 126 | cp -r ../shaders konstructs-client-package/usr/local/share/konstructs-client 127 | cp build/konstructs konstructs-client-package/usr/local/bin/konstructs-client 128 | cp konstructs-client.desktop \ 129 | konstructs-client-package/usr/share/applications/konstructs-client.desktop 130 | 131 | docker-zip: /tmp/build-deps/zlib-bin 132 | @echo -e "\033[1m" 133 | @echo "# Make a zip archive" 134 | @echo -e "\033[0m" 135 | 136 | mkdir -p konstructs-client 137 | cp build-windows/konstructs.exe konstructs-client/konstructs-client.exe 138 | cp /tmp/build-deps/zlib-bin/bin/zlib1.dll konstructs-client 139 | cp /usr/i686-w64-mingw32/sys-root/mingw/bin/libgcc_s_sjlj-1.dll konstructs-client 140 | cp /usr/i686-w64-mingw32/sys-root/mingw/bin/libstdc++-6.dll konstructs-client 141 | cp /usr/i686-w64-mingw32/sys-root/mingw/bin/libwinpthread-1.dll konstructs-client 142 | cp -r ../textures konstructs-client 143 | cp -r ../models konstructs-client 144 | cp -r ../shaders konstructs-client 145 | zip -r konstructs-client.zip konstructs-client 146 | chown $$HOST_UID *.zip 147 | rm -rf konstructs-client 148 | 149 | upload-docs: docs 150 | @echo -e "\033[1m" 151 | @echo "# Upload docs to doc.konstructs.org" 152 | @echo -e "\033[0m" 153 | 154 | @mkdir -p $$HOME/.aws 155 | @echo "[default]" > $$HOME/.aws/credentials 156 | @echo "aws_access_key_id = $${AWS_S3_ACCESS_KEY_ID}" >> $$HOME/.aws/credentials 157 | @echo "aws_secret_access_key = $${AWS_S3_SECRET_ACCESS_KEY}" >> $$HOME/.aws/credentials 158 | @echo "[default]" > $$HOME/.aws/config 159 | @echo "region = eu-west-1" >> $$HOME/.aws/config 160 | 161 | aws s3 sync ../docs/html/ s3://doc.konstructs.org/client/ --acl public-read --delete 162 | 163 | astyle: 164 | ${DOCKER} run -ti -v ${PROJECT_DIR}:/build \ 165 | nsgb/konstructs-build-image-astyle \ 166 | make docker-astyle -C package 167 | 168 | docker-astyle: 169 | astyle \ 170 | --style=java \ 171 | --indent-namespaces \ 172 | --indent-preproc-block \ 173 | --indent-preproc-cond \ 174 | --add-brackets \ 175 | --keep-one-line-statements \ 176 | --convert-tabs \ 177 | --max-code-length=120 \ 178 | --recursive \ 179 | '../src/*.cpp' \ 180 | '../lib/src/*.cpp' \ 181 | '../lib/include/*.h' 182 | 183 | test: astyle 184 | git diff --exit-code 185 | 186 | clean: 187 | rm -rf \ 188 | konstructs-client-linux \ 189 | build build-windows \ 190 | konstructs-client.zip 191 | 192 | .PHONY: linux osx window clean docs zip test astyle 193 | .PHONY: docker-linux docker-package-prep docker-windows docker-zip 194 | .PHONY: package-prep 195 | .PHONY: upload-docs 196 | -------------------------------------------------------------------------------- /lib/src/chunk_shader.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | #include 3 | #include 4 | #include "chunk_shader.h" 5 | #include "matrix.h" 6 | 7 | namespace konstructs { 8 | 9 | const Array3i chunk_offset = Vector3i(CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE).array(); 10 | ChunkModel::ChunkModel(const shared_ptr &data, 11 | GLuint data_attr) : 12 | position(data->position), 13 | faces(data->faces), 14 | data_attr(data_attr) { 15 | glGenBuffers(1, &buffer); 16 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 17 | glBufferData(GL_ARRAY_BUFFER, data->size * sizeof(GLuint), 18 | data->data(), GL_STATIC_DRAW); 19 | Vector3f pos = 20 | (position.array() * chunk_offset).matrix().cast(); 21 | 22 | Vector3f rpos = Vector3f(pos[0], pos[2], pos[1]); 23 | translation = Affine3f(Translation3f(rpos)).matrix(); 24 | } 25 | 26 | int ChunkModel::vertices() { 27 | return faces*6; 28 | } 29 | 30 | void ChunkModel::bind() { 31 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 32 | glEnableVertexAttribArray(data_attr); 33 | glVertexAttribIPointer(data_attr, 2, GL_UNSIGNED_INT, 34 | 0, 0); 35 | } 36 | 37 | void ChunkShader::add(const shared_ptr &data) { 38 | auto it = models.find(data->position); 39 | auto model = new ChunkModel(data, data_attr); 40 | if (it != models.end()) { 41 | auto second = it->second; 42 | it->second = model; 43 | delete second; 44 | } else { 45 | models.insert({data->position, model}); 46 | } 47 | 48 | } 49 | 50 | ChunkShader::ChunkShader(const float fov, const GLuint block_texture, const GLuint damage_texture, 51 | const GLuint sky_texture, const float near_distance, const string &vert_str, 52 | const string &frag_str) : 53 | ShaderProgram("chunk", vert_str, frag_str), 54 | data_attr(attributeId("data")), 55 | matrix(uniformId("matrix")), 56 | translation(uniformId("translation")), 57 | sampler(uniformId("sampler")), 58 | sky_sampler(uniformId("sky_sampler")), 59 | damage_sampler(uniformId("damage_sampler")), 60 | fog_distance(uniformId("fog_distance")), 61 | ambient_color(uniformId("ambient_color")), 62 | ambient_light(uniformId("ambient_light")), 63 | timer(uniformId("timer")), 64 | camera(uniformId("camera")), 65 | fov(fov), 66 | block_texture(block_texture), 67 | sky_texture(sky_texture), 68 | damage_texture(damage_texture), 69 | near_distance(near_distance) {} 70 | 71 | int ChunkShader::size() const { 72 | return models.size(); 73 | } 74 | 75 | int ChunkShader::render(const Player &player, const int width, const int height, 76 | const float current_daylight, const float current_timer, 77 | const int radius, const float view_distance, const Vector3i &player_chunk) { 78 | int faces = 0; 79 | int visible = 0; 80 | bind([&](Context c) { 81 | c.enable(GL_DEPTH_TEST); 82 | c.enable(GL_CULL_FACE); 83 | float aspect_ratio = (float)width / (float)height; 84 | const Matrix4f m = matrix::projection_perspective(fov, aspect_ratio, near_distance, view_distance) * player.view(); 85 | c.set(matrix, m); 86 | c.set(sampler, (int)block_texture); 87 | c.set(sky_sampler, (int)sky_texture); 88 | c.set(damage_sampler, (int)damage_texture); 89 | c.set(fog_distance, view_distance); 90 | float value = min(1.0f, current_daylight); 91 | float v = value * 0.3 + 0.15; 92 | c.set(ambient_color, Vector3f(v, v, v)); 93 | Vector3f ambient((float)sin(M_PI*current_daylight)/2 + v, (float)sin(M_PI*current_daylight)/4 + v, v); 94 | c.set(ambient_light, ambient); 95 | c.set(timer, current_timer); 96 | c.set(camera, player.camera()); 97 | float planes[6][4]; 98 | matrix::ext_frustum_planes(planes, radius, m); 99 | for(auto it = models.begin(); it != models.end();) { 100 | int distance = (it->second->position - player_chunk).norm(); 101 | if (distance > radius + KEEP_EXTRA_CHUNKS) { 102 | it = models.erase(it); 103 | } else if(distance <= radius) { 104 | auto pos = it->first; 105 | if(chunk_visible(planes, pos)) { 106 | const auto m = it->second; 107 | visible++; 108 | c.set(translation, m->translation); 109 | c.draw(m); 110 | faces += m->faces; 111 | } 112 | ++it; 113 | } else { 114 | ++it; 115 | } 116 | } 117 | c.disable(GL_CULL_FACE); 118 | c.disable(GL_DEPTH_TEST); 119 | }); 120 | return faces; 121 | } 122 | 123 | bool chunk_visible(const float planes[6][4], const Vector3i &position) { 124 | float x = position[0] * CHUNK_SIZE - 1; 125 | float z = position[1] * CHUNK_SIZE - 1; 126 | float y = position[2] * CHUNK_SIZE - 1; 127 | float d = CHUNK_SIZE + 1; 128 | float points[8][3] = { 129 | {x + 0, y + 0, z + 0}, 130 | {x + d, y + 0, z + 0}, 131 | {x + 0, y + 0, z + d}, 132 | {x + d, y + 0, z + d}, 133 | {x + 0, y + d, z + 0}, 134 | {x + d, y + d, z + 0}, 135 | {x + 0, y + d, z + d}, 136 | {x + d, y + d, z + d} 137 | }; 138 | for (int i = 0; i < 6; i++) { 139 | int in = 0; 140 | int out = 0; 141 | for (int j = 0; j < 8; j++) { 142 | float d = 143 | planes[i][0] * points[j][0] + 144 | planes[i][1] * points[j][1] + 145 | planes[i][2] * points[j][2] + 146 | planes[i][3]; 147 | if (d < 0) { 148 | out++; 149 | } else { 150 | in++; 151 | } 152 | if (in && out) { 153 | break; 154 | } 155 | } 156 | if (in == 0) { 157 | return false; 158 | } 159 | } 160 | return true; 161 | } 162 | 163 | }; 164 | -------------------------------------------------------------------------------- /lib/src/matrix.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "matrix.h" 4 | #include "util.h" 5 | 6 | #define FAR_PLANE 1000 7 | #define NEAR_PLANE 0.25 8 | #define FOV 60 9 | 10 | namespace konstructs { 11 | namespace matrix { 12 | using namespace Eigen; 13 | 14 | Matrix4f projection(int width, int height) { 15 | float aspect_ratio = (float)width / (float)height; 16 | float y_scale = (1.0f / tanf((FOV / 2.0f) * PI / 360.0)) * aspect_ratio; 17 | float x_scale = y_scale / aspect_ratio; 18 | float frustrum_length = FAR_PLANE - NEAR_PLANE; 19 | 20 | Matrix4f m; 21 | m << 22 | x_scale, 0.0, 0.0, 0.0, 23 | 0.0, y_scale, 0.0, 0.0, 24 | 0.0, 0.0, -((FAR_PLANE + NEAR_PLANE) / frustrum_length), -((2 * NEAR_PLANE * FAR_PLANE) / frustrum_length), 25 | 0.0, 0.0, -1.0, 0.0; 26 | return m; 27 | } 28 | 29 | Matrix4f projection_2d(const int width, const int height) { 30 | Matrix4f mvp; 31 | mvp.setIdentity(); 32 | mvp.row(0) *= (float) height / (float) width; 33 | return mvp; 34 | } 35 | 36 | Matrix4f projection_frustum(float left, float right, float bottom, 37 | float top, float znear, float zfar) { 38 | float temp, temp2, temp3, temp4; 39 | temp = 2.0 * znear; 40 | temp2 = right - left; 41 | temp3 = top - bottom; 42 | temp4 = zfar - znear; 43 | Matrix4f m; 44 | 45 | m << 46 | temp / temp2, 0.0, (right + left) / temp2, 0.0, 47 | 0.0, temp / temp3, (top + bottom) / temp3, 0.0, 48 | 0.0, 0.0, (-zfar - znear) / temp4, (-temp * zfar) / temp4, 49 | 0.0, 0.0, -1.0, 0.0; 50 | return m; 51 | } 52 | 53 | Matrix4f projection_perspective(float fov, float aspect, 54 | float znear, float zfar) { 55 | float ymax, xmax; 56 | ymax = znear * tanf(fov * PI / 360.0); 57 | xmax = ymax * aspect; 58 | return projection_frustum(-xmax, xmax, -ymax, ymax, znear, zfar); 59 | } 60 | 61 | void ext_frustum_planes(float planes[6][4], int radius, const Matrix4f &m) { 62 | float znear = 0.125; 63 | float zfar = radius * 32 + 64; 64 | planes[0][0] = m(3) + m(0); 65 | planes[0][1] = m(7) + m(4); 66 | planes[0][2] = m(11) + m(8); 67 | planes[0][3] = m(15) + m(12); 68 | planes[1][0] = m(3) - m(0); 69 | planes[1][1] = m(7) - m(4); 70 | planes[1][2] = m(11) - m(8); 71 | planes[1][3] = m(15) - m(12); 72 | planes[2][0] = m(3) + m(1); 73 | planes[2][1] = m(7) + m(5); 74 | planes[2][2] = m(11) + m(9); 75 | planes[2][3] = m(15) + m(13); 76 | planes[3][0] = m(3) - m(1); 77 | planes[3][1] = m(7) - m(5); 78 | planes[3][2] = m(11) - m(9); 79 | planes[3][3] = m(15) - m(13); 80 | planes[4][0] = znear * m(3) + m(2); 81 | planes[4][1] = znear * m(7) + m(6); 82 | planes[4][2] = znear * m(11) + m(10); 83 | planes[4][3] = znear * m(15) + m(14); 84 | planes[5][0] = zfar * m(3) - m(2); 85 | planes[5][1] = zfar * m(7) - m(6); 86 | planes[5][2] = zfar * m(11) - m(10); 87 | planes[5][3] = zfar * m(15) - m(14); 88 | } 89 | }; 90 | }; 91 | 92 | void normalize(float *x, float *y, float *z) { 93 | float d = sqrtf((*x) * (*x) + (*y) * (*y) + (*z) * (*z)); 94 | *x /= d; *y /= d; *z /= d; 95 | } 96 | 97 | void mat_identity(float *matrix) { 98 | matrix[0] = 1; 99 | matrix[1] = 0; 100 | matrix[2] = 0; 101 | matrix[3] = 0; 102 | matrix[4] = 0; 103 | matrix[5] = 1; 104 | matrix[6] = 0; 105 | matrix[7] = 0; 106 | matrix[8] = 0; 107 | matrix[9] = 0; 108 | matrix[10] = 1; 109 | matrix[11] = 0; 110 | matrix[12] = 0; 111 | matrix[13] = 0; 112 | matrix[14] = 0; 113 | matrix[15] = 1; 114 | } 115 | 116 | void mat_translate(float *matrix, float dx, float dy, float dz) { 117 | matrix[0] = 1; 118 | matrix[1] = 0; 119 | matrix[2] = 0; 120 | matrix[3] = 0; 121 | matrix[4] = 0; 122 | matrix[5] = 1; 123 | matrix[6] = 0; 124 | matrix[7] = 0; 125 | matrix[8] = 0; 126 | matrix[9] = 0; 127 | matrix[10] = 1; 128 | matrix[11] = 0; 129 | matrix[12] = dx; 130 | matrix[13] = dy; 131 | matrix[14] = dz; 132 | matrix[15] = 1; 133 | } 134 | 135 | void mat_rotate(float *matrix, float x, float y, float z, float angle) { 136 | normalize(&x, &y, &z); 137 | float s = sinf(angle); 138 | float c = cosf(angle); 139 | float m = 1 - c; 140 | matrix[0] = m * x * x + c; 141 | matrix[1] = m * x * y - z * s; 142 | matrix[2] = m * z * x + y * s; 143 | matrix[3] = 0; 144 | matrix[4] = m * x * y + z * s; 145 | matrix[5] = m * y * y + c; 146 | matrix[6] = m * y * z - x * s; 147 | matrix[7] = 0; 148 | matrix[8] = m * z * x - y * s; 149 | matrix[9] = m * y * z + x * s; 150 | matrix[10] = m * z * z + c; 151 | matrix[11] = 0; 152 | matrix[12] = 0; 153 | matrix[13] = 0; 154 | matrix[14] = 0; 155 | matrix[15] = 1; 156 | } 157 | 158 | void mat_vec_multiply(float *vector, float *a, float *b) { 159 | float result[4]; 160 | for (int i = 0; i < 4; i++) { 161 | float total = 0; 162 | for (int j = 0; j < 4; j++) { 163 | int p = j * 4 + i; 164 | int q = j; 165 | total += a[p] * b[q]; 166 | } 167 | result[i] = total; 168 | } 169 | for (int i = 0; i < 4; i++) { 170 | vector[i] = result[i]; 171 | } 172 | } 173 | 174 | void mat_multiply(float *matrix, float *a, float *b) { 175 | float result[16]; 176 | for (int c = 0; c < 4; c++) { 177 | for (int r = 0; r < 4; r++) { 178 | int index = c * 4 + r; 179 | float total = 0; 180 | for (int i = 0; i < 4; i++) { 181 | int p = i * 4 + r; 182 | int q = c * 4 + i; 183 | total += a[p] * b[q]; 184 | } 185 | result[index] = total; 186 | } 187 | } 188 | for (int i = 0; i < 16; i++) { 189 | matrix[i] = result[i]; 190 | } 191 | } 192 | 193 | void mat_apply(float *data, float *matrix, int count, int offset, int stride) { 194 | float vec[4] = {0, 0, 0, 1}; 195 | for (int i = 0; i < count; i++) { 196 | float *d = data + offset + stride * i; 197 | vec[0] = *(d++); vec[1] = *(d++); vec[2] = *(d++); 198 | mat_vec_multiply(vec, matrix, vec); 199 | d = data + offset + stride * i; 200 | *(d++) = vec[0]; *(d++) = vec[1]; *(d++) = vec[2]; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /lib/src/chunk.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "block.h" 5 | #include "compress.h" 6 | #include "chunk.h" 7 | #include "matrix.h" 8 | 9 | namespace konstructs { 10 | using nonstd::nullopt; 11 | 12 | ChunkData SOLID_CHUNK(SOLID_TYPE); 13 | ChunkData VACUUM_CHUNK(VACUUM_TYPE); 14 | 15 | 16 | std::shared_ptr read_chunk_data(uint8_t *buffer, 17 | std::unordered_map> &cached_data) { 18 | BlockData *blocks = new BlockData[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE]; 19 | uint16_t chunk_type = buffer[0] + (buffer[1] << 8); 20 | bool use_cached = true; 21 | for(int i = 0; i < CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE; i++) { 22 | blocks[i].type = buffer[i * BLOCK_SIZE] + (buffer[i * BLOCK_SIZE + 1] << 8); 23 | blocks[i].health = buffer[i * BLOCK_SIZE + 2] + ((buffer[i * BLOCK_SIZE + 3] & 0x07) << 8); 24 | blocks[i].direction = (buffer[i * BLOCK_SIZE + 3] & 0xE0) >> 5; 25 | blocks[i].rotation = (buffer[i * BLOCK_SIZE + 3] & 0x18) >> 3; 26 | blocks[i].ambient = (buffer[i * BLOCK_SIZE + 4] & 0xF); 27 | blocks[i].r = (buffer[i * BLOCK_SIZE + 4] & 0xF0) >> 4; 28 | blocks[i].g = (buffer[i * BLOCK_SIZE + 5] & 0xF); 29 | blocks[i].b = (buffer[i * BLOCK_SIZE + 5] & 0xF0) >> 4; 30 | blocks[i].light = (buffer[i * BLOCK_SIZE + 6] & 0xF); 31 | if(blocks[i].type != chunk_type || blocks[i].light > 0 || blocks[i].ambient < AMBIENT_LIGHT_FULL) { 32 | use_cached = false; 33 | } 34 | } 35 | if(use_cached) { 36 | try { 37 | auto r = cached_data.at(chunk_type); 38 | delete[] blocks; 39 | return r; 40 | } catch(std::out_of_range e) { 41 | std::shared_ptr r(blocks, std::default_delete()); 42 | cached_data.insert({chunk_type, r}); 43 | return r; 44 | } 45 | } else { 46 | std::shared_ptr r(blocks, std::default_delete()); 47 | return r; 48 | } 49 | } 50 | 51 | int chunked_int(int p) { 52 | if(p < 0) { 53 | return (p - CHUNK_SIZE + 1) / CHUNK_SIZE; 54 | } else { 55 | return p / CHUNK_SIZE; 56 | } 57 | } 58 | 59 | int chunked(float p) { 60 | return chunked_int(roundf(p)); 61 | } 62 | 63 | Vector3i chunked_vec_int(const Vector3i position) { 64 | return Vector3i(chunked_int(position[0]), chunked_int(position[2]), chunked_int(position[1])); 65 | } 66 | 67 | Vector3i chunked_vec(const Vector3f position) { 68 | return chunked_vec_int(position.cast()); 69 | } 70 | 71 | ChunkData::ChunkData(const Vector3i position, char *compressed, const int size, uint8_t *buffer, 72 | std::unordered_map> &cached_data): 73 | position(position) { 74 | int out_size = inflate_data(compressed + BLOCKS_HEADER_SIZE, 75 | size - BLOCKS_HEADER_SIZE, 76 | (char*)buffer, BLOCK_BUFFER_SIZE); 77 | revision = 78 | compressed[2] + 79 | (compressed[2 + 1] << 8) + 80 | (compressed[2 + 2] << 16) + 81 | (compressed[2 + 3] << 24); 82 | blocks = read_chunk_data(buffer, cached_data); 83 | } 84 | 85 | ChunkData::ChunkData(const uint16_t type) : revision(0) { 86 | BlockData *b = new BlockData[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE]; 87 | for(int i = 0; i < CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE; i++) { 88 | b[i].type = type; 89 | b[i].health = MAX_HEALTH; 90 | b[i].direction = DIRECTION_UP; 91 | b[i].rotation = ROTATION_IDENTITY; 92 | b[i].ambient = AMBIENT_LIGHT_DARK; 93 | b[i].r = 0; 94 | b[i].g = 0; 95 | b[i].b = 0; 96 | b[i].light = 0; 97 | } 98 | blocks = std::shared_ptr(b, std::default_delete()); 99 | } 100 | 101 | ChunkData::ChunkData(const Vector3i position, const uint32_t revision, BlockData *b) : 102 | position(position), revision(revision) { 103 | blocks = std::shared_ptr(b, std::default_delete()); 104 | } 105 | 106 | BlockData ChunkData::get(const Vector3i &pos) const { 107 | int lx = pos[0] - position[0] * CHUNK_SIZE; 108 | int ly = pos[1] - position[2] * CHUNK_SIZE; 109 | int lz = pos[2] - position[1] * CHUNK_SIZE; 110 | 111 | // TODO: Looking for a block in the wrong chunk is a bit weird, but hit code does it 112 | if(lx < CHUNK_SIZE && ly < CHUNK_SIZE && lz < CHUNK_SIZE && 113 | lx >= 0 && ly >= 0 && lz >= 0) { 114 | int i = lx+ly*CHUNK_SIZE+lz*CHUNK_SIZE*CHUNK_SIZE; 115 | return blocks.get()[i]; 116 | } else { 117 | return {0, 0}; 118 | } 119 | } 120 | 121 | ChunkData ChunkData::set(const Vector3i &pos, const BlockData &data) const { 122 | int lx = pos[0] - position[0] * CHUNK_SIZE; 123 | int ly = pos[1] - position[2] * CHUNK_SIZE; 124 | int lz = pos[2] - position[1] * CHUNK_SIZE; 125 | 126 | BlockData *new_blocks = new BlockData[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE]; 127 | BlockData *b = blocks.get(); 128 | memcpy(new_blocks, b, CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE*sizeof(BlockData)); 129 | 130 | new_blocks[lx+ly*CHUNK_SIZE+lz*CHUNK_SIZE*CHUNK_SIZE] = data; 131 | 132 | // A chunk that we altered ourselves is treated as invalid 133 | 134 | return ChunkData(position, 0, new_blocks); 135 | } 136 | 137 | /** 138 | * Using a camera position and a camera direction, find the 139 | * closest within max_distance that intersect the directional 140 | * vector. 141 | */ 142 | optional> ChunkData::get(const Vector3f &camera_position, 143 | const Vector3f &camera_direction, 144 | const float max_distance, 145 | const BlockTypeInfo &blocks) const { 146 | int m = 4; 147 | Vector3f pos = camera_position; 148 | Vector3i blockPos(roundf(pos[0]), roundf(pos[1]), roundf(pos[2])); 149 | for (int i = 0; i < max_distance * m; i++) { 150 | const Vector3i nBlockPos(roundf(pos[0]), roundf(pos[1]), roundf(pos[2])); 151 | if (nBlockPos != blockPos) { 152 | BlockData data = get(nBlockPos); 153 | if (blocks.is_obstacle[data.type] || blocks.is_plant[data.type]) { 154 | return optional>(pair(Block(blockPos, data), 155 | Block(nBlockPos, data))); 156 | } 157 | blockPos = nBlockPos; 158 | } 159 | pos += (camera_direction / m); 160 | } 161 | return nullopt; 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /shaders/chunk.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | /* Look-up tables for reconstructing cube vertices and normals. 4 | * They are all allocated as uniforms since for arrays this is faster 5 | * by several orders of a magnitude (otherwise they consume the per 6 | * execution unit memory of the shader). 7 | */ 8 | 9 | /* distance from the middle of the voxel */ 10 | const float N = 0.5; 11 | 12 | /* This table contains all required vertexes for the different voxels. 13 | */ 14 | uniform vec3 positions[16] = vec3[16]( 15 | vec3(-N, -N, -N), //0 - Block corners 16 | vec3(-N, -N, +N), //1 17 | vec3(-N, +N, -N), //2 18 | vec3(+N, -N, -N), //3 19 | vec3(+N, +N, -N), //4 20 | vec3(-N, +N, +N), //5 21 | vec3(+N, -N, +N), //6 22 | vec3(+N, +N, +N), //7 23 | vec3(+0, -N, -N), //8 - Plant corners 24 | vec3(+0, -N, +N), //9 25 | vec3(+0, +N, -N), //10 26 | vec3(+0, +N, +N), //11 27 | vec3(-N, -N, +0), //12 28 | vec3(-N, +N, +0), //13 29 | vec3(+N, -N, +0), //14 30 | vec3(+N, +N, +0) //15 31 | ); 32 | 33 | /* 34 | * This table contain normal vectors (the vector that points 35 | * perpendicular to the the plane formed by the triangle). 36 | * There are 6 normals, one for direction and axis. 37 | */ 38 | uniform vec3 normals[6] = vec3[6]( 39 | vec3(-1, 0, 0), //0 40 | vec3(+1, 0, 0), //1 41 | vec3(0, +1, 0), //2 42 | vec3(0, -1, 0), //3 43 | vec3(0, 0, -1), //4 44 | vec3(0, 0, +1) //5 45 | ); 46 | 47 | /* Data offsets and mask */ 48 | 49 | /* x component */ 50 | 51 | /* First is the normal index (0 - 5) encoded in 3 bits */ 52 | const uint OFF_NORMAL = uint(0); 53 | const uint MASK_NORMAL = uint(0x07); 54 | 55 | /* Second is the vertex index (0 - 15) encoded in 4 bits */ 56 | const uint OFF_VERTEX = uint(3); 57 | const uint MASK_VERTEX = uint(0x0F); 58 | 59 | /* Then comes the x,y,z position of the block in the chunk, 60 | * encoded in 5 bits each */ 61 | const uint OFF_X = uint(7); 62 | const uint OFF_Y = uint(12); 63 | const uint OFF_Z = uint(17); 64 | const uint MASK_POS = uint(0x1F); 65 | 66 | /* Then the ambient occlusion (0 - 31) encoded in 5 bits. */ 67 | const uint OFF_AO = uint(22); 68 | const uint MASK_AO = uint(0x1F); 69 | 70 | /* And lastly the uv coordinates for the block damage, (0 - 8) and (0 - 1) encoded in 4 + 1 bits */ 71 | const uint OFF_DAMAGE_U = uint(27); 72 | const uint MASK_DAMAGE_U = uint(0x0F); 73 | 74 | const uint OFF_DAMAGE_V = uint(31); 75 | const uint MASK_DAMAGE_V = uint(0x01); 76 | 77 | /* y component */ 78 | 79 | /* First comes the UV coordinates for the texture of this face, 80 | * encoded in 5 bits each. 81 | */ 82 | const uint OFF_DU = uint(0); 83 | const uint OFF_DV = uint(5); 84 | const uint MASK_UV = uint(0x1F); 85 | 86 | /* Second comes the ambient light level of the block encoded 87 | * in 4 bits 88 | */ 89 | const uint OFF_AL = uint(10); 90 | const uint MASK_AL = uint(0x0F); 91 | 92 | /* Third comes the light color and light level encoded with 4 bits 93 | * per RGB channel and 4 bits of strength 94 | */ 95 | const uint OFF_R = uint(14); 96 | const uint MASK_R = uint(0x0F); 97 | 98 | const uint OFF_G = uint(18); 99 | const uint MASK_G = uint(0x0F); 100 | 101 | const uint OFF_B = uint(22); 102 | const uint MASK_B = uint(0x0F); 103 | 104 | const uint OFF_LIGHT = uint(26); 105 | const uint MASK_LIGHT = uint(0x0F); 106 | 107 | /* UV stepping */ 108 | const float S = (1.0 / 16.0); 109 | const float DS = (1.0 / 8.0); 110 | 111 | /* Influences how much the damage is mixed into the block */ 112 | const float damage_weight = 0.6; 113 | 114 | /* Projection and player translation */ 115 | uniform mat4 matrix; 116 | 117 | /* Camera position */ 118 | uniform vec3 camera; 119 | 120 | /* Fog distance */ 121 | uniform float fog_distance; 122 | 123 | /* Chunk translation */ 124 | uniform mat4 translation; 125 | 126 | /* The per vertex data as described above */ 127 | in uvec2 data; 128 | 129 | /* Output to fragment shader */ 130 | 131 | /* UV coordinates in texture space */ 132 | out vec2 fragment_uv; 133 | out vec2 damage_uv; 134 | 135 | /* Damage */ 136 | flat out float damage_factor; 137 | 138 | /* The ambient value */ 139 | out float ambient; 140 | out float fragment_ao; 141 | 142 | 143 | 144 | /* The light value */ 145 | out vec3 light; 146 | 147 | out float fog_factor; 148 | out float fog_height; 149 | 150 | /* Diffuse lightning a.k.a. the sun */ 151 | out float diffuse; 152 | 153 | const float PI = 3.14159265; 154 | const vec3 light_direction = normalize(vec3(-1.0, 1.0, -1.0)); 155 | 156 | void main() { 157 | 158 | /* Extract data from x component */ 159 | uint d1 = data.x; 160 | 161 | /* Extract the block face index (0 - 5) */ 162 | uint normal = (d1 >> OFF_NORMAL) & MASK_NORMAL; 163 | 164 | /* Extract the corner of the face (0 - 3) */ 165 | uint vertex = (d1 >> OFF_VERTEX) & MASK_VERTEX; 166 | 167 | /* Extract the amount of ambient occlusion */ 168 | uint ao = (d1 >> OFF_AO) & MASK_AO; 169 | 170 | /* Extract block damage UV */ 171 | uint damage_u = (d1 >> OFF_DAMAGE_U) & MASK_DAMAGE_U; 172 | uint damage_v = (d1 >> OFF_DAMAGE_V) & MASK_DAMAGE_V; 173 | 174 | /* Extract block position */ 175 | uint x = (d1 >> OFF_X) & MASK_POS; 176 | uint y = (d1 >> OFF_Y) & MASK_POS; 177 | uint z = (d1 >> OFF_Z) & MASK_POS; 178 | 179 | /* Extract data from y component */ 180 | uint d2 = data.y; 181 | 182 | /* Extract the block type texture index */ 183 | uint du = (d2 >> OFF_DU) & MASK_UV; 184 | uint dv = (d2 >> OFF_DV) & MASK_UV; 185 | 186 | /* Extract the ambient light */ 187 | uint al = (d2 >> OFF_AL) & MASK_AL; 188 | 189 | /* Extract light */ 190 | uint r = (d2 >> OFF_R) & MASK_R; 191 | uint g = (d2 >> OFF_G) & MASK_G; 192 | uint b = (d2 >> OFF_B) & MASK_B; 193 | uint light_level = (d2 >> OFF_LIGHT) & MASK_LIGHT; 194 | 195 | /* All values extracted, shader code starts here */ 196 | 197 | /* Create a translation matrix from the block position */ 198 | mat4 block_translation = mat4( 199 | 1, 0, 0, 0, 200 | 0, 1, 0, 0, 201 | 0, 0, 1, 0, 202 | x, y, z, 1); 203 | 204 | /* Calculate the vertex position within the chunk by applying the block translation */ 205 | vec4 position = block_translation * vec4(positions[vertex], 1); 206 | 207 | /* Calculate the global position of the vertex by applying the chunk translation */ 208 | vec4 global_position = translation * position; 209 | 210 | /* Apply projection */ 211 | gl_Position = matrix * global_position; 212 | 213 | /* Calculate light */ 214 | float rf = float(r) * 0.0625; 215 | float gf = float(g) * 0.0625; 216 | float bf = float(b) * 0.0625; 217 | float lf = float(light_level) * 0.0625; 218 | 219 | light = vec3(lf * rf, lf * gf, lf * bf); 220 | 221 | /* Calculate the ambient light */ 222 | ambient = float(al + uint(1)) * 0.0625; 223 | 224 | /* Calculate ambient occlusion */ 225 | fragment_ao = (1.0 - float(ao) * 0.03125 * 0.7); 226 | 227 | /* Calculate UV coordinates */ 228 | fragment_uv = vec2(du * S, dv * S); 229 | damage_uv = vec2(damage_u * DS, damage_v); 230 | 231 | damage_factor = (damage_u * DS) * damage_weight; 232 | 233 | diffuse = clamp(dot(normals[normal], light_direction), 0.0, 1.0); 234 | 235 | float camera_distance = distance(camera, vec3(global_position)); 236 | fog_factor = pow(clamp(camera_distance / fog_distance, 0.0, 1.0), 4.0); 237 | float dy = global_position.y - camera.y; 238 | float dx = distance(global_position.xz, camera.xz); 239 | fog_height = (atan(dy, dx) + PI / 2) / PI; 240 | } 241 | -------------------------------------------------------------------------------- /lib/include/shader.h: -------------------------------------------------------------------------------- 1 | #ifndef __SHADER_H__ 2 | #define __SHADER_H__ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "tiny_obj_loader.h" 9 | 10 | namespace konstructs { 11 | using namespace Eigen; 12 | 13 | /** Generic method to load a shader of a specific type from a 14 | * string. This method is used internally by the Shader class, 15 | * but can also be used directly to compile a shader and 16 | * associate it with an id. 17 | * @param type Type of shader (as described in glCreateShader) 18 | * @param shader The source code of the shader 19 | * @return The id of the newly created shader 20 | * @throws std::runtime_error on compilation failure 21 | */ 22 | GLuint creater_shader(const GLint type, const std::string &shader); 23 | 24 | /** Base class for models. A model is responsible for keeping 25 | * track of a VBO and the number of vertices it contains. It is 26 | * also responsible to bind the VBO to the appropriate attributes 27 | * (in variables) when its bind method is called. 28 | */ 29 | class Model { 30 | public: 31 | /** Bind the VBO associated with the Model */ 32 | virtual void bind() = 0; 33 | /** Return the number of vertices in the VBO */ 34 | virtual int vertices() = 0; 35 | /** Return if model is indexed */ 36 | virtual bool is_indexed() = 0; 37 | }; 38 | 39 | /** Base class for that require no special handling for their VBO. 40 | * The variable buffer contains the handler to a VBO. It will 41 | * automatically be deleted by the desctructor of this class. 42 | */ 43 | class BufferModel : public Model { 44 | public: 45 | ~BufferModel(); 46 | virtual bool is_indexed(); 47 | protected: 48 | GLuint buffer; 49 | }; 50 | 51 | /** Model that is based on any Eigen Matrix type. 52 | * Each column represents a vertices in the VBO 53 | */ 54 | class EigenModel : public BufferModel { 55 | public: 56 | /** Creates a EigenModel 57 | * @param name Attribute id that the VBO should be attached to. 58 | * @param m Eigen Matrix to be stored in the VBO 59 | */ 60 | template EigenModel(const GLuint name, 61 | const Matrix &m): 62 | name(name), 63 | type((GLuint) konstructs::type_traits ::type), 64 | integral((bool) konstructs::type_traits::integral), 65 | dim(m.rows()), 66 | columns(m.cols()) { 67 | uint32_t compSize = sizeof(typename Matrix::Scalar); 68 | GLuint glType = (GLuint) konstructs::type_traits::type; 69 | 70 | glGenBuffers(1, &buffer); 71 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 72 | glBufferData(GL_ARRAY_BUFFER, m.size() * compSize, m.data(), GL_STATIC_DRAW); 73 | glBindBuffer(GL_ARRAY_BUFFER, 0); 74 | } 75 | virtual void bind(); 76 | virtual int vertices(); 77 | const GLuint name; 78 | private: 79 | const GLuint type; 80 | const bool integral; 81 | const GLuint dim; 82 | const int columns; 83 | }; 84 | 85 | class ShapeModel : public Model { 86 | public: 87 | ShapeModel(GLuint position_attr, GLuint normal_attr, GLuint uv_attr, 88 | tinyobj::shape_t &shape); 89 | ~ShapeModel(); 90 | virtual void bind(); 91 | virtual int vertices(); 92 | virtual bool is_indexed(); 93 | private: 94 | GLuint position_attr; 95 | GLuint normal_attr; 96 | GLuint uv_attr; 97 | GLuint position_buffer; 98 | GLuint normal_buffer; 99 | GLuint uv_buffer; 100 | GLuint index_buffer; 101 | int indices; 102 | }; 103 | 104 | /** A Context instance provides access to Open GL functionality in 105 | * safe way. When it is available the different methods are 106 | * allowed to be used (i.e. the correct shader program has been 107 | * loaded and the correct VAO attached). 108 | */ 109 | class Context { 110 | public: 111 | Context(const GLenum _draw_mode) : 112 | draw_mode(_draw_mode) {} 113 | /** Draw a model 114 | * @param model A pointer to the Model to be drawn 115 | */ 116 | void draw(Model *model); 117 | /** Draw a model 118 | * @param model A reference to the Model to be drawn 119 | */ 120 | void draw(Model &model); 121 | /** Set a uniform to a float value */ 122 | void set(const GLuint name, const float value); 123 | /** Set a uniform to a Matrix4f */ 124 | void set(const GLuint name, const Matrix4f &value); 125 | /** Set a uniform to a integer value */ 126 | void set(const GLuint name, const int value); 127 | /** Set a uniform to a GL unsigned integer value */ 128 | void set(const GLuint name, const GLuint value); 129 | /** Set a uniform to a Vector2f */ 130 | void set(const GLuint name, const Vector2f &v); 131 | /** Set a uniform to a Vector3f */ 132 | void set(const GLuint name, const Vector3f &v); 133 | /** Set a uniform to a Vector4f */ 134 | void set(const GLuint name, const Vector4f &v); 135 | /** See glLogicOp */ 136 | void logic_op(const GLenum opcode); 137 | /** See glEnable */ 138 | void enable(const GLenum cap); 139 | /** See glDisable */ 140 | void disable(const GLenum cap); 141 | /** See glBlendFunc*/ 142 | void blend_func(const GLenum sfactor, const GLenum dfactor); 143 | private: 144 | const GLenum draw_mode; 145 | }; 146 | 147 | /** A ShaderProgram is a combination of a vertex and a fragment 148 | * shader. The class keeps track of loading the program and the 149 | * VAO as well as providing a Context to be used for drawing 150 | * Models associated with the shader. 151 | */ 152 | class ShaderProgram { 153 | public: 154 | /** Creates a ShaderProgram instance. 155 | * @param shader_name Name of the shader 156 | * @param vertex_shader Code for the vertex shader 157 | * @param fragment_shader Code for the fragment shader 158 | * @param draw_mode The drawing mode used (see glDrawArrays), 159 | * defaults to GL_TRIANGLES 160 | */ 161 | ShaderProgram(const std::string &shader_name, 162 | const std::string &vertex_shader, 163 | const std::string &fragment_shader, 164 | const GLenum draw_mode = GL_TRIANGLES); 165 | ~ShaderProgram(); 166 | /** Binds the shader and the VAO and calls the provided function with a Context 167 | * @param context The function that will draw models using this shader 168 | */ 169 | void bind(std::function f); 170 | protected: 171 | /** Look up a uniform id by name. This is requires the program 172 | * to be bound. It is typically used by the constructor or 173 | * initialization list of a sub class to get the uniform id 174 | * of uniforms provided in the shader code. 175 | */ 176 | GLuint uniformId(const std::string &uName); 177 | /** Look up a attribute id by name. This is requires the program 178 | * to be bound. It is typically used by the constructor or 179 | * initialization list of a sub class to get the attribute id 180 | * of attributes provided in the vertex shader code. 181 | */ 182 | GLuint attributeId(const std::string &aName); 183 | private: 184 | const std::string &name; 185 | const GLuint vertex; 186 | const GLuint fragment; 187 | const GLuint program; 188 | const GLenum draw_mode; 189 | GLuint vao; 190 | }; 191 | 192 | }; 193 | 194 | #endif 195 | -------------------------------------------------------------------------------- /lib/src/shader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "shader.h" 3 | 4 | namespace konstructs { 5 | GLuint creater_shader(const GLint type, const std::string &shader) { 6 | if (shader.empty()) { 7 | throw std::invalid_argument( "Shader code must not be empty" ); 8 | } 9 | GLuint id = glCreateShader(type); 10 | const char *shader_const = shader.c_str(); 11 | glShaderSource(id, 1, &shader_const, nullptr); 12 | glCompileShader(id); 13 | 14 | GLint status; 15 | glGetShaderiv(id, GL_COMPILE_STATUS, &status); 16 | 17 | if (status != GL_TRUE) { 18 | char buffer[512]; 19 | if (type == GL_VERTEX_SHADER) { 20 | std::cerr << "Vertex shader:" << std::endl; 21 | } else if (type == GL_FRAGMENT_SHADER) { 22 | std::cerr << "Fragment shader:" << std::endl; 23 | } else if (type == GL_GEOMETRY_SHADER) { 24 | std::cerr << "Geometry shader:" << std::endl; 25 | } 26 | std::cerr << shader << std::endl << std::endl; 27 | glGetShaderInfoLog(id, 512, nullptr, buffer); 28 | std::cerr << "Error: " << std::endl << buffer << std::endl; 29 | throw std::runtime_error("Shader compilation failed!"); 30 | } 31 | 32 | return id; 33 | } 34 | 35 | ShaderProgram::ShaderProgram(const std::string &shader_name, 36 | const std::string &vertex_shader, 37 | const std::string &fragment_shader, 38 | const GLenum _draw_mode): 39 | name(shader_name), 40 | vertex(creater_shader(GL_VERTEX_SHADER, vertex_shader)), 41 | fragment(creater_shader(GL_FRAGMENT_SHADER, fragment_shader)), 42 | program(glCreateProgram()), 43 | draw_mode(_draw_mode) { 44 | glAttachShader(program, vertex); 45 | glAttachShader(program, fragment); 46 | glLinkProgram(program); 47 | GLint status; 48 | glGetProgramiv(program, GL_LINK_STATUS, &status); 49 | if (status != GL_TRUE) { 50 | char buffer[512]; 51 | glGetProgramInfoLog(program, 512, nullptr, buffer); 52 | std::cerr << "Linker error: " << std::endl << buffer << std::endl; 53 | throw std::runtime_error("Shader linking failed!"); 54 | } 55 | glGenVertexArrays(1, &vao); 56 | glUseProgram(program); 57 | } 58 | ShaderProgram::~ShaderProgram() { 59 | glDeleteVertexArrays(1, &vao); 60 | glDeleteShader(vertex); 61 | glDeleteShader(fragment); 62 | glDeleteProgram(program); 63 | } 64 | 65 | GLuint ShaderProgram::uniformId(const std::string &uName) { 66 | GLint id = glGetUniformLocation(program, uName.c_str()); 67 | if (id == -1) { 68 | std::cerr << name << "Warning: did not find uniform " << uName << std::endl; 69 | throw std::runtime_error("Could not find uniform for program"); 70 | } 71 | return id; 72 | } 73 | 74 | GLuint ShaderProgram::attributeId(const std::string &aName) { 75 | GLint id = glGetAttribLocation(program, aName.c_str()); 76 | if (id == -1) { 77 | std::cerr << name << ": warning: did not find attrib " << aName << std::endl; 78 | throw std::runtime_error("Could not find attribute for program"); 79 | } 80 | return id; 81 | } 82 | 83 | void ShaderProgram::bind(std::function f) { 84 | glUseProgram(program); 85 | glBindVertexArray(vao); 86 | Context c(draw_mode); 87 | f(c); 88 | } 89 | 90 | void Context::draw(Model *model) { 91 | model->bind(); 92 | if(model->is_indexed()) { 93 | glDrawElements(draw_mode, model->vertices(), GL_UNSIGNED_INT, 0); 94 | } else { 95 | glDrawArrays(draw_mode, 0, model->vertices()); 96 | } 97 | } 98 | 99 | void Context::draw(Model &model) { 100 | model.bind(); 101 | if(model.is_indexed()) { 102 | glDrawElements(draw_mode, model.vertices(), GL_UNSIGNED_INT, 0); 103 | } else { 104 | glDrawArrays(draw_mode, 0, model.vertices()); 105 | } 106 | } 107 | 108 | void Context::set(const GLuint name, const float value) { 109 | glUniform1f(name, value); 110 | } 111 | 112 | void Context::set(const GLuint name, const Matrix4f &value) { 113 | glUniformMatrix4fv(name, 1, GL_FALSE, value.data()); 114 | } 115 | 116 | void Context::set(const GLuint name, const GLuint value) { 117 | glUniform1i(name, value); 118 | } 119 | 120 | 121 | void Context::set(const GLuint name, const int value) { 122 | glUniform1i(name, value); 123 | } 124 | 125 | void Context::set(const GLuint name, const Vector2f &v) { 126 | glUniform2f(name, v.x(), v.y()); 127 | } 128 | 129 | void Context::set(const GLuint name, const Vector3f &v) { 130 | glUniform3f(name, v.x(), v.y(), v.z()); 131 | } 132 | 133 | void Context::set(const GLuint name, const Vector4f &v) { 134 | glUniform4f(name, v.x(), v.y(), v.z(), v.w()); 135 | } 136 | 137 | void Context::logic_op(const GLenum opcode) { 138 | glLogicOp(opcode); 139 | } 140 | 141 | void Context::enable(const GLenum cap) { 142 | glEnable(cap); 143 | } 144 | 145 | void Context::disable(const GLenum cap) { 146 | glDisable(cap); 147 | } 148 | 149 | void Context::blend_func(const GLenum sfactor, const GLenum dfactor) { 150 | glBlendFunc(sfactor, dfactor); 151 | } 152 | 153 | BufferModel::~BufferModel() { 154 | glDeleteBuffers(1, &buffer); 155 | } 156 | 157 | bool BufferModel::is_indexed() { 158 | return false; 159 | } 160 | 161 | int EigenModel::vertices() { 162 | return columns; 163 | } 164 | 165 | void EigenModel::bind() { 166 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 167 | glEnableVertexAttribArray(name); 168 | glVertexAttribPointer(name, dim, 169 | type, integral, 0, 0); 170 | } 171 | 172 | ShapeModel::ShapeModel(GLuint position_attr, GLuint normal_attr, 173 | GLuint uv_attr, tinyobj::shape_t &shape) : 174 | position_attr(position_attr), 175 | normal_attr(normal_attr), 176 | uv_attr(uv_attr) { 177 | 178 | glGenBuffers(1, &position_buffer); 179 | glBindBuffer(GL_ARRAY_BUFFER, position_buffer); 180 | glBufferData(GL_ARRAY_BUFFER, shape.mesh.positions.size() * sizeof(GLfloat), 181 | shape.mesh.positions.data(), GL_STATIC_DRAW); 182 | 183 | glGenBuffers(1, &normal_buffer); 184 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer); 185 | glBufferData(GL_ARRAY_BUFFER, shape.mesh.normals.size() * sizeof(GLfloat), 186 | shape.mesh.normals.data(), GL_STATIC_DRAW); 187 | 188 | glGenBuffers(1, &uv_buffer); 189 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 190 | glBufferData(GL_ARRAY_BUFFER, shape.mesh.texcoords.size() * sizeof(GLfloat), 191 | shape.mesh.texcoords.data(), GL_STATIC_DRAW); 192 | 193 | glGenBuffers(1, &index_buffer); 194 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer); 195 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape.mesh.indices.size() * sizeof(GLint), 196 | shape.mesh.indices.data(), GL_STATIC_DRAW); 197 | 198 | indices = shape.mesh.indices.size(); 199 | 200 | } 201 | 202 | void ShapeModel::bind() { 203 | glBindBuffer(GL_ARRAY_BUFFER, position_buffer); 204 | glEnableVertexAttribArray(position_attr); 205 | glVertexAttribPointer(position_attr, 3, GL_FLOAT, 206 | GL_FALSE, 0, 0); 207 | 208 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer); 209 | glEnableVertexAttribArray(normal_attr); 210 | glVertexAttribPointer(normal_attr, 3, GL_FLOAT, 211 | GL_FALSE, 0, 0); 212 | 213 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer); 214 | glEnableVertexAttribArray(uv_attr); 215 | glVertexAttribPointer(uv_attr, 2, GL_FLOAT, 216 | GL_FALSE, 0, 0); 217 | 218 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer); 219 | } 220 | 221 | int ShapeModel::vertices() { 222 | return indices; 223 | } 224 | 225 | bool ShapeModel::is_indexed() { 226 | return true; 227 | } 228 | 229 | ShapeModel::~ShapeModel() { 230 | glDeleteBuffers(1, &position_buffer); 231 | glDeleteBuffers(1, &normal_buffer); 232 | glDeleteBuffers(1, &uv_buffer); 233 | glDeleteBuffers(1, &index_buffer); 234 | } 235 | }; 236 | -------------------------------------------------------------------------------- /lib/src/player.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "player.h" 3 | #include "matrix.h" 4 | #include "math.h" 5 | 6 | namespace konstructs { 7 | 8 | using namespace Eigen; 9 | using nonstd::nullopt; 10 | 11 | static float CAMERA_OFFSET = 0.5f; 12 | static Vector3f CAMERA_OFFSET_VECTOR = Vector3f(0, CAMERA_OFFSET, 0); 13 | 14 | static bool block_is_obstacle(const optional &block, const BlockTypeInfo &blocks) { 15 | return block && blocks.is_obstacle[(*block).type]; 16 | } 17 | 18 | Player::Player(const int id, const Vector3f position, const float rx, 19 | const float ry): 20 | id(id), position(position), mrx(rx), mry(ry), flying(false), dy(0) {} 21 | 22 | Matrix4f Player::direction() const { 23 | return (Affine3f(AngleAxisf(mrx, Vector3f::UnitX())) * 24 | Affine3f(AngleAxisf(mry, Vector3f::UnitY()))).matrix(); 25 | } 26 | 27 | Matrix4f Player::translation() const { 28 | return (Affine3f(Translation3f(position)) * 29 | Affine3f(AngleAxisf(-mry, Vector3f::UnitY())) * 30 | Affine3f(AngleAxisf(-mrx, Vector3f::UnitX()))).matrix(); 31 | } 32 | 33 | Matrix4f Player::view() const { 34 | return (Affine3f(AngleAxisf(mrx, Vector3f::UnitX())) * 35 | Affine3f(AngleAxisf(mry, Vector3f::UnitY())) * 36 | Affine3f(Translation3f(-camera()))).matrix(); 37 | } 38 | 39 | Vector3f Player::camera() const { 40 | return position + CAMERA_OFFSET_VECTOR; 41 | } 42 | 43 | Vector3f Player::camera_direction() const { 44 | float m = cosf(mrx); 45 | Vector3f vec(cosf(mry - (M_PI / 2.0f)) * m, -sinf(mrx), sinf(mry - (M_PI / 2.0f)) * m); 46 | vec.normalize(); 47 | return vec; 48 | } 49 | 50 | Vector3i Player::feet() const { 51 | return Vector3i(roundf(position[0]), roundf(position[1]) - 1, roundf(position[2])); 52 | } 53 | 54 | bool Player::can_place(Vector3i block, const World &world, const BlockTypeInfo &blocks) { 55 | Vector3i f = feet(); 56 | /* Are we trying to place blocks on ourselves? */ 57 | if(block(0) == f(0) && block(2) == f(2) && block(1) >= f(1) && block(1) < f(1) + 2) { 58 | /* We may place on our feet under certain circumstances */ 59 | if(f(1) == block(1)) { 60 | /* Allow placing on our feet if the block above our head is not an obstacle*/ 61 | return !block_is_obstacle(world.get_block(Vector3i(f(0), f(1) + 2, f(2))), blocks); 62 | } else { 63 | /* We are never allowed to place on our head */ 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | 70 | Vector3f Player::update_position(int sz, int sx, float dt, 71 | const World &world, const BlockTypeInfo &blocks, 72 | const float near_distance, const bool jump, 73 | const bool sneaking) { 74 | optional chunk_opt = world.chunk_by_block(position); 75 | 76 | if(chunk_opt) { // Only update position if the chunk we are in is loaded 77 | float vx = 0, vy = 0, vz = 0; 78 | if (!sz && !sx) { // Not mowing in X or Z 79 | vx = 0; 80 | vz = 0; 81 | } else { // Moving in X or Z 82 | 83 | 84 | float strafe = atan2f(sz, sx); 85 | 86 | if (flying) { 87 | float m = cosf(mrx); 88 | float y = sinf(mrx); 89 | if (sx) { 90 | if (!sz) { 91 | y = 0; 92 | } 93 | m = 1; 94 | } 95 | if (sz < 0) { 96 | y = -y; 97 | } 98 | vx = cosf(mry + strafe) * m; 99 | vy = y; 100 | vz = sinf(mry + strafe) * m; 101 | } else { 102 | vx = cosf(mry + strafe); 103 | vy = 0; 104 | vz = sinf(mry + strafe); 105 | } 106 | } 107 | 108 | if(jump) { 109 | if(flying) { 110 | // Jump in flight moves upward at constant speed 111 | vy = 1; 112 | } else if(dy == 0) { 113 | // Jump when walking changes the acceleration upwards to 8 114 | dy = 8; 115 | } else { 116 | // Get middle of block 117 | Vector3i iPos((int)(position[0] + 0.5f), (int)(position[1]), (int)(position[2] + 0.5f)); 118 | auto chunk = world.chunk_by_block(iPos); 119 | 120 | if(chunk && blocks.state[chunk->get(iPos).type] == STATE_LIQUID) { 121 | dy = 5.5; 122 | } 123 | } 124 | } 125 | 126 | float speed = flying ? 20 : 5; 127 | int estimate = 128 | roundf(sqrtf(powf(vx * speed, 2) + 129 | powf(vy * speed + std::abs(dy) * 2, 2) + 130 | powf(vz * speed, 2)) * dt * 8); 131 | int step = std::max(8, estimate); 132 | float ut = dt / step; 133 | vx = vx * ut * speed; 134 | vy = vy * ut * speed; 135 | vz = vz * ut * speed; 136 | for (int i = 0; i < step; i++) { 137 | if (flying) { 138 | // When flying upwards acceleration is constant i.e. not falling 139 | dy = 0; 140 | } else { 141 | // Calculate "gravity" by decreasing upwards acceleration 142 | dy -= ut * 25; 143 | dy = std::max(dy, -250.0f); 144 | } 145 | position += Vector3f(vx, vy + dy * ut, vz); 146 | if (collide(world, blocks, near_distance, sneaking)) { 147 | dy = 0; 148 | } 149 | } 150 | if (position[1] < 0) { 151 | position[1] = 2; 152 | } 153 | } 154 | return position; 155 | } 156 | 157 | optional> Player::looking_at(const World &world, 158 | const BlockTypeInfo &blocks) const { 159 | optional> block(nullopt); 160 | float best = 0; 161 | const Vector3f v = camera_direction(); 162 | const Vector3f camera_position = camera(); 163 | int p = chunked(camera_position[0]); 164 | int q = chunked(camera_position[2]); 165 | int k = chunked(camera_position[1]); 166 | const auto &atAndAround = world.atAndAround({p, q, k}); 167 | for (const auto &chunk: atAndAround) { 168 | const auto &seen = chunk.get(camera_position, v, 8.0f, blocks); 169 | if (seen) { 170 | auto h = seen->second; 171 | float d = sqrtf(powf(h.position[0] - camera_position[0], 2) + 172 | powf(h.position[1] - camera_position[1], 2) + 173 | powf(h.position[2] - camera_position[2], 2)); 174 | if (best == 0 || d < best) { 175 | best = d; 176 | block = seen; 177 | } 178 | } 179 | } 180 | return block; 181 | } 182 | 183 | void Player::rotate_x(float speed) { 184 | mrx += speed; 185 | mrx = std::max(mrx, -((float)M_PI / 2.0f)); 186 | mrx = std::min(mrx, ((float)M_PI / 2.0f)); 187 | } 188 | 189 | void Player::rotate_y(float speed) { 190 | mry += speed; 191 | if (mry < 0) { 192 | mry += (M_PI * 2); 193 | } 194 | if (mry >= (M_PI * 2)) { 195 | mry -= (M_PI * 2); 196 | } 197 | } 198 | 199 | void Player::fly() { 200 | flying = !flying; 201 | } 202 | 203 | float Player::rx() { 204 | return mrx; 205 | } 206 | 207 | float Player::ry() { 208 | return mry; 209 | } 210 | 211 | int Player::collide(const World &world, const BlockTypeInfo &blocks, 212 | const float near_distance, const bool sneaking) { 213 | int result = 0; 214 | float x = position[0]; 215 | float y = position[1]; 216 | float z = position[2]; 217 | int height = 2; 218 | int p = chunked(x); 219 | int q = chunked(z); 220 | int k = chunked(y); 221 | int nx = roundf(x); 222 | int ny = roundf(y); 223 | int nz = roundf(z); 224 | float px = x - nx; 225 | float py = y - ny; 226 | float pz = z - nz; 227 | float pad = near_distance * 2; 228 | 229 | try { 230 | 231 | if (block_is_obstacle(world.get_block(feet()), blocks)) { 232 | position[1] += 1.0f; 233 | return 1; 234 | } 235 | 236 | if(sneaking) { 237 | if (px < -pad && !block_is_obstacle(world.get_block(Vector3i(nx - 1, ny - 2, nz)), blocks)) { 238 | position[0] = nx - pad; 239 | } 240 | if (px > pad && !block_is_obstacle(world.get_block(Vector3i(nx + 1, ny - 2, nz)), blocks)) { 241 | position[0] = nx + pad; 242 | } 243 | if (pz < -pad && !block_is_obstacle(world.get_block(Vector3i(nx, ny - 2, nz - 1)), blocks)) { 244 | position[2] = nz - pad; 245 | } 246 | if (pz > pad && !block_is_obstacle(world.get_block(Vector3i(nx, ny - 2, nz + 1)), blocks)) { 247 | position[2] = nz + pad; 248 | } 249 | } 250 | for (int dy = 0; dy < height; dy++) { 251 | if (px < -pad && block_is_obstacle(world.get_block(Vector3i(nx - 1, ny - dy, nz)), blocks)) { 252 | position[0] = nx - pad; 253 | } 254 | if (px > pad && block_is_obstacle(world.get_block(Vector3i(nx + 1, ny - dy, nz)), blocks)) { 255 | position[0] = nx + pad; 256 | } 257 | if (py < -pad && block_is_obstacle(world.get_block(Vector3i(nx, ny - dy - 1, nz)), blocks)) { 258 | position[1] = ny - pad; 259 | result = 1; 260 | } 261 | if (py > (pad - CAMERA_OFFSET) && block_is_obstacle(world.get_block(Vector3i(nx, ny - dy + 1, nz)), blocks)) { 262 | position[1] = ny + pad - CAMERA_OFFSET; 263 | result = 1; 264 | } 265 | if (pz < -pad && block_is_obstacle(world.get_block(Vector3i(nx, ny - dy, nz - 1)), blocks)) { 266 | position[2] = nz - pad; 267 | } 268 | if (pz > pad && block_is_obstacle(world.get_block(Vector3i(nx, ny - dy, nz + 1)), blocks)) { 269 | position[2] = nz + pad; 270 | } 271 | } 272 | } catch(std::out_of_range e) { 273 | /* chunk was not loaded yet */ 274 | return result; 275 | } 276 | return result; 277 | } 278 | }; 279 | -------------------------------------------------------------------------------- /lib/src/hud_shader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "matrix.h" 4 | #include "hud.h" 5 | #include "cube.h" 6 | #include "hud_shader.h" 7 | 8 | namespace konstructs { 9 | using matrix::projection_2d; 10 | using std::vector; 11 | 12 | Matrix4f hud_translation_matrix(const float scale, const float xscale, 13 | const float screen_area); 14 | 15 | Vector4f hud_offset_vector(const float xscale, const float screen_area); 16 | 17 | void make_block(int type, float x, float y, float z, float size, 18 | float rx, float ry, float rz, float *d, 19 | const BlockTypeInfo &blocks); 20 | 21 | void make_stacks(const std::unordered_map> &stacks, 22 | float *d, 23 | const float rx, 24 | const float ry, 25 | const float rz, 26 | const BlockTypeInfo &blocks); 27 | 28 | void make_stack_amounts(const std::unordered_map> &stacks, float *d); 29 | 30 | vector make_square(const std::unordered_map> &background); 31 | 32 | vector make_health_bars(const std::unordered_map> &stacks); 33 | 34 | BaseModel::BaseModel(const GLuint position_attr, const GLuint normal_attr, 35 | const GLuint uv_attr) : 36 | position_attr(position_attr), normal_attr(normal_attr), 37 | uv_attr(uv_attr) {} 38 | 39 | void BaseModel::bind() { 40 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 41 | glEnableVertexAttribArray(position_attr); 42 | glEnableVertexAttribArray(normal_attr); 43 | glEnableVertexAttribArray(uv_attr); 44 | glVertexAttribPointer(position_attr, 3, GL_FLOAT, GL_FALSE, 45 | sizeof(GLfloat) * 10, 0); 46 | glVertexAttribPointer(normal_attr, 3, GL_FLOAT, GL_FALSE, 47 | sizeof(GLfloat) * 10, (GLvoid *)(sizeof(GLfloat) * 3)); 48 | glVertexAttribPointer(uv_attr, 4, GL_FLOAT, GL_FALSE, 49 | sizeof(GLfloat) * 10, (GLvoid *)(sizeof(GLfloat) * 6)); 50 | } 51 | 52 | int BaseModel::vertices() { 53 | return verts; 54 | } 55 | 56 | ItemStackModel::ItemStackModel(const GLuint position_attr, const GLuint normal_attr, 57 | const GLuint uv_attr, 58 | const std::unordered_map> &stacks, 59 | const BlockTypeInfo &blocks) : 60 | BaseModel(position_attr, normal_attr, uv_attr) { 61 | 62 | verts = 0; 63 | for (const auto &pair: stacks) { 64 | if(blocks.is_plant[pair.second.type]) { 65 | verts += 6; 66 | } else { 67 | verts += (6 * 6); 68 | } 69 | } 70 | 71 | float *data = new float[verts * 10]; 72 | make_stacks(stacks, data, - M_PI / 8, M_PI / 8, 0, blocks); 73 | 74 | glGenBuffers(1, &buffer); 75 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 76 | glBufferData(GL_ARRAY_BUFFER, verts * 10 * sizeof(GLfloat), 77 | data, GL_STATIC_DRAW); 78 | delete[] data; 79 | } 80 | 81 | AmountModel::AmountModel(const GLuint position_attr, const GLuint normal_attr, 82 | const GLuint uv_attr, 83 | const std::unordered_map> &stacks): 84 | BaseModel(position_attr, normal_attr, uv_attr) { 85 | int total_text_length = 0; 86 | for (const auto &pair: stacks) { 87 | if(pair.second.amount == 0) { 88 | continue; 89 | } 90 | if(pair.second.amount > 9) { 91 | total_text_length += 2; 92 | } else { 93 | total_text_length ++; 94 | } 95 | } 96 | float *data = new float[total_text_length * 10 * 6]; 97 | 98 | make_stack_amounts(stacks, data); 99 | 100 | glGenBuffers(1, &buffer); 101 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 102 | glBufferData(GL_ARRAY_BUFFER, total_text_length * 10 * 6 * sizeof(GLfloat), 103 | data, GL_STATIC_DRAW); 104 | verts = total_text_length * 6; 105 | delete[] data; 106 | } 107 | 108 | HudModel::HudModel(const std::unordered_map> &background, 109 | const GLuint position_attr, const GLuint normal_attr, 110 | const GLuint uv_attr) : 111 | BaseModel(position_attr, normal_attr, uv_attr) { 112 | 113 | auto data = make_square(background); 114 | 115 | glGenBuffers(1, &buffer); 116 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 117 | glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(GLfloat), 118 | data.data(), GL_STATIC_DRAW); 119 | verts = data.size() / 10; 120 | } 121 | 122 | HealthBarModel::HealthBarModel(const std::unordered_map> &stacks, 123 | const GLuint position_attr, const GLuint normal_attr, 124 | const GLuint uv_attr) : 125 | BaseModel(position_attr, normal_attr, uv_attr) { 126 | 127 | auto data = make_health_bars(stacks); 128 | 129 | glGenBuffers(1, &buffer); 130 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 131 | glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(GLfloat), 132 | data.data(), GL_STATIC_DRAW); 133 | verts = data.size() / 10; 134 | } 135 | 136 | BlockModel::BlockModel(const GLuint position_attr, const GLuint normal_attr, 137 | const GLuint uv_attr, 138 | const int type, const float x, const float y, 139 | const float size, 140 | const BlockTypeInfo &blocks) : 141 | BaseModel(position_attr, normal_attr, uv_attr) { 142 | verts = blocks.is_plant[type] ? 6 : 6 * 6; 143 | float *data = new float[verts * 10]; 144 | make_block(type, x, y, 0.0, size, - M_PI / 8, M_PI / 8, M_PI / 32, data, blocks); 145 | 146 | glGenBuffers(1, &buffer); 147 | glBindBuffer(GL_ARRAY_BUFFER, buffer); 148 | glBufferData(GL_ARRAY_BUFFER, verts * 10 * sizeof(GLfloat), 149 | data, GL_STATIC_DRAW); 150 | delete[] data; 151 | } 152 | 153 | HudShader::HudShader(const int columns, const int rows, const GLuint texture, 154 | const GLuint block_texture, const GLuint font_texture, 155 | const GLuint health_bar_texture) : 156 | ShaderProgram( 157 | "hud", 158 | 159 | "#version 330\n" 160 | "uniform vec4 offset;\n" 161 | "uniform mat4 matrix;\n" 162 | "in vec3 position;\n" 163 | "in vec3 normal;\n" 164 | "in vec4 uv;\n" 165 | "out vec2 fragment_uv;\n" 166 | "out float diffuse;\n" 167 | "const vec3 light_direction = normalize(vec3(1.0, 0.5, 0.0));\n" 168 | "void main() {\n" 169 | " fragment_uv = uv.xy;\n" 170 | " diffuse = max(0.0, dot(normal, light_direction));\n" 171 | " gl_Position = vec4(position, 1.0) * matrix + offset;\n" 172 | "}\n", 173 | "#version 330\n" 174 | "uniform sampler2D sampler;\n" 175 | "in vec2 fragment_uv;\n" 176 | "in float diffuse;\n" 177 | "out vec4 fragColor;\n" 178 | "const vec3 light_color = vec3(1.0, 1.0, 1.0);\n" 179 | "void main() {\n" 180 | " vec4 color = vec4(texture(sampler, fragment_uv));\n" 181 | " if (color.xyz == vec3(1.0, 0.0, 1.0)) {\n" 182 | " discard;\n" 183 | " }\n" 184 | " float fr_a = max(color.a, 0.7f);\n" 185 | " fragColor = vec4(mix(color.xyz, light_color * diffuse, 0.2), fr_a);\n" 186 | "}\n"), 187 | position(attributeId("position")), 188 | normal(attributeId("normal")), 189 | uv(attributeId("uv")), 190 | offset(uniformId("offset")), 191 | matrix(uniformId("matrix")), 192 | sampler(uniformId("sampler")), 193 | texture(texture), 194 | block_texture(block_texture), 195 | font_texture(font_texture), 196 | health_bar_texture(health_bar_texture), 197 | columns(columns), 198 | rows(rows), 199 | screen_area(0.6) {} 200 | 201 | optional HudShader::clicked_at(const double x, const double y, 202 | const int width, const int height) { 203 | // Convert to Open GL coordinates (-1 to 1) and inverted y 204 | double glx = (x / (double)width) * 2.0 - 1.0; 205 | double gly = (((double)height - y) / (double)height) * 2.0 - 1.0; 206 | 207 | double scale = 4.0/(double)columns; 208 | double xscale = (double)height / (double)width; 209 | 210 | // Convert to inventory positions 211 | double ix = (glx + 2.0 * xscale * screen_area) / (scale * xscale * screen_area); 212 | double iy = (gly + 1.0) / (scale * screen_area); 213 | 214 | // Return position if it is within bounds 215 | if(ix >= 0.0 && ix < (double)columns && iy >= 0.0 && iy < (double)rows) { 216 | return optional({(int)ix, (int)iy}); 217 | } else { 218 | return nullopt; 219 | } 220 | } 221 | 222 | void HudShader::render(const int width, const int height, 223 | const float mouse_x, const float mouse_y, 224 | const Hud &hud, 225 | const BlockTypeInfo &blocks) { 226 | bind([&](Context c) { 227 | float scale = 4.0f/(float)columns; 228 | float xscale = (float)height / (float)width; 229 | 230 | c.blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 231 | 232 | /* Set up for 17 x 14 HUD grid */ 233 | c.set(matrix, hud_translation_matrix(scale, xscale, screen_area)); 234 | c.set(offset, hud_offset_vector(xscale, screen_area)); 235 | 236 | /* Use background texture*/ 237 | c.set(sampler, texture); 238 | 239 | /* Generate and draw background model */ 240 | 241 | HudModel hm(hud.backgrounds(), position, normal, uv); 242 | c.enable(GL_BLEND); 243 | c.draw(hm); 244 | c.disable(GL_BLEND); 245 | 246 | c.enable(GL_DEPTH_TEST); 247 | c.enable(GL_CULL_FACE); 248 | 249 | /* Use block texture */ 250 | c.set(sampler, block_texture); 251 | /* Generate and draw item stacks */ 252 | ItemStackModel ism(position, normal, uv, hud.stacks(), blocks); 253 | c.draw(ism); 254 | 255 | /* Check for held block*/ 256 | auto held = hud.held(); 257 | if(held && hud.get_interactive()) { 258 | 259 | /* Calculate mouse position on screen as gl coordinates */ 260 | float x = (mouse_x / (float)width) * 2.0f - 1.0f; 261 | float y = (((float)height - mouse_y) / (float)height) * 2.0f - 1.0f; 262 | 263 | /* Set up for drawing on whole screen */ 264 | Matrix4f m = Matrix4f::Identity(); 265 | /* This scales items drawn so that they are kept "square" */ 266 | m(0) = xscale; 267 | Vector4f v = Vector4f::Zero(); 268 | c.set(matrix, m); 269 | c.set(offset, v); 270 | /* Use block textures */ 271 | c.set(sampler, block_texture); 272 | /* Generate a single block model */ 273 | BlockModel bm(position, normal, uv, held->type, x / xscale, y, 274 | scale * xscale * screen_area * 0.55, blocks); 275 | glClear(GL_DEPTH_BUFFER_BIT); 276 | c.draw(bm); 277 | } 278 | c.disable(GL_CULL_FACE); 279 | c.disable(GL_DEPTH_TEST); 280 | 281 | /* Set up for 17 x 14 HUD grid */ 282 | c.set(matrix, hud_translation_matrix(scale, xscale, screen_area)); 283 | c.set(offset, hud_offset_vector(xscale, screen_area)); 284 | 285 | 286 | /* Use health bar texture */ 287 | c.set(sampler, health_bar_texture); 288 | 289 | /* Generate and draw health bars */ 290 | HealthBarModel hbm(hud.stacks(), position, normal, uv); 291 | c.draw(hbm); 292 | 293 | /* Use font texture */ 294 | c.set(sampler, font_texture); 295 | 296 | /* Generate and draw item stack amounts */ 297 | AmountModel am(position, normal, uv, hud.stacks()); 298 | c.enable(GL_BLEND); 299 | c.draw(am); 300 | c.disable(GL_BLEND); 301 | 302 | }); 303 | } 304 | 305 | Matrix4f hud_translation_matrix(const float scale, const float xscale, 306 | const float screen_area) { 307 | Matrix4f m; 308 | m.col(0) << scale * screen_area * xscale, 0.0f, 0.0f, 0.0f; 309 | m.col(1) << 0.0f, scale * screen_area, 0.0f, 0.0f; 310 | m.col(2) << 0.0f, 0.0f, 1.0f, 0.0f; 311 | m.col(3) << 0.0f, 0.0f, 0.0f, 1.0f; 312 | return m; 313 | } 314 | 315 | Vector4f hud_offset_vector(const float xscale, const float screen_area) { 316 | return Vector4f(-2*xscale*screen_area, -1.0f, 0.0f, 0.0f); 317 | } 318 | 319 | vector make_square(const std::unordered_map> &background) { 320 | vector m; 321 | float ts = 0.25; 322 | for(auto pair: background) { 323 | int i = pair.first[0]; 324 | int j = pair.first[1]; 325 | int t = pair.second; 326 | if(t >= 0) { 327 | m.push_back(-0.0f+i); m.push_back(1.0f+j); m.push_back(0.0f); 328 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 329 | m.push_back(t*ts); m.push_back(1.0f); 330 | m.push_back(0.0f); m.push_back(0.0f); 331 | 332 | m.push_back(1.0f+i); m.push_back(1.0f+j); m.push_back(0.0f); 333 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 334 | m.push_back(t*ts + ts); m.push_back(1.0f); 335 | m.push_back(0.0f); m.push_back(0.0f); 336 | 337 | m.push_back(-0.0f+i); m.push_back(-0.0f+j); m.push_back(0.0f); 338 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 339 | m.push_back(t*ts); m.push_back(0.0f); 340 | m.push_back(0.0f); m.push_back(0.0f); 341 | 342 | m.push_back(-0.0f+i); m.push_back(-0.0f+j); m.push_back(0.0f); 343 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 344 | m.push_back(t*ts); m.push_back(0.0f); 345 | m.push_back(0.0f); m.push_back(0.0f); 346 | 347 | m.push_back(1.0f+i); m.push_back(1.0f+j); m.push_back(0.0f); 348 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 349 | m.push_back(t*ts + ts); m.push_back(1.0f); 350 | m.push_back(0.0f); m.push_back(0.0f); 351 | 352 | m.push_back(1.0f+i); m.push_back(-0.0f+j); m.push_back(0.0f); 353 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 354 | m.push_back(t*ts + ts); m.push_back(0.0f); 355 | m.push_back(0.0f); m.push_back(0.0f); 356 | } 357 | } 358 | return m; 359 | } 360 | 361 | void make_block(int type, float x, float y, float z, float size, 362 | float rx, float ry, float rz, float *d, 363 | const BlockTypeInfo &blocks) { 364 | char ao[6][4] = {0}; 365 | if(blocks.is_plant[type]) { 366 | make_rotated_cube(d, ao, 367 | 0, 0, 0, 0, 0, 1, 368 | x, y, z, size, 0, 0, 0, 369 | type, blocks.blocks); 370 | } else { 371 | make_rotated_cube(d, ao, 372 | 1, 1, 1, 1, 1, 1, 373 | x, y, z, size, rx, ry, rz, 374 | type, blocks.blocks); 375 | } 376 | } 377 | 378 | void make_stacks(const std::unordered_map> &stacks, 379 | float *d, 380 | const float rx, 381 | const float ry, 382 | const float rz, 383 | const BlockTypeInfo &blocks) { 384 | 385 | int offset = 0; 386 | for (const auto &pair: stacks) { 387 | make_block(pair.second.type, pair.first[0] + 0.5, pair.first[1] + 0.45, 0, 0.3, 388 | rx, ry, rz, d + offset, blocks); 389 | offset += blocks.is_plant[pair.second.type] ? 10 * 6 : 10 * 6 * 6; 390 | } 391 | } 392 | 393 | vector make_health_bars(const std::unordered_map> &stacks) { 394 | 395 | vector m; 396 | float ts = 0.125f; 397 | for (const auto &pair: stacks) { 398 | if(pair.second.amount == 0) { 399 | continue; 400 | } 401 | int i = pair.first[0]; 402 | int j = pair.first[1]; 403 | float offset = 0.11f; 404 | float h = (float)pair.second.health / (float)(MAX_HEALTH + 1); 405 | int t = (int)(h * 8.0f); 406 | float health = h * (1.0f - offset * 2.0f); 407 | float bar_bottom = 0.1f; 408 | float bar_height = bar_bottom + 0.06f; 409 | 410 | m.push_back(offset+i); m.push_back(bar_height+j); m.push_back(0.0f); 411 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 412 | m.push_back(t*ts); m.push_back(1.0f); 413 | m.push_back(0.0f); m.push_back(0.0f); 414 | 415 | m.push_back(offset+health+i); m.push_back(bar_height+j); m.push_back(0.0f); 416 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 417 | m.push_back(t*ts + ts); m.push_back(1.0f); 418 | m.push_back(0.0f); m.push_back(0.0f); 419 | 420 | m.push_back(offset+i); m.push_back(bar_bottom+j); m.push_back(0.0f); 421 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 422 | m.push_back(t*ts); m.push_back(0.0f); 423 | m.push_back(0.0f); m.push_back(0.0f); 424 | 425 | m.push_back(offset+i); m.push_back(bar_bottom+j); m.push_back(0.0f); 426 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 427 | m.push_back(t*ts); m.push_back(0.0f); 428 | m.push_back(0.0f); m.push_back(0.0f); 429 | 430 | m.push_back(offset+health+i); m.push_back(bar_height+j); m.push_back(0.0f); 431 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 432 | m.push_back(t*ts + ts); m.push_back(1.0f); 433 | m.push_back(0.0f); m.push_back(0.0f); 434 | 435 | m.push_back(offset+health+i); m.push_back(bar_bottom+j); m.push_back(0.0f); 436 | m.push_back(0.0f); m.push_back(1.0f); m.push_back(0.0f); 437 | m.push_back(t*ts + ts); m.push_back(0.0f); 438 | m.push_back(0.0f); m.push_back(0.0f); 439 | } 440 | return m; 441 | } 442 | 443 | void make_stack_amounts(const std::unordered_map> &stacks, 444 | float *d) { 445 | int i = 0; 446 | 447 | for (const auto &pair: stacks) { 448 | if(pair.second.amount == 0) { 449 | continue; 450 | } 451 | std::string text = std::to_string(pair.second.amount); 452 | for (int index = 0; index < text.length(); index++) { 453 | int offset = text.length() - index - 1; 454 | make_character(d + i * 10 * 6, pair.first[0] - (float)offset*0.2 + 0.75f, pair.first[1] + 0.25f, 0.1, 0.2, text[index], 455 | 0.0); 456 | i++; 457 | } 458 | } 459 | } 460 | 461 | }; 462 | -------------------------------------------------------------------------------- /lib/src/client.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #include 4 | #define close closesocket 5 | #define sleep Sleep 6 | #else 7 | #include 8 | #include 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "client.h" 20 | 21 | #define PROTOCOL_VERSION 10 22 | #define MAX_RECV_SIZE 4096*1024 23 | #define PACKETS (MAX_PENDING_CHUNKS * 2) 24 | #define HEADER_SIZE 4 25 | 26 | namespace konstructs { 27 | using nonstd::nullopt; 28 | 29 | const int NO_CHUNK_FOUND = 0x0FFFFFFF; 30 | 31 | Client::Client(bool debug_mode) : 32 | connected(false), debug_mode(debug_mode), 33 | player_chunk(0,0,0), radius(0), loaded_radius(0) { 34 | recv_thread = new std::thread(&Client::recv_worker, this); 35 | send_thread = new std::thread(&Client::send_worker, this); 36 | chunk_thread = new std::thread(&Client::chunk_worker, this); 37 | inflation_buffer = new char[BLOCK_BUFFER_SIZE]; 38 | } 39 | 40 | string Client::get_error_message() { 41 | return error_message; 42 | } 43 | 44 | void Client::open_connection(Settings::Server server) { 45 | error_message = ""; 46 | struct hostent *host; 47 | struct sockaddr_in address; 48 | if ((host = gethostbyname(server.address.c_str())) == 0) { 49 | #ifdef _WIN32 50 | std::cerr << "WSAGetLastError: " << WSAGetLastError() << std::endl; 51 | #endif 52 | SHOWERROR("gethostbyname"); 53 | error_message = "Could not find server: " + server.address; 54 | throw std::runtime_error(error_message); 55 | } 56 | memset(&address, 0, sizeof(address)); 57 | address.sin_family = AF_INET; 58 | address.sin_addr.s_addr = ((struct in_addr *)(host->h_addr_list[0]))->s_addr; 59 | address.sin_port = htons(server.port); 60 | if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 61 | SHOWERROR("socket"); 62 | error_message = "Failed to create socket"; 63 | throw std::runtime_error(error_message); 64 | } 65 | if (connect(sock, (struct sockaddr *)&address, sizeof(address)) == -1) { 66 | SHOWERROR("connect"); 67 | error_message = "Could not connect to server"; 68 | throw std::runtime_error(error_message); 69 | } 70 | version(PROTOCOL_VERSION, server.username, server.password); 71 | } 72 | 73 | size_t Client::recv_all(char* out_buf, const size_t size) { 74 | int t = 0; 75 | int length = 0; 76 | while(t < size) { 77 | if ((length = recv(sock, out_buf + t, size - t, 0)) <= 0) { 78 | #ifdef _WIN32 79 | if(WSAGetLastError() == WSAEINTR) { 80 | continue; 81 | } 82 | #else 83 | if(errno == EINTR) { 84 | continue; 85 | } 86 | #endif 87 | SHOWERROR("recv"); 88 | throw std::runtime_error("Failed to receive"); 89 | } 90 | t += length; 91 | } 92 | return t; 93 | } 94 | 95 | void Client::process_chunk_updated(Packet *packet) { 96 | std::string str = packet->to_string(); 97 | int p,q,k; 98 | if(sscanf(str.c_str(), ",%d,%d,%d", &p, &q, &k) != 3) { 99 | throw std::runtime_error(str); 100 | } 101 | Vector3i pos(p, q, k); 102 | chunk_updated(pos); 103 | } 104 | 105 | 106 | void Client::process_error(Packet *packet) { 107 | error_message = packet->to_string().substr(1); 108 | force_close(); 109 | } 110 | 111 | void Client::process_chunk(Packet *packet) { 112 | int p, q, k; 113 | char *pos = packet->buffer(); 114 | 115 | p = ntohl(*((int*)pos)); 116 | pos += sizeof(int); 117 | 118 | q = ntohl(*((int*)pos)); 119 | pos += sizeof(int); 120 | 121 | k = ntohl(*((int*)pos)); 122 | pos += sizeof(int); 123 | 124 | Vector3i position(p, q, k); 125 | received_chunk(position); 126 | const int blocks_size = packet->size - 3 * sizeof(int); 127 | auto chunk = ChunkData(position, pos, blocks_size, (uint8_t*)inflation_buffer, cached_data); 128 | std::lock_guard lock_packets(packets_mutex); 129 | chunks.push_back(chunk); 130 | } 131 | 132 | void Client::recv_worker() { 133 | if (debug_mode) { 134 | std::cout<<"[Recv worker]: started"< ulock_connected(mutex_connected); 143 | cv_connected.wait(ulock_connected, [&] { return connected; }); 144 | ulock_connected.unlock(); 145 | if (debug_mode) { 146 | std::cout<<"[Recv worker]: connection established, entering main loop"< MAX_RECV_SIZE) { 156 | std::cerr << "package too large, received " << size << " bytes" << std::endl; 157 | throw std::runtime_error("Packet too large"); 158 | } 159 | 160 | char type; 161 | // read 'size' bytes from the network 162 | recv_all(&type, sizeof(char)); 163 | 164 | // Remove one byte type header' 165 | size = size - 1; 166 | auto packet = make_shared(type,size); 167 | // read 'size' bytes from the network 168 | int r = recv_all(packet->buffer(), packet->size); 169 | // move data over to packet_buffer 170 | if(packet->type == 'C') { 171 | process_chunk(packet.get()); 172 | } else if(packet->type == 'E') { 173 | process_error(packet.get()); 174 | } else if(packet->type == 'c') { 175 | process_chunk_updated(packet.get()); 176 | } else { 177 | std::lock_guard lock_packets(packets_mutex); 178 | packets.push(packet); 179 | } 180 | } 181 | } catch(const std::exception& ex) { 182 | std::cout << "[Recv worker]: Caught exception: " << ex.what() << std::endl; 183 | std::cout << "[Recv worker]: Will assume connection is down: " << ex.what() << std::endl; 184 | error_message = "Disconnected from server."; 185 | force_close(); 186 | } 187 | } 188 | } 189 | 190 | vector> Client::receive(const int max) { 191 | vector> head; 192 | std::lock_guard lock_packets(packets_mutex); 193 | for(int i=0; i < max; i++) { 194 | if(packets.empty()) { 195 | break; 196 | } 197 | head.push_back(packets.front()); 198 | packets.pop(); 199 | } 200 | return head; 201 | } 202 | 203 | optional Client::receive_prio_chunk(const Vector3i pos) { 204 | std::lock_guard lock_packets(packets_mutex); 205 | for(auto it = chunks.begin(); it != chunks.end(); ++it) { 206 | auto chunk = *it; 207 | Vector3i chunk_position = chunk.position; 208 | int dp = chunk_position[0] - pos[0]; 209 | int dq = chunk_position[1] - pos[1]; 210 | int dk = chunk_position[2] - pos[2]; 211 | if(dp >= -1 && dp <= 1 && dq >= -1 && dq <= 1 && dk >= -1 && dk <= 1) { 212 | chunks.erase(it); 213 | return optional(chunk); 214 | } 215 | } 216 | return nullopt; 217 | } 218 | 219 | vector Client::receive_chunks(const int max) { 220 | std::lock_guard lock_packets(packets_mutex); 221 | int maxOrSize = std::min(max, (int)chunks.size()); 222 | auto last = chunks.begin() + maxOrSize; 223 | vector head(chunks.begin(), last); 224 | chunks.erase(chunks.begin(), last); 225 | return head; 226 | } 227 | 228 | int Client::send_all(const char *data, int length) { 229 | int count = 0; 230 | while (count < length) { 231 | int n = send(sock, data + count, length, 0); 232 | if (n == -1) { 233 | return -1; 234 | } 235 | count += n; 236 | length -= n; 237 | bytes_sent += n; 238 | } 239 | return 0; 240 | } 241 | 242 | void Client::send_string(const string &str) { 243 | { 244 | std::lock_guard lock(mutex_send); 245 | send_queue.push(str); 246 | } 247 | cv_send.notify_all(); 248 | } 249 | 250 | void Client::send_worker() { 251 | if (debug_mode) { 252 | std::cout<<"[Send worker]: started"< ulock_connected(mutex_connected); 261 | cv_connected.wait(ulock_connected, [&] { return connected; }); 262 | ulock_connected.unlock(); 263 | if (debug_mode) { 264 | std::cout<<"[Send worker]: connection established, entering main loop"< ulock_send(mutex_send); 270 | cv_send.wait(ulock_send, [&] { return send_queue.size() > 0; }); 271 | auto str = send_queue.front(); 272 | send_queue.pop(); 273 | ulock_send.unlock(); 274 | int header_size = htonl(str.size()); 275 | if (send_all((char*)&header_size, sizeof(header_size)) == -1) { 276 | SHOWERROR("client_sendall"); 277 | } 278 | if (send_all(str.c_str(), str.size()) == -1) { 279 | SHOWERROR("client_sendall"); 280 | } 281 | } 282 | } catch(const std::exception& ex) { 283 | std::cout << "[Send worker]: Caught exception: " << ex.what() << std::endl; 284 | std::cout << "[Send worker]: Will assume connection is down: " << ex.what() << std::endl; 285 | error_message = "Disconnected from server."; 286 | force_close(); 287 | } 288 | } 289 | } 290 | 291 | void Client::version(const int version, const string &nick, const string &hash) { 292 | std::stringstream ss; 293 | ss << "V," << version << "," << nick << "," << hash; 294 | send_string(ss.str()); 295 | } 296 | 297 | void Client::position(const Vector3f position, 298 | const float rx, const float ry) { 299 | std::stringstream ss; 300 | ss << "P," << position[0] << "," << position[1] << "," << position[2] << "," << rx << "," << ry; 301 | send_string(ss.str()); 302 | } 303 | 304 | void Client::click_at(const int hit, const Vector3i pos, 305 | const int button, const int active, 306 | const uint8_t direction, const uint8_t rotation) { 307 | std::stringstream ss; 308 | ss << "M," << hit << "," << pos[0] << "," << pos[1] << "," << pos[2] << 309 | "," << button << "," << active << "," << (int)direction << "," << (int)rotation; 310 | send_string(ss.str()); 311 | } 312 | 313 | void Client::chunk(const Vector3i position) { 314 | std::stringstream ss; 315 | ss << "C," << position[0] << "," << position[1] << "," << position[2]; 316 | send_string(ss.str()); 317 | } 318 | 319 | void Client::konstruct() { 320 | send_string("K"); 321 | } 322 | 323 | void Client::click_inventory(const int item, const int button) { 324 | std::stringstream ss; 325 | ss << "R," << item << "," << button; 326 | send_string(ss.str()); 327 | } 328 | 329 | void Client::close_inventory() { 330 | send_string("I"); 331 | } 332 | 333 | void Client::talk(const string &text) { 334 | std::stringstream ss; 335 | ss << "T," << text; 336 | send_string(ss.str()); 337 | } 338 | 339 | void Client::update_radius(const int radius) { 340 | std::stringstream ss; 341 | ss << "D," << radius; 342 | send_string(ss.str()); 343 | } 344 | 345 | void Client::set_connected(bool state) { 346 | std::lock_guard lck(mutex_connected); 347 | connected = state; 348 | cv_connected.notify_all(); 349 | } 350 | 351 | bool Client::is_connected() { 352 | std::lock_guard lck(mutex_connected); 353 | return connected; 354 | } 355 | 356 | void Client::set_logged_in(bool state) { 357 | std::lock_guard lck(mutex_connected); 358 | logged_in = state; 359 | cv_connected.notify_all(); 360 | } 361 | 362 | bool Client::is_logged_in() { 363 | std::lock_guard lck(mutex_connected); 364 | return logged_in; 365 | } 366 | 367 | void Client::force_close() { 368 | close(sock); 369 | set_logged_in(false); 370 | set_connected(false); 371 | } 372 | 373 | void Client::chunk_updated(const Vector3i &pos) { 374 | std::lock_guard ulck_chunk(mutex_chunk); 375 | updated_queue.push_back(pos); 376 | } 377 | 378 | 379 | void Client::set_player_chunk(const Vector3i &chunk) { 380 | std::lock_guard ulck_chunk(mutex_chunk); 381 | player_chunk = chunk; 382 | } 383 | 384 | void Client::set_radius(int r) { 385 | { 386 | std::lock_guard ulck_chunk(mutex_chunk); 387 | radius = r; 388 | } 389 | update_radius(r + KEEP_EXTRA_CHUNKS); 390 | } 391 | 392 | void Client::set_loaded_radius(int r) { 393 | std::lock_guard ulck_chunk(mutex_chunk); 394 | 395 | if (r > radius || loaded_radius > radius) { 396 | // Never set radius outside radius 397 | 398 | loaded_radius = radius; 399 | } else if (r > loaded_radius) { 400 | // The loaded radius has increased 401 | 402 | loaded_radius = r; 403 | } else { 404 | // We recevied a chunk closer then the loaded radius 405 | // and we never want to reduce the loaded radius. 406 | } 407 | } 408 | 409 | int Client::get_loaded_radius() { 410 | std::lock_guard ulck_chunk(mutex_chunk); 411 | return loaded_radius; 412 | } 413 | 414 | void Client::received_chunk(const Vector3i &pos) { 415 | std::lock_guard ulck_chunk(mutex_chunk); 416 | received_queue.push_back(pos); 417 | } 418 | 419 | /* The chunk is not received, and never requested */ 420 | bool Client::is_empty_chunk(Vector3i pos) { 421 | return received.find(pos) == received.end() && requested.find(pos) == requested.end(); 422 | } 423 | 424 | /* The chunk is requested */ 425 | bool Client::is_requested_chunk(Vector3i pos) { 426 | return requested.find(pos) != requested.end(); 427 | } 428 | 429 | /* The chunk is not requested, and has updates */ 430 | bool Client::is_updated_chunk(Vector3i pos) { 431 | return requested.find(pos) == requested.end() && updated.find(pos) != updated.end(); 432 | } 433 | 434 | /* Ask the server for a chunk, and wait a little while */ 435 | void Client::request_chunk_and_sleep(Vector3i pos, int msec) { 436 | requested.insert(pos); 437 | chunk(pos); 438 | std::this_thread::sleep_for(std::chrono::milliseconds(msec)); 439 | } 440 | 441 | void Client::chunk_worker() { 442 | if (debug_mode) { 443 | std::cout<<"[Chunk worker]: started"< ulock_connected(mutex_connected); 451 | cv_connected.wait(ulock_connected, [&] { return connected && logged_in; }); 452 | ulock_connected.unlock(); 453 | if (debug_mode) { 454 | std::cout << "[Chunk worker]: connection established and user logged in, entering main loop" 455 | << std::endl; 456 | } 457 | 458 | int r = 0; // Stores the current radius 459 | int old_r = 0; // Stores the previous radius 460 | Vector3i p_chunk; // Stores the player chunk 461 | bool chunk_changed = false; // Stores if the chunk the player is in changed 462 | // Stores the chunks that needs to be fetched in priority order 463 | priority_queue, LessThanByScore> chunks_to_fetch; 464 | 465 | while(connected && logged_in) { 466 | 467 | { 468 | // Locks the chunk mutex and makes local copies of all 469 | // shared variables and empties the updated and received queues 470 | std::lock_guard lck_chunk(mutex_chunk); 471 | 472 | // Copy all chunks from the updated queue 473 | for(auto chunk: updated_queue) { 474 | updated.insert(chunk); 475 | } 476 | 477 | // Clear the updated queue 478 | updated_queue.clear(); 479 | 480 | // Copy all chunks from the receive queue 481 | for(auto chunk: received_queue) { 482 | // Remove received chunks from requested set 483 | requested.erase(chunk); 484 | // Remove received chunks from updated set 485 | updated.erase(chunk); 486 | // Insert into received set 487 | received.insert(chunk); 488 | } 489 | 490 | // Clear received queue 491 | received_queue.clear(); 492 | 493 | // Check if player chunk changed 494 | if(p_chunk != player_chunk) { 495 | // Update local chunk variable 496 | p_chunk = player_chunk; 497 | // Set chunk_changed true 498 | chunk_changed = true; 499 | } 500 | 501 | // Updated local radius 502 | r = radius; 503 | } 504 | 505 | // Chunk changed 506 | if (chunk_changed) { 507 | 508 | // Request the surrounding chunks quickly 509 | for (int p = -1; p < 2; p++) { 510 | for (int q = -1; q < 2; q++) { 511 | for (int s = -1; s < 2; s++) { 512 | 513 | Vector3i lpos = p_chunk + Vector3i(p, q, s); 514 | if (is_empty_chunk(lpos)) { 515 | // Insert into requested set 516 | requested.insert(lpos); 517 | 518 | // Request the chunk 519 | chunk(lpos); 520 | } 521 | } 522 | } 523 | } 524 | 525 | // Empty the priority queue 526 | chunks_to_fetch = priority_queue, LessThanByScore>(); 527 | 528 | // Rebuild the priority queue 529 | for(int p = -r - 1; p < r; p++) { 530 | for(int q = -r - 1; q < r; q++) { 531 | for(int k = -r - 1; k < r; k++) { 532 | Vector3i pos = p_chunk + Vector3i(p, q, k); 533 | 534 | if(is_empty_chunk(pos)) { 535 | int distance = (pos - p_chunk).norm(); 536 | // This checks removes edges so that we request a sphere not a cube 537 | if(distance <= r) { 538 | // Add chunk to queue 539 | chunks_to_fetch.push({distance, pos}); 540 | } 541 | } 542 | } 543 | } 544 | } 545 | // Set chunk change to false, 546 | // no need to rebuild queue until the chunk changes again 547 | chunk_changed = false; 548 | } 549 | 550 | // Check if the radius increased 551 | if(r - old_r > 0) { 552 | // Extend the priority queue with new chunks due to increased radius 553 | for(int p = -r - 1; p < r; p++) { 554 | for(int q = -r - 1; q < r; q++) { 555 | for(int k = -r - 1; k < r; k++) { 556 | Vector3i pos = p_chunk + Vector3i(p, q, k); 557 | 558 | if(is_empty_chunk(pos)) { 559 | int distance = (pos - p_chunk).norm(); 560 | 561 | // This checks removes edges so that we request a sphere not a cube 562 | // It also rejects chunks that was already previously added to the queue 563 | // that is chunks within the old radius 564 | if(distance <= r && distance >= old_r) { 565 | chunks_to_fetch.push({distance, pos}); 566 | } 567 | } 568 | } 569 | } 570 | } 571 | } 572 | 573 | // Update the old radius with the new one 574 | old_r = r; 575 | 576 | // Remove old chunks in received set that are outside render distance 577 | for(auto it = received.begin(); it != received.end();) { 578 | int distance = (*it - p_chunk).norm(); 579 | if(distance >= (r + KEEP_EXTRA_CHUNKS)) { 580 | // Erase increases iterator to the next element 581 | it = received.erase(it); 582 | } else { 583 | // If we didn't erase we need to increase iterator ourselves 584 | ++it; 585 | } 586 | } 587 | 588 | // Look at the update queue and add to request queue 589 | for(auto it = updated.begin(); it != updated.end();) { 590 | Vector3i pos = *it; 591 | // Erase increases iterator 592 | it = updated.erase(it); 593 | int distance = (pos - p_chunk).norm(); 594 | 595 | // Add to chunk queue 596 | chunks_to_fetch.push({distance, pos}); 597 | 598 | // Remove from requested set, since we need a newer version 599 | requested.erase(pos); 600 | } 601 | 602 | if(!chunks_to_fetch.empty()) { 603 | ChunkToFetch c; 604 | c.score = NO_CHUNK_FOUND; 605 | 606 | // Fetch next chunk in the queue that is within radius 607 | do { 608 | c = chunks_to_fetch.top(); 609 | chunks_to_fetch.pop(); 610 | // The following check validates before exiting the loop that: 611 | // 1. The chunk is within the radius 612 | // 2. The chunk queue is empty (i.e. no chunk was found) 613 | // 3. The chunk was not already requested 614 | } while((c.chunk - p_chunk).norm() > r && 615 | !chunks_to_fetch.empty() && 616 | !is_requested_chunk(c.chunk)); 617 | 618 | // Check that at least one suitable chunk was found 619 | if(c.score != NO_CHUNK_FOUND) { 620 | // Insert into requested 621 | requested.insert(c.chunk); 622 | // Remove from updated 623 | updated.erase(c.chunk); 624 | // Request chunk 625 | chunk(c.chunk); 626 | // Update loaded radius 627 | set_loaded_radius(c.score); 628 | } 629 | } 630 | std::this_thread::sleep_for(std::chrono::milliseconds(15)); 631 | } 632 | } 633 | } 634 | }; 635 | --------------------------------------------------------------------------------