Compare commits

..

89 Commits

Author SHA1 Message Date
EmaMaker fa3d36ec58 shaders: fix rotated textures on x axis 2023-10-12 21:47:05 +02:00
EmaMaker ebef608000 blockpick: avoid blocks being placed diagonally 2023-10-12 21:47:00 +02:00
EmaMaker 0b294bb35b blockpick: cleanup blockpick place code 2023-10-09 21:55:31 +02:00
EmaMaker f3d8ffed54 blockpick: skip iteration if block has not changed 2023-10-04 23:37:04 +02:00
EmaMaker 0560fd43c6 print gpu in debug window 2023-10-04 20:58:06 +02:00
EmaMaker 62212a22ab main: do not terminate imgui, leads to crash 2023-10-04 14:59:31 +02:00
EmaMaker 357f67aac1 chunkmgr: debug info for chunkmgr 2023-10-04 14:59:17 +02:00
EmaMaker 1b4cef8958 Merge pull request 'Refactor Main/Update thread communication' (#14) from multithread-refactor into main
Reviewed-on: #14
2023-10-04 14:58:07 +02:00
EmaMaker e4c6eb50f3 Merge branch 'main' into multithread-refactor 2023-10-04 14:55:47 +02:00
EmaMaker 9b5939d256 renderer: do not send empty meshes to the gpu only at creation
I feel never sending empty meshes to the GPU is the cause of the bug causing floating quads near
chunk borders when a block is placed and then destroyed.
When destroying a block in a chunk, if nearby empty chunk meshes are not updated, the old mesh is
kept, which includes a quad at the border
2023-10-04 14:53:05 +02:00
EmaMaker 8401a3dca7 Merge branch 'main' into multithread-refactor 2023-10-04 14:51:56 +02:00
EmaMaker 255460892d blockpick control belongs to control, not chunkmgr 2023-10-04 14:41:44 +02:00
EmaMaker afdd622ec2 refactor message system between main and update threads
restores blockpicking with new multithread system
2023-10-04 14:35:29 +02:00
EmaMaker 93bc0e7066 renderer: do not send empty meshes to the gpu only at creation
I feel never sending empty meshes to the GPU is the cause of the bug causing floating quads near
chunk borders when a block is placed and then destroyed.
When destroying a block in a chunk, if nearby empty chunk meshes are not updated, the old mesh is
kept, which includes a quad at the border
2023-10-04 13:57:12 +02:00
EmaMaker 8544620899 Merge pull request 'Refactor Meshing/Rendering communication' (#13) from multithread-refactor into main
Reviewed-on: #13
2023-10-04 13:52:26 +02:00
EmaMaker a4f1e5fc1f Merge branch 'main' into multithread-refactor 2023-10-04 13:51:56 +02:00
EmaMaker 78b65894b7 chunkmesher: compiler flag to not show borders at world limit 2023-10-04 13:35:50 +02:00
EmaMaker 355da726f6 Merge pull request 'Refactor Secondary Threads' (#12) from multithread-refactor into main
Reviewed-on: #12
2023-10-04 13:32:23 +02:00
EmaMaker ba95db4678 renderer: also delete meshes of old chunks 2023-10-04 13:24:06 +02:00
EmaMaker 7c82a71dd0 chunk: vram buffers are managed by renderer now 2023-10-04 13:24:06 +02:00
EmaMaker 7786d4f04d renderer: use ChunkMeshData from rendering thread
This system decouples the Secondary threads from the Render thread. Once a chunk is meshed, only the mesh data is sent to the rendering thread, which does not use any direct reference to the chunk itself
2023-10-04 13:24:06 +02:00
EmaMaker 1822911845 chunkmesher: use goto for error handling 2023-10-04 13:24:06 +02:00
EmaMaker d0ddf2256f move chunkmeshdata into its own file
And let it be managed by chunkmesher instead of chunkmanager
2023-10-04 13:23:49 +02:00
EmaMaker 88abf21502 multithread: refactor update thread, communication between mesh/gen/upd threads
- Use parallel_for to iterate over all the stored chunks
- Only push a chunk to a queue if it is not already present, using chunk state bitfield (solves
	memory leak, continously adding new elements to a queue)
- Properly delete an element from chunks concurrent_hash_map using accessor, then free the memory
(solves memory leak: not being able to delete old elements and always adding new ones)

Breaks:

- Rendering (will be properly refactored in a future commit)
- Block picking (will be refactored in a future commit)
2023-10-04 13:10:05 +02:00
EmaMaker f4947d5f70 debugwindow: catch exception to avoid crash when missing parameters 2023-10-04 12:58:00 +02:00
EmaMaker 4e7fadd2b9 chunk: use chunk state to mark presence of chunk in thread communication queues 2023-10-04 12:57:57 +02:00
EmaMaker d1b151f92f chunk: typedef for chunk state 2023-10-04 12:57:57 +02:00
EmaMaker 44edf3e53a Merge pull request 'Misc fixes in preparation for multithreading refactor' (#10) from multithread-refactor into main
Reviewed-on: #10
2023-10-03 22:55:11 +02:00
EmaMaker 3adb061057 Merge branch 'main' into multithread-refactor 2023-10-03 22:53:42 +02:00
EmaMaker 1a50d1fb84 chunkmgr: span chunk indices in a cube around the player, not a sphere 2023-10-03 22:45:09 +02:00
EmaMaker 60bbc85682 chunk: store index in chunk itself 2023-10-03 22:45:09 +02:00
EmaMaker 490f207e39 chunk/mgr: calculate index belongs to chunk namespace 2023-10-03 22:45:09 +02:00
EmaMaker c6d00c4200 fix type mismatch in chunk index/coordinates
typedef an appropriate chunk_index_t and chunk_intcoord_t
2023-10-03 22:45:09 +02:00
EmaMaker 2a57796ed2 move input handling from main into dedicated file 2023-10-03 22:45:09 +02:00
EmaMaker ca043bac68 threads: allow for proper shutdown
using `if` instead of `while` avoids the need to wait for the queue to empty to shutdown the thread
2023-10-03 22:44:55 +02:00
EmaMaker f6f4057109 update player debug variables in main instead of chunkmgr 2023-10-03 22:09:03 +02:00
EmaMaker 353ef37186 renderer: perform frustum culling only if chunk has vertices 2023-10-03 22:08:40 +02:00
EmaMaker 880c634be0 camera: set atomic position at startup
this avoids the first few chunk update loops recognizing the camera as being positioned at (0,0,0), which in turns avoids wastefully generating chunks out of view at startup
2023-10-03 22:08:40 +02:00
EmaMaker 9ad0485a79 Merge pull request 'Adds HUD elements' (#9) from hud into main
Reviewed-on: #9
2023-09-20 12:43:59 +02:00
EmaMaker 50bc52a679 debug window: use wireframe from checkbox in rendering 2023-09-20 12:42:05 +02:00
EmaMaker 07068d740a debug window: use block type from slider in block picking 2023-09-20 12:42:05 +02:00
EmaMaker 52696f2dde debug window: use crosshair type from slider in shader 2023-09-20 12:42:05 +02:00
EmaMaker 9d2d0c8772 debug window: populate with data 2023-09-20 12:42:05 +02:00
EmaMaker f526e9b152 create debug window with imgui 2023-09-20 12:42:05 +02:00
EmaMaker e7c4b2d56b lib: add dearimgui lib 2023-09-20 12:42:05 +02:00
EmaMaker a6a419fdff hud: crosshair with fragment shader 2023-09-20 12:42:05 +02:00
EmaMaker 3f61a6a753 renderer: screenshot by saving render texture to file 2023-09-20 12:37:32 +02:00
EmaMaker 00a4b8e1e2 renderer: properly handle framebuffer resizing 2023-09-20 12:37:32 +02:00
EmaMaker 4b723d58fa libs: import stb_image_write 2023-09-20 12:37:32 +02:00
EmaMaker 1c0ee1315f renderer: render scene to a texture
And then render the texture onto a quad that fills the screen
The screen-filled quad upon which the texture is rendered must never be rendered in wireframe. Rendering in wireframe only makes sense when rendering the world on the texture
2023-09-20 12:37:32 +02:00
EmaMaker 6670f3b41c shader: make geometry shader not mandatory 2023-09-20 12:37:32 +02:00
EmaMaker 41275486a6 blockpicking: eventually mesh nearby chunks when destroying a block
this prevents stray floating quads when destroy a block near the chunk border
2023-08-12 08:44:46 +02:00
EmaMaker 3d0d8b7593 chunkmgr: chunk can be meshed if all neighbors are generated
(Restores seemless chunk borders)
2023-08-08 17:54:20 +02:00
EmaMaker 8d160c3239 Merge pull request 'Responsive block picking during generation and meshing' (#7) from better-generation into main
Reviewed-on: #7
2023-07-30 12:30:31 +02:00
EmaMaker ca04afcc89 Merge pull request 'Better World Generation' (#6) from better-generation into main
Reviewed-on: #6
2023-07-30 12:28:59 +02:00
EmaMaker 1bea6c835c separate mesh and generation threads, with priority queues for input
Allows blockpicking while the world is generating, without hiccups
2023-07-30 12:17:51 +02:00
EmaMaker 83f0aafba0 give leaves a sprinkle of color 2023-07-30 12:17:51 +02:00
EmaMaker b0cf413baf generator: add comments + a bit of refactor 2023-07-30 12:17:48 +02:00
EmaMaker baa8d14bb3 generator: optimize leaves generation routine with LUT 2023-07-30 12:17:00 +02:00
EmaMaker 381cd698c7 Initial tree generation
Still very slow because multiple noise evaluations are needed
2023-07-30 12:16:32 +02:00
EmaMaker 9bc5bab3b2 mountain terrain with multiple octaves of noise 2023-07-30 12:13:08 +02:00
EmaMaker 8313ee97bc Merge pull request 'Use Geometry Shader to generate the Mesh' (#5) from vram-reduce into main
Reviewed-on: #5
2023-07-19 13:19:18 +02:00
EmaMaker 4fa89fd2f5 gamma correction 2023-07-19 13:06:01 +02:00
EmaMaker 950c43b163 refactor chunk meshing routine 2023-07-19 12:56:55 +02:00
EmaMaker 54c7fa172f generate mesh from points using geometry shader 2023-07-19 12:42:45 +02:00
EmaMaker e7fc35ec47 create chunk mesh as cloud of points 2023-07-19 12:42:23 +02:00
EmaMaker ea036f403c shader.hpp: add geometry shader support 2023-07-19 12:41:44 +02:00
EmaMaker bff9e6ad4f move normal data to own array 2023-07-18 21:18:12 +02:00
EmaMaker a7b4671517 toggle wireframe with F 2023-07-18 20:12:25 +02:00
EmaMaker 4ada24e0d5 chunkmesher: check for NULLBLK to avoid holes at chunkborders 2023-07-18 20:02:37 +02:00
EmaMaker 73f38e5d2f Merge pull request 'Seamless chunk borders' (#4) from chunkborders into main
Reviewed-on: #4
2023-06-01 21:37:17 +02:00
EmaMaker 393e5ca9b2 chunkmesher: seamless chunkborders by checking neighbouring chunks 2023-06-01 21:31:18 +02:00
EmaMaker f798575cac chunkmanager: add function getBlockAtPos, returns block at world pos
Returns Block::NULLBLK only to signal an invalid position
2023-06-01 21:31:18 +02:00
EmaMaker 0ebbb897dc chunk: handle special cases for getBlocks
Returning Block::AIR when chunk is not yet generated (CHUNK_STATE_GENERATED set to false) is also a way to avoid thread-unsafe concurrent
access to the IntervalMaps data structure, since CHUNK_STATE_GENERATED is set to false before
generating the Chunk and set again to true after generation is complete
2023-06-01 21:31:18 +02:00
EmaMaker 32d0475dbf Merge pull request 'multithread blockpicking' (#2) from multithread into main
Reviewed-on: #2
2023-06-01 17:47:48 +02:00
EmaMaker 095ebfa70d Merge branch 'main' into multithread 2023-06-01 17:47:38 +02:00
EmaMaker 105fff0029 write number of triangles to Chunk only when sending to GPU
previously the current number of vertices was stored in the chunk, and the same variable got later reused for the number of triangles. This modification increases clarity and avoid glitches when blockpicking
2023-05-26 23:07:04 +02:00
EmaMaker 6113886117 multithreaded blockpicking 2023-05-26 23:03:18 +02:00
EmaMaker 802257abe3 Merge pull request 'multithread' (#1) from multithread into main
Reviewed-on: #1
2023-05-20 22:33:56 +02:00
EmaMaker 2f9b7cebcd chunk: make chunk size 32
slight increase in fps with no great increase in ram usage, with greater render distance
2023-05-20 22:15:32 +02:00
EmaMaker 0acb8c7c1e free chunks from memory 2023-05-20 22:15:32 +02:00
EmaMaker e225babb0c chunkmesher: external queue of chunk mesh data
instead of every chunk having its own queue

This decreases usage of ram
2023-05-20 22:15:32 +02:00
EmaMaker 78e3bc11e6 initial update and render with concurrent DS 2023-05-20 22:15:15 +02:00
EmaMaker 1d3132cf3c use oneTBB library 2023-04-29 14:54:09 +02:00
EmaMaker 52537715ef chunks: make chunk state atomic 2023-04-23 16:28:36 +02:00
EmaMaker 3daf994ac3 opengl: disable vsync
This way I can better watch how changes affect FPS/frame times, rather than have it capped at
60FPS/16.6ms
Does not introduce visible tearing.

The engine runs at about 170 FPS on my Tesla M40, and so does minecraft. It never goes past that, I think this is a
limitation of using prime offloading. Runs at 300FPS on average on the Radeon HD6850 to which I
normally offload the Tesla to
2023-04-10 00:45:25 +02:00
EmaMaker 47543c9101 intervalmap: use updated upper_bound when checking removal of next node 2023-04-10 00:30:28 +02:00
EmaMaker 1a4412c5b1 renderer: initial texture support via ArrayTextures
totally stolen textures from minecraft
2023-04-10 00:30:28 +02:00
EmaMaker 2b1991ff2b separate rendering in a dedicated file 2023-04-10 00:30:24 +02:00
39 changed files with 3564 additions and 698 deletions

2
.gitignore vendored
View File

@ -9,3 +9,5 @@ gmon.out*
cscope*
test.cpp
a.out
*screenshot*
imgui.ini

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/imgui"]
path = lib/imgui
url = https://github.com/ocornut/imgui/

View File

@ -2,8 +2,9 @@ cmake_minimum_required(VERSION 3.2)
project(cmake-project-template)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})

View File

@ -6,7 +6,9 @@ enum class Block{
AIR,
STONE,
DIRT,
GRASS
GRASS,
WOOD,
LEAVES
};
#endif
#endif

View File

@ -6,7 +6,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <atomic>
class Camera
{
@ -17,7 +17,11 @@ public:
view = glm::mat4(1.0f);
// This matrix needs to be also updated in viewPortCallback whenever it is changed
projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 200.0f);
projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 1200.0f);
posX = cameraPos.x;
posY = cameraPos.y;
posZ = cameraPos.z;
}
void update(GLFWwindow *window, float deltaTime)
@ -38,6 +42,9 @@ public:
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS)
this->cameraPos -= cameraSpeed * cameraUp;
posX = cameraPos.x;
posY = cameraPos.y;
posZ = cameraPos.z;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
@ -49,7 +56,7 @@ public:
void viewPortCallBack(GLFWwindow *window, int width, int height)
{
projection = glm::perspective(glm::radians(80.0f), (float)width / (float)height, 0.1f, 350.0f);
projection = glm::perspective(glm::radians(80.0f), (float)width / (float)height, 0.1f, 1200.0f);
}
void mouseCallback(GLFWwindow *window, double xpos, double ypos)
@ -78,6 +85,10 @@ public:
glm::mat4 getView() { return view; }
glm::mat4 getProjection() { return projection; }
float getAtomicPosX() { return posX; }
float getAtomicPosY() { return posY; }
float getAtomicPosZ() { return posZ; }
// Plane extraction as per Gribb&Hartmann
// 6 planes, each with 4 components (a,b,c,d)
void getFrustumPlanes(glm::vec4 planes[6], bool normalize)
@ -105,7 +116,7 @@ public:
private:
glm::vec3 cameraPos = glm::vec3(0.0, 80.0f, 0.0f);
glm::vec3 cameraPos = glm::vec3(512.0, 80.0f, 512.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 direction = glm::vec3(0.0f);
@ -114,6 +125,8 @@ private:
float lastX = 400, lastY = 300;
float yaw, pitch;
std::atomic<float> posX, posY, posZ;
};
#endif

View File

@ -1,16 +1,14 @@
#ifndef CHUNK_H
#define CHUNK_H
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <atomic>
#include <array>
#include <bitset>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "block.hpp"
@ -22,14 +20,27 @@
#define CHUNK_VOLUME (CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE)
#define CHUNK_MAX_INDEX (CHUNK_VOLUME - 1)
// int32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1023). There's actually two spare bits
typedef int32_t chunk_index_t;
typedef int16_t chunk_intcoord_t;
typedef uint16_t chunk_state_t;
namespace Chunk
{
constexpr uint8_t CHUNK_STATE_GENERATED = 1;
constexpr uint8_t CHUNK_STATE_MESHED = 2;
constexpr uint8_t CHUNK_STATE_MESH_LOADED = 3;
constexpr uint8_t CHUNK_STATE_LOADED = 4;
constexpr uint8_t CHUNK_STATE_EMPTY = 7;
chunk_index_t calculateIndex(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k);
chunk_index_t calculateIndex(glm::vec3 pos);
constexpr chunk_state_t CHUNK_STATE_GENERATED = 1;
constexpr chunk_state_t CHUNK_STATE_MESHED = 2;
constexpr chunk_state_t CHUNK_STATE_MESH_LOADED = 4;
constexpr chunk_state_t CHUNK_STATE_LOADED = 8;
constexpr chunk_state_t CHUNK_STATE_OUTOFVISION = 16;
constexpr chunk_state_t CHUNK_STATE_UNLOADED = 32;
constexpr chunk_state_t CHUNK_STATE_EMPTY = 64;
constexpr chunk_state_t CHUNK_STATE_IN_GENERATION_QUEUE = 128;
constexpr chunk_state_t CHUNK_STATE_IN_MESHING_QUEUE = 256;
constexpr chunk_state_t CHUNK_STATE_IN_DELETING_QUEUE = 512;
int coord3DTo1D(int x, int y, int z);
@ -42,9 +53,14 @@ namespace Chunk
public:
glm::vec3 getPosition() { return this->position; }
std::bitset<8> getTotalState() { return this->state; }
bool getState(uint8_t n) { return this->state.test(n); }
void setState(uint8_t nstate, bool value);
void setState(chunk_state_t nstate, bool value);
bool getState(chunk_state_t n) { return (this->state & n) == n; }
bool isFree(){ return !(
this->getState(CHUNK_STATE_IN_GENERATION_QUEUE) ||
this->getState(CHUNK_STATE_IN_MESHING_QUEUE) ||
this->getState(CHUNK_STATE_IN_DELETING_QUEUE)
); }
chunk_state_t getTotalState() { return this->state; }
void setBlock(Block b, int x, int y, int z);
void setBlocks(int start, int end, Block b);
@ -53,23 +69,15 @@ namespace Chunk
std::unique_ptr<Block[]> getBlocksArray(int* len) { return (this->blocks.toArray(len)); }
public:
GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, nIndices{0};
std::mutex mutex_state;
std::vector<GLfloat> vertices;
std::vector<GLfloat> colors;
std::vector<GLuint> indices;
std::unordered_map<glm::vec3, std::tuple<GLuint, glm::vec3, glm::vec3> > vertices_map; // index,
// normal,
std::vector<glm::vec3> index_to_vertex;
std::atomic<float> unload_timer{0};
chunk_index_t getIndex(){ return this->index; }
private:
glm::vec3 position{};
IntervalMap<Block> blocks{};
std::bitset<8> state{0};
std::atomic<chunk_state_t> state{0};
chunk_index_t index;
};
};

View File

@ -1,28 +1,41 @@
#ifndef CHUNKMANAGER_H
#define CHUNKMANAGER_H
// Second to be passed outside of render distance for a chunk to be destroyed
#include <oneapi/tbb/concurrent_hash_map.h>
#include <oneapi/tbb/concurrent_queue.h>
#include <oneapi/tbb/concurrent_priority_queue.h>
#include <thread>
#include "chunk.hpp"
#include "globals.hpp"
#include "worldupdatemessage.h"
// Seconds to be passed outside of render distance for a chunk to be destroyed
#define UNLOAD_TIMEOUT 10
#include <thread>
#define MESHING_PRIORITY_NORMAL 0
#define MESHING_PRIORITY_PLAYER_EDIT 10
#define GENERATION_PRIORITY_NORMAL 0
namespace chunkmanager
{
std::thread initGenThread();
std::thread initMeshThread();
void stopGenThread();
void stopMeshThread();
void mesh();
void generate();
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, Chunk::Chunk*> ChunkTable;
typedef std::pair<Chunk::Chunk*, uint8_t> ChunkPQEntry;
// The comparing function to use
struct compare_f {
bool operator()(const ChunkPQEntry& u, const ChunkPQEntry& v) const {
return u.second > v.second;
}
};
typedef oneapi::tbb::concurrent_priority_queue<ChunkPQEntry, compare_f> ChunkPriorityQueue;
void init();
void blockpick(bool place);
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k);
void update();
void stop();
void destroy();
void update(float deltaTime);
void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t);
WorldUpdateMsgQueue& getWorldUpdateQueue();
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices();
Block getBlockAtPos(int x, int y, int z);
}
#endif

36
include/chunkmeshdata.hpp Normal file
View File

@ -0,0 +1,36 @@
#ifndef CHUNK_MESH_DATA_H
#define CHUNK_MESH_DATA_H
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
enum class ChunkMeshDataType{
MESH_UPDATE
};
typedef struct ChunkMeshData{
chunk_index_t index;
glm::vec3 position;
int num_vertices = 0;
std::vector<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> texinfo;
ChunkMeshDataType message_type;
void clear(){
vertices.clear();
texinfo.clear();
extents.clear();
index = 0;
position = glm::vec3(0);
num_vertices = 0;
}
}ChunkMeshData;
typedef oneapi::tbb::concurrent_queue<ChunkMeshData*> ChunkMeshDataQueue;
#endif

View File

@ -1,28 +1,34 @@
#ifndef CHUNKMESH_H
#define CHUNKMESH_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <string>
#include <vector>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <GLFW/glfw3.h>
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
#include "chunkmeshdata.hpp"
#include "globals.hpp"
#include "shader.hpp"
namespace chunkmesher{
void mesh(Chunk::Chunk* chunk);
void sendtogpu(Chunk::Chunk* chunk);
void draw(Chunk::Chunk* chunk, glm::mat4 model);
struct MeshData{
Chunk::Chunk* chunk;
GLuint numVertices{0};
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight,
glm::vec3 bottomRight, glm::vec3 normal, Block block, bool backFace);
std::vector<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> texinfo;
};
ChunkMeshDataQueue& getMeshDataQueue();
void init();
void mesh(Chunk::Chunk* chunk);
}
#endif

14
include/controls.hpp Normal file
View File

@ -0,0 +1,14 @@
#ifndef CONTROLS_H
#define CONTROLS_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define BLOCKPICK_TIMEOUT 0.1f
namespace controls{
void init();
void update(GLFWwindow* window);
};
#endif

19
include/debugwindow.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef DEBUG_WINDOW_H
#define DEBUG_WINDOW_H
#include <any>
#include <string>
#include <GLFW/glfw3.h>
namespace debug{
namespace window {
void init(GLFWwindow* window);
void prerender();
void render();
void destroy();
void set_parameter(std::string key, std::any value);
}
}
#endif

View File

@ -13,7 +13,12 @@
#define RENDER_DISTANCE 16
extr Camera theCamera;
extr Shader* theShader;
// the cube spans in both directions, to each axis has to be multiplied by 2. 2^3=8
constexpr int chunks_volume = 8*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE);
extr bool wireframe;
extr float sines[360];
extr float cosines[360];
extr uint32_t MORTON_XYZ_ENCODE[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
extr uint32_t MORTON_XYZ_DECODE[CHUNK_VOLUME][3];

View File

@ -38,8 +38,9 @@ public:
if(end_next_entry->first != end)
treemap[end] = end_prev_entry->second;
auto e = treemap.upper_bound(end);
// A little optimization: delete next key if it is of the same value of the end key
if(end_next_entry->second == treemap[end]) treemap.erase(end_next_entry);
if(e != treemap.end() && e->second == treemap[end]) treemap.erase(end_next_entry);
}
// insert the start key. Replaces whatever value is already there. Do not place if the element before is of the same value

View File

@ -1,8 +1,10 @@
#ifndef MAIN_H
#define MAIN_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
void framebuffer_size_callback(GLFWwindow *, int, int);
void mouse_callback(GLFWwindow *window, double xpos, double ypos);
void processInput(GLFWwindow *);
#endif

61
include/renderer.hpp Normal file
View File

@ -0,0 +1,61 @@
#ifndef RENDERER_H
#define RENDERER_H
#include <oneapi/tbb/concurrent_unordered_set.h>
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
#include "chunkmesher.hpp"
#include "chunkmeshdata.hpp"
#include "shader.hpp"
namespace renderer{
typedef struct RenderInfo {
chunk_index_t index;
int num_vertices;
glm::vec3 position;
bool buffers_allocated=false;
GLuint VAO, VBO, extentsBuffer, texinfoBuffer;
void allocateBuffers(){
// Allocate buffers
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &extentsBuffer);
glGenBuffers(1, &texinfoBuffer);
buffers_allocated=true;
}
void deallocateBuffers(){
// Allocate buffers
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &extentsBuffer);
glDeleteBuffers(1, &texinfoBuffer);
glDeleteVertexArrays(1, &VAO);
buffers_allocated=false;
}
} RenderInfo;
typedef oneapi::tbb::concurrent_queue<int32_t> IndexQueue;
void init(GLFWwindow* window);
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info);
void render();
void resize_framebuffer(int width, int height);
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void destroy();
void saveScreenshot(bool forceFullHD=false);
Shader* getRenderShader();
ChunkMeshDataQueue& getMeshDataQueue();
IndexQueue& getDeleteIndexQueue();
};
#endif

View File

@ -19,22 +19,32 @@ public:
unsigned int ID;
// constructor generates the shader on the fly
// ------------------------------------------------------------------------
Shader(const char *vertexPath, const char *fragmentPath)
Shader(const char* geometryPath, const char *vertexPath, const char *fragmentPath)
{
// 1. retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
std::ifstream gShaderFile;
// ensure ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
std::stringstream vShaderStream, fShaderStream, gShaderStream;
if(geometryPath){
gShaderFile.open(geometryPath);
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
@ -49,29 +59,45 @@ public:
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
}
const char *vShaderCode = vertexCode.c_str();
const char *fShaderCode = fragmentCode.c_str();
// 2. compile shaders
unsigned int vertex, fragment;
// shader Program
ID = glCreateProgram();
unsigned int vertex, fragment, geometry;
// geometry shader
if(geometryPath){
const char *gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
checkCompileErrors(geometry, "GEOMETRY");
glAttachShader(ID, geometry);
}
// vertex shader
const char *vShaderCode = vertexCode.c_str();
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
glAttachShader(ID, vertex);
// fragment Shader
const char *fShaderCode = fragmentCode.c_str();
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// shader Program
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
// Constructu the program
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// delete the shaders as they're linked into our program now and no longer necessary
glDeleteShader(vertex);
glDeleteShader(fragment);
if(geometryPath) glDeleteShader(geometry);
}
// activate the shader
// ------------------------------------------------------------------------

1724
include/stb_image_write.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
#ifndef WORLD_UPDATE_MSG_H
#define WORLD_UPDATE_MSG_H
#include <glm/glm.hpp>
#include <oneapi/tbb/concurrent_queue.h>
#include "block.hpp"
enum class WorldUpdateMsgType{
BLOCKPICK_PLACE,
BLOCKPICK_BREAK
};
typedef struct WorldUpdateMsg{
WorldUpdateMsgType msg_type;
glm::vec3 cameraPos;
glm::vec3 cameraFront;
float time;
Block block;
} WorldUpdateMsg;
typedef oneapi::tbb::concurrent_queue<WorldUpdateMsg> WorldUpdateMsgQueue;
#endif

View File

@ -1,2 +1,3 @@
add_subdirectory(glad)
add_subdirectory(glm)
add_subdirectory(glm)
add_subdirectory(imgui)

1
lib/imgui Submodule

@ -0,0 +1 @@
Subproject commit 6addf28c4b5d8fd109a6db73bed6436952b230b2

28
shaders/shader-quad.fs Normal file
View File

@ -0,0 +1,28 @@
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D renderTex;
uniform int screenWidth;
uniform int screenHeight;
uniform int crosshairType;
void main(){
float crosshair_alpha = 0.8;
float dist = length(gl_FragCoord.xy-vec2(screenWidth/2, screenHeight/2));
FragColor = texture(renderTex, TexCoord);
/*float crosshair_color = (FragColor.x + FragColor.y + FragColor.z) / 3;
/*if(crosshair_color <= 0.5) crosshair_color = 1.0;
/*else crosshair_color = 0.0;*/
float crosshair_color = 1.0;
if(dist <= 7){
if( (crosshairType == 0 && dist >= 5) ||
(crosshairType == 1 && ( int(gl_FragCoord.x) == int(screenWidth / 2) ||
int(gl_FragCoord.y) == int(screenHeight / 2)) )
) FragColor = vec4(vec3(crosshair_color), crosshair_alpha);
}
}

12
shaders/shader-quad.vs Normal file
View File

@ -0,0 +1,12 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
TexCoord = aTexCoord;
gl_Position = vec4(aPos, 1.0);
}

47
shaders/shader-texture.fs Normal file
View File

@ -0,0 +1,47 @@
#version 330 core
out vec4 FragColor;
in vec3 TexCoord;
in vec3 Normal;
in vec3 FragPos;
vec3 lightColor = vec3(1.0);
vec3 lightDir = -normalize(vec3(0.0, 100.0, 0.0) - vec3(32.0));
float ambientStrength = 0.1;
float diffuseStrength = 0.8;
float specularStrength = 0.1;
uniform vec3 viewPos;
uniform float u_time;
uniform sampler2DArray textureArray;
float gamma = 2.2;
void main(){
// Load the texture
// anti-gamma-correction of the texture. Without this it would be gamma corrected twice!
vec3 vColor = pow(texture(textureArray, TexCoord).rgb, vec3(gamma));
if(TexCoord.z == 4) vColor = vColor * normalize(vec3(10, 250, 10));
vec3 normal = normalize(Normal);
/* Start of Blinn-Phong lighting */
// Ambient
vec3 ambient = lightColor*vColor;
// Diffuse
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = vColor * diff;
// Blinn Specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
vec3 specular = lightColor * vColor * spec;
// Final color
vec3 color = ambient * ambientStrength + diffuse * diffuseStrength + specular * specularStrength;
FragColor.rgb = pow(color, vec3(1.0/gamma));
}

81
shaders/shader-texture.gs Normal file
View File

@ -0,0 +1,81 @@
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
in VS_OUT{
vec3 Extents;
vec3 Normal;
float BlockType;
} gs_in[];
out vec3 TexCoord;
out vec3 Normal;
out vec3 FragPos;
uniform mat4 view;
uniform mat4 projection;
void main(){
Normal = gs_in[0].Normal;
TexCoord = vec3(0.0, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position;
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
if(gs_in[0].Extents.x == 0){
TexCoord = vec3(gs_in[0].Extents.z, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(0.0, gs_in[0].Extents.y, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, gs_in[0].Extents.y, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.z, gs_in[0].Extents.y, gs_in[0].BlockType);
}
else if(gs_in[0].Extents.y == 0){
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.x, gs_in[0].Extents.z, gs_in[0].BlockType);
}
else{
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(0.0, gs_in[0].Extents.y, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, gs_in[0].Extents.y, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.x, gs_in[0].Extents.y, gs_in[0].BlockType);
}
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
EndPrimitive();
}

27
shaders/shader-texture.vs Normal file
View File

@ -0,0 +1,27 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aExtents;
layout (location = 2) in vec2 aInfo;
uniform mat4 model;
out VS_OUT {
vec3 Extents;
vec3 Normal;
float BlockType;
} vs_out;
void main()
{
//vNormal = mat3(transpose(inverse(model))) * aNormal;
vs_out.Extents = aExtents;
vs_out.BlockType = aInfo.y;
if(aExtents.x == 0) vs_out.Normal = vec3(1.0 - 2*aInfo.x, 0.0, 0.0);
else if(aExtents.y == 0) vs_out.Normal = vec3(0.0, 1.0 - 2*aInfo.x, 0.0);
else vs_out.Normal = vec3(0.0, 0.0, 1.0 - 2*aInfo.x);
vs_out.Normal = mat3(transpose(inverse(model))) * vs_out.Normal;
gl_Position = model * vec4(aPos, 1.0);
}

View File

@ -1,9 +1,10 @@
cmake_minimum_required(VERSION 3.2)
project(OpenGLTest)
set(SOURCE_FILES main.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenerator.cpp spacefilling.cpp stb_image.cpp utils.cpp OpenSimplexNoise.cpp)
set(SOURCE_FILES main.cpp controls.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenerator.cpp
debugwindow.cpp renderer.cpp spacefilling.cpp stb_image.cpp utils.cpp OpenSimplexNoise.cpp)
add_executable(OpenGLTest ${SOURCE_FILES})
target_link_libraries(OpenGLTest glfw glad glm)
target_link_libraries(OpenGLTest glfw tbb glad glm imgui)
install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})

View File

@ -15,37 +15,31 @@ namespace Chunk
return utils::coord3DTo1D(x, y, z, CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE);
}
chunk_index_t calculateIndex(glm::vec3 pos){
return calculateIndex(static_cast<chunk_intcoord_t>(pos.x), static_cast<chunk_intcoord_t>(pos.y),
static_cast<chunk_intcoord_t>(pos.z));
}
chunk_index_t calculateIndex(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k){
return i | (j << 10) | (k << 20);
}
Chunk::Chunk(glm::vec3 pos)
{
this->position = pos;
this->setState(CHUNK_STATE_EMPTY, true);
glGenVertexArrays(1, &(this->VAO));
glGenBuffers(1, &(this->colorBuffer));
glGenBuffers(1, &(this->VBO));
glGenBuffers(1, &(this->EBO));
this->setBlocks(0, CHUNK_MAX_INDEX, Block::AIR);
this->index = calculateIndex(pos);
}
Chunk ::~Chunk()
{
vertices_map.clear();
index_to_vertex.clear();
vertices.clear();
indices.clear();
colors.clear();
glDeleteBuffers(1, &(this->colorBuffer));
glDeleteBuffers(1, &(this->VBO));
glDeleteBuffers(1, &(this->EBO));
glDeleteVertexArrays(1, &(this->VAO));
mutex_state.unlock();
}
Block Chunk::getBlock(int x, int y, int z)
{
if(x < 0 || y < 0 || z < 0 || x > CHUNK_SIZE -1 || y > CHUNK_SIZE -1 || z > CHUNK_SIZE-1 ||
!getState(CHUNK_STATE_GENERATED)) return Block::AIR;
return blocks.at(HILBERT_XYZ_ENCODE[x][y][z]);
}
@ -60,11 +54,11 @@ namespace Chunk
this->blocks.insert(start < 0 ? 0 : start, end >= CHUNK_VOLUME ? CHUNK_VOLUME : end, b);
}
void Chunk::setState(uint8_t nstate, bool value)
void Chunk::setState(chunk_state_t nstate, bool value)
{
if (value)
this->state.set((size_t)nstate);
this->state.fetch_or(nstate);
else
this->state.reset((size_t)nstate);
this->state.fetch_and(~nstate);
}
}

View File

@ -9,60 +9,111 @@
#include "utils.hpp"
#define GRASS_OFFSET 40
#define NOISE_GRASS_MULT 20
#define NOISE_GRASS_MULT 30
#define NOISE_DIRT_MULT 3
#define NOISE_DIRT_MIN 2
#define NOISE_DIRT_MIN 3
#define NOISE_DIRT_X_MULT 0.001f
#define NOISE_DIRT_Z_MULT 0.001f
#define NOISE_GRASS_X_MULT 0.035f
#define NOISE_GRASS_Z_MULT 0.035f
#define NOISE_GRASS_X_MULT 0.018f
#define NOISE_GRASS_Z_MULT 0.018f
#define NOISE_TREE_X_MULT 0.01f
#define NOISE_TREE_Z_MULT 0.01f
#define LEAVES_RADIUS 3
#define WOOD_CELL_SIZE 13
#define WOOD_CELL_CENTER 7
#define TREE_STANDARD_HEIGHT 7
#define TREE_HEIGHT_VARIATION 2
#define WOOD_CELL_BORDER (LEAVES_RADIUS-1)
#define WOOD_MAX_OFFSET (WOOD_CELL_SIZE-WOOD_CELL_CENTER-WOOD_CELL_BORDER)
void generatePyramid(Chunk::Chunk *chunk);
void generateNoise(Chunk::Chunk *chunk);
void generateNoise3D(Chunk::Chunk *chunk);
void generateChunk(Chunk::Chunk *chunk)
{
generateNoise(chunk);
}
Block block;
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves);
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz);
std::random_device dev;
std::mt19937 mt(dev());
OpenSimplexNoise::Noise noiseGen1(mt());
OpenSimplexNoise::Noise noiseGen2(mt());
OpenSimplexNoise::Noise noiseGenWood(mt());
// Trees are generated by virtually dividing the world into cells. Each cell can contain exactly one
// tree, with some offset in the position. Having a border in the cell ensures that no trees are generated in
// adjacent blocks
// cover CHUNK_SIZE with WOOD_CELLS + 2 cells before and after the chunk
constexpr int TREE_LUT_SIZE = std::ceil(static_cast<float>(CHUNK_SIZE)/static_cast<float>(WOOD_CELL_SIZE)) + 2;
// Info on the tree cell to generate
struct TreeCellInfo{
// Cell coordinates (in "tree cell space")
int wcx, wcz;
// trunk offset from 0,0 in the cell
int trunk_x_offset, trunk_z_offset;
// Global x,z position of the trunk
int trunk_x, trunk_z;
// Y of the center of the leaves sphere
int leaves_y_pos;
};
// Lookup tables for generation
std::array<int, CHUNK_SIZE * CHUNK_SIZE> grassNoiseLUT;
std::array<int, CHUNK_SIZE * CHUNK_SIZE> dirtNoiseLUT;
std::array<TreeCellInfo, TREE_LUT_SIZE*TREE_LUT_SIZE> treeLUT;
void generateNoise(Chunk::Chunk *chunk)
{
int cx = chunk->getPosition().x * CHUNK_SIZE;
int cy = chunk->getPosition().y * CHUNK_SIZE;
int cz = chunk->getPosition().z * CHUNK_SIZE;
// Precalculate LUTs
// Terrain LUTs
// Noise value at a given (x,z), position represents:
// Grass Noise LUT: Height of the terrain: when the grass is placed and the player will stand
// Dirt Noise LUT: How many blocks of dirt to place before there is stone
// Anything below (grass-level - dirt_height) will be stone
for (int i = 0; i < grassNoiseLUT.size(); i++)
{
grassNoiseLUT[i] = -1;
dirtNoiseLUT[i] = -1;
int bx = i / CHUNK_SIZE;
int bz = i % CHUNK_SIZE;
grassNoiseLUT[i] = GRASS_OFFSET + evaluateNoise(noiseGen1, cx+bx, cz+bz, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5);
dirtNoiseLUT[i] = NOISE_DIRT_MIN + (int)((1 + noiseGen2.eval(cx+bx * NOISE_DIRT_X_MULT,
cz+bz * NOISE_DIRT_Z_MULT)) * NOISE_DIRT_MULT);
}
Block block_prev{Block::AIR};
int block_prev_start{0};
// Tree LUT
int tree_lut_x_offset = cx / WOOD_CELL_SIZE - 1;
int tree_lut_z_offset = cz / WOOD_CELL_SIZE - 1;
// A space filling curve is continuous, so there is no particular order
for(int i = 0; i < TREE_LUT_SIZE; i++)
for(int k = 0; k < TREE_LUT_SIZE; k++){
int wcx = (tree_lut_x_offset + i);
int wcz = (tree_lut_z_offset + k);
treeLUT[i * TREE_LUT_SIZE + k] = evaluateTreeCell(wcx, wcz);
}
// Generation of terrain
// March along the space-filling curve, calculate information about the block at every position
// A space-filling curve is continuous, so there is no particular order
// Take advantage of the interval-map structure by only inserting contigous runs of blocks
Block block_prev{Block::AIR}, block;
int block_prev_start{0};
for (int s = 0; s < CHUNK_VOLUME; s++)
{
int x = HILBERT_XYZ_DECODE[s][0] + CHUNK_SIZE * chunk->getPosition().x;
int y = HILBERT_XYZ_DECODE[s][1] + CHUNK_SIZE * chunk->getPosition().y;
int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z;
int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2];
if (grassNoiseLUT[d2] == -1)
grassNoiseLUT[d2] = GRASS_OFFSET + (int)((0.5 + noiseGen1.eval(x * NOISE_GRASS_X_MULT, z * NOISE_GRASS_Z_MULT) * NOISE_GRASS_MULT));
if (dirtNoiseLUT[d2] == -1)
dirtNoiseLUT[d2] = NOISE_DIRT_MIN + (int)((0.5 + noiseGen2.eval(x * NOISE_DIRT_X_MULT, z * NOISE_DIRT_Z_MULT) * NOISE_DIRT_MULT));
int grassNoise = grassNoiseLUT[d2];
int dirtNoise = dirtNoiseLUT[d2];
int bx = HILBERT_XYZ_DECODE[s][0];
int by = HILBERT_XYZ_DECODE[s][1];
int bz = HILBERT_XYZ_DECODE[s][2];
int x = bx + cx;
int y = by + cy;
int z = bz + cz;
int lut_index = bx * CHUNK_SIZE + bz;
int grassNoise = grassNoiseLUT[lut_index];
int dirtNoise = dirtNoiseLUT[lut_index];
int stoneLevel = grassNoise - dirtNoise;
if (y < stoneLevel)
@ -71,23 +122,116 @@ void generateNoise(Chunk::Chunk *chunk)
block = Block::DIRT;
else if (y == grassNoise)
block = Block::GRASS;
else
else
block = Block::AIR;
// Divide the world into cells, each with exactly one tree, so that no two trees will be adjacent of each other
struct TreeCellInfo info;
int wcx = (int)(x / WOOD_CELL_SIZE) - tree_lut_x_offset; // wood cell x
int wcz = (int)(z / WOOD_CELL_SIZE) - tree_lut_z_offset; // wood cell z
// Retrieve info on the cell from LUT
info = treeLUT[wcx * TREE_LUT_SIZE + wcz];
// A tree is to be placed in this position if the coordinates are those of the tree of the current cell
int wood_height = TREE_STANDARD_HEIGHT;
bool wood = x == info.trunk_x && z == info.trunk_z && y > grassNoiseLUT[lut_index] && y <= info.leaves_y_pos;
bool leaf{false};
// Check placing of leaves
if(wood) leaf = y > info.leaves_y_pos && y < info.leaves_y_pos+LEAVES_RADIUS;
else{
if(!leaf) leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
// Eventually search neighboring cells
if(!leaf && wcx+1 < TREE_LUT_SIZE){
info = treeLUT[(wcx+1) * TREE_LUT_SIZE + wcz];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
if(!leaf && wcx-1 >= 0){
info = treeLUT[(wcx-1) * TREE_LUT_SIZE + wcz];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
if(!leaf && wcz-1 >= 0){
info = treeLUT[wcx * TREE_LUT_SIZE + (wcz-1)];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
if(!leaf && wcz+1 < TREE_LUT_SIZE){
info = treeLUT[wcx * TREE_LUT_SIZE + (wcz+1)];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
}
if(wood) block = Block::WOOD;
if(leaf) block = Block::LEAVES;
// Use the interval-map structure of the chunk to compress the world: insert "runs" of
// equal blocks using indices in the hilbert curve
if (block != block_prev)
{
chunk->setBlocks(block_prev_start, s, block_prev);
block_prev_start = s;
}
block_prev = block;
}
// Insert the last run of blocks
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
// Mark the chunk as generated, is needed to trigger the next steps
chunk->setState(Chunk::CHUNK_STATE_GENERATED, true);
}
// Noise evaluation with Fractal Brownian Motion
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves){
double sum = 0;
for(int i = 0; i < octaves; i++){
sum += amplitude * noiseGen.eval(x*frequency, y*frequency);
amplitude *= persistence;
frequency *= lacunarity;
}
return sum;
}
// Tree cell Info
const int TREE_MASTER_SEED_X = mt();
const int TREE_MASTER_SEED_Z = mt();
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz){
int anglex = TREE_MASTER_SEED_X*wcx+TREE_MASTER_SEED_Z*wcz;
int anglez = TREE_MASTER_SEED_Z*wcz+TREE_MASTER_SEED_X*wcx;
// Start at the center of the cell, with a bit of random offset
int wcx_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * sines[anglex % 360];
int wcz_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * cosines[anglez % 360];
struct TreeCellInfo result{};
// Cell to world coordinates
result.trunk_x = wcx * WOOD_CELL_SIZE + wcx_off;
result.trunk_z = wcz * WOOD_CELL_SIZE + wcz_off;
result.trunk_x_offset = wcx_off;
result.trunk_z_offset = wcz_off;
result.leaves_y_pos = 1 + TREE_STANDARD_HEIGHT + GRASS_OFFSET + evaluateNoise(noiseGen1,
result.trunk_x, result.trunk_z, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5);
return result;
}
void generateChunk(Chunk::Chunk *chunk)
{
generateNoise(chunk);
}
/* EXPERIMENTAL STUFF */
void generateNoise3D(Chunk::Chunk *chunk) {
Block block_prev{Block::AIR};
Block block_prev{Block::AIR}, block;
int block_prev_start{0};
// A space filling curve is continuous, so there is no particular order
@ -101,11 +245,11 @@ void generateNoise3D(Chunk::Chunk *chunk) {
double noise = noiseGen1.eval(x * 0.025, y*0.025, z * 0.025);
if (noise < 0)
if (noise < -0.1)
block = Block::STONE;
else if (noise >= 0 && noise < 0.1)
else if (noise >= -0.1 && noise < 0)
block = Block::DIRT;
else if (noise >= 0.1 && noise < 0.2)
else if (noise >= 0 && noise < 0.08)
block = Block::GRASS;
else
block = Block::AIR;
@ -121,11 +265,3 @@ void generateNoise3D(Chunk::Chunk *chunk) {
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
}
void generatePyramid(Chunk::Chunk *chunk)
{
for (int i = 0; i < CHUNK_SIZE; i++)
for (int j = 0; j < CHUNK_SIZE; j++)
for (int k = 0; k < CHUNK_SIZE; k++)
chunk->setBlock(i >= j && i < CHUNK_SIZE - j && k >= j && k < CHUNK_SIZE - j ? (j & 1) == 0 ? Block::GRASS : Block::STONE : Block::AIR, i, j, k);
}

View File

@ -1,282 +1,303 @@
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "chunk.hpp"
#include "chunkgenerator.hpp"
#include "chunkmanager.hpp"
#include "chunkmesher.hpp"
#include "globals.hpp"
#include <atomic>
#include <iostream>
#include <math.h>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include <thread>
std::unordered_map<std::uint32_t, Chunk::Chunk *> chunks;
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtc/matrix_transform.hpp>
constexpr int chunks_volume = static_cast<int>(1.333333333333*M_PI*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE));
std::array<std::array<int, 3>, chunks_volume> chunks_indices;
#include <oneapi/tbb/parallel_for.h>
#include "block.hpp"
#include "chunk.hpp"
#include "chunkgenerator.hpp"
#include "chunkmesher.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
#include "utils.hpp"
namespace chunkmanager
{
// thread management
std::mutex mutex_queue_generate;
std::mutex mutex_queue_mesh;
std::set<Chunk::Chunk *> to_generate;
std::set<Chunk::Chunk *> to_mesh;
std::atomic_bool generate_should_run;
std::atomic_bool mesh_should_run;
void blockpick(WorldUpdateMsg& msg); // There's no need of passing by value again (check
// controls.cpp)
void generate();
void mesh();
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority);
// update variables
uint8_t f = 0;
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
glm::vec4 frustumPlanes[6];
glm::vec3 cameraPos;
int chunkX, chunkY, chunkZ;
int total{0}, toGpu{0};
/* Chunk holding data structures */
// Concurrent hash table of chunks
ChunkTable chunks;
// Chunk indices. Centered at (0,0,0), going in concentric sphere outwards
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume> chunks_indices;
// disposal
std::unordered_map<uint32_t, float> to_delete;
std::set<uint32_t> to_delete_delete;
/* World Update messaging data structure */
WorldUpdateMsgQueue WorldUpdateQueue;
void mesh()
{
while (mesh_should_run)
if (mutex_queue_mesh.try_lock())
{
for (const auto &c : to_mesh)
{
if (c->mutex_state.try_lock())
{
chunkmesher::mesh(c);
c->setState(Chunk::CHUNK_STATE_MESHED, true);
c->mutex_state.unlock();
}
}
to_mesh.clear();
mutex_queue_mesh.unlock();
}
}
/* Multithreading */
std::atomic_bool should_run;
std::thread gen_thread, mesh_thread, update_thread;
void generate()
{
while (generate_should_run)
if (mutex_queue_generate.try_lock())
{
for (const auto &c : to_generate)
{
if (c->mutex_state.try_lock())
{
generateChunk(c);
c->setState(Chunk::CHUNK_STATE_GENERATED, true);
c->mutex_state.unlock();
}
}
to_generate.clear();
mutex_queue_generate.unlock();
}
}
std::thread initMeshThread()
{
mesh_should_run = true;
std::thread mesh_thread(mesh);
return mesh_thread;
}
std::thread initGenThread()
{
generate_should_run = true;
std::thread gen_thread(generate);
return gen_thread;
}
// Queue of chunks to be generated
ChunkPriorityQueue chunks_to_generate_queue;
// Queue of chunks to be meshed
ChunkPriorityQueue chunks_to_mesh_queue;
WorldUpdateMsgQueue& getWorldUpdateQueue(){ return WorldUpdateQueue; }
// Init chunkmanager. Chunk indices and start threads
void init(){
int index{0};
int xp{0}, x{0};
bool b = true;
for(chunk_intcoord_t i = -RENDER_DISTANCE; i < RENDER_DISTANCE; i++)
for(chunk_intcoord_t j = -RENDER_DISTANCE; j < RENDER_DISTANCE; j++)
for(chunk_intcoord_t k = -RENDER_DISTANCE; k < RENDER_DISTANCE; k++){
// Iterate over all chunks, in concentric spheres starting fron the player and going
// outwards. Alternate left and right
// Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
while (xp <= RENDER_DISTANCE)
{
// Alternate between left and right
if (b) x = +xp;
else x = -xp;
// Step 1. At current x, get the corresponding y values (2nd degree equation, up to 2
// possible results)
int y1 = static_cast<int>(sqrt((rr) - x*x));
for (int y = -y1 + 1 ; y <= y1; y++)
{
// Step 2. At both y's, get the corresponding z values
int z1 = static_cast<int>(sqrt( rr - x*x - y*y));
for (int z = -z1 + 1; z <= z1; z++){
chunks_indices[index][0] = x;
chunks_indices[index][1] = y;
chunks_indices[index][2] = z;
chunks_indices[index][0]=i;
chunks_indices[index][1]=j;
chunks_indices[index][2]=k;
index++;
}
}
if (!b)
{
xp++;
b = true;
}
else b = false;
}
should_run = true;
update_thread = std::thread(update);
gen_thread = std::thread(generate);
mesh_thread = std::thread(mesh);
}
void update(float deltaTime)
{
// Try to lock resources
f = 0;
f |= mutex_queue_generate.try_lock();
f |= mutex_queue_mesh.try_lock() << 1;
cameraPos = theCamera.getPos();
theCamera.getFrustumPlanes(frustumPlanes, true);
chunkX=static_cast<int>(cameraPos.x) / CHUNK_SIZE;
chunkY=static_cast<int>(cameraPos.y) / CHUNK_SIZE;
chunkZ=static_cast<int>(cameraPos.z) / CHUNK_SIZE;
// Use time in float to be consistent with glfw
float currentTime = glfwGetTime();
// Check for far chunks that need to be cleaned up from memory
int nUnloaded{0};
for(const auto& n : chunks){
Chunk::Chunk* c = n.second;
int x{(int)(c->getPosition().x)};
int y{(int)(c->getPosition().y)};
int z{(int)(c->getPosition().z)};
if( (chunkX-x)*(chunkX-x) + (chunkY-y)*(chunkY-y) + (chunkZ-z)*(chunkZ-z) >=
(int)(RENDER_DISTANCE*1.5)*(int)(RENDER_DISTANCE*1.5))
if(to_delete.find(n.first) == to_delete.end())
to_delete.insert(std::make_pair(n.first, currentTime));
}
for(const auto& n :to_delete){
if( currentTime>=n.second + UNLOAD_TIMEOUT) {
delete chunks.at(n.first);
chunks.erase(n.first);
nUnloaded++;
// Delete afterwards to avoid exception due to invalid iterators
to_delete_delete.insert(n.first);
// Method for world generation thread(s)
void generate(){
while(should_run){
ChunkPQEntry entry;
if(chunks_to_generate_queue.try_pop(entry)){
Chunk::Chunk* chunk = entry.first;
generateChunk(chunk);
chunk->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false);
}
}
for(uint32_t i : to_delete_delete) to_delete.erase(i);
to_delete_delete.clear();
if(nUnloaded) std::cout << "Unloaded " << nUnloaded << " chunks\n";
for(int i = 0; i < chunks_volume; i++)
updateChunk(calculateIndex(chunks_indices[i][0] + chunkX,
chunks_indices[i][1] + chunkY,
chunks_indices[i][2] + chunkZ),
chunks_indices[i][0] + chunkX,
chunks_indices[i][1] + chunkY,
chunks_indices[i][2] + chunkZ);
//std::cout << "Chunks to mesh: " << to_mesh.size() << "\n";
//std::cout << "Chunks to generate: " << to_generate.size() << "\n";
//std::cout << "Total chunks to draw: " << total << ". Sent to GPU: " << toGpu << "\n";
//total = 0;
//toGpu = 0;
// Unlock mutexes if previously locked. Unlocking a mutex not locked by the current thread
// or already locked is undefined behaviour, so checking has to be done
if ((f & 1))
mutex_queue_generate.unlock();
if ((f & 2))
mutex_queue_mesh.unlock();
chunks_to_generate_queue.clear();
}
// Generation and meshing happen in two separate threads from the main one
// Chunk states are used to decide which actions need to be done on the chunk and sets+mutexes
// to pass the chunks to be operated on between the threads.
// Uploading data to GPU still needs to be done in the main thread, or another OpenGL context
// needs to be opened, which further complicates stuff.
void updateChunk(uint32_t index, uint16_t i, uint16_t j, uint16_t k)
{
if (chunks.find(index) == chunks.end())
{
Chunk::Chunk *c = new Chunk::Chunk(glm::vec3(i, j, k));
chunks.insert(std::make_pair(index, c));
}
else
{
Chunk::Chunk *c = chunks.at(index);
// Method for chunk meshing thread(s)
void mesh(){
while(should_run){
ChunkPQEntry entry;
if(chunks_to_mesh_queue.try_pop(entry)){
Chunk::Chunk* chunk = entry.first;
chunkmesher::mesh(chunk);
chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false);
}
}
chunks_to_mesh_queue.clear();
}
if (!(c->mutex_state.try_lock()))
return;
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority){
c->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, true);
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_NORMAL));
}
if (!c->getState(Chunk::CHUNK_STATE_GENERATED))
{
if (f & 1)
to_generate.insert(c);
}
else
{
if (!c->getState(Chunk::CHUNK_STATE_MESHED))
{
if (f & 2)
to_mesh.insert(c);
}
else
{
if (!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) chunkmesher::sendtogpu(c);
oneapi::tbb::concurrent_queue<chunk_index_t> chunks_todelete;
void update(){
while(should_run) {
/* Setup variables for the whole loop */
// Atomic is needed by parallel_for
std::atomic_int nUnloaded{0}, nMarkUnload{0}, nExplored{0}, nMeshed{0}, nGenerated{0};
std::atomic_int chunkX=static_cast<int>(theCamera.getAtomicPosX() / CHUNK_SIZE);
std::atomic_int chunkY=static_cast<int>(theCamera.getAtomicPosY() / CHUNK_SIZE);
std::atomic_int chunkZ=static_cast<int>(theCamera.getAtomicPosZ() / CHUNK_SIZE);
// Frustum Culling of chunk
total++;
/* Process update messages before anything happens */
WorldUpdateMsg msg;
while(WorldUpdateQueue.try_pop(msg)){
switch(msg.msg_type){
case WorldUpdateMsgType::BLOCKPICK_BREAK:
case WorldUpdateMsgType::BLOCKPICK_PLACE:
blockpick(msg);
break;
}
}
glm::vec3 chunk = c->getPosition();
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0);
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
// Check if all the corners of the chunk are outside any of the planes
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
// plane against each corner of the chunk
bool out=false;
int a{0};
for(int p = 0; p < 6; p++){
a = 0;
for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
+ ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0;
/* Delete old chunks */
// In my head it makes sense to first delete old chunks, then create new ones
// I think it's easier for memory allocator to re-use the memory that was freed just
// before, but this isn't backed be any evidence and I might be wrong. Anyway this way
// works fine so I'm gonna keep it.
chunk_index_t i;
ChunkTable::accessor a;
while(chunks_todelete.try_pop(i)){
const chunk_index_t index = i;
if(chunks.find(a, index)){
Chunk::Chunk* c = a->second;
// Use the accessor to erase the element
// Using the key doesn't work
if(chunks.erase(a)){
nUnloaded++;
renderer::getDeleteIndexQueue().push(index);
delete c;
} else {
c->setState(Chunk::CHUNK_STATE_IN_DELETING_QUEUE, false);
std::cout << "failed to delete " << index << std::endl;
}
} else std::cout << "no such element found to delete\n";
}
if(a==8){
out=true;
break;
/* Create new chunks around the player */
for(int i = 0; i < chunks_volume; i++) {
const chunk_intcoord_t x = chunks_indices[i][0] + chunkX;
const chunk_intcoord_t y = chunks_indices[i][1] + chunkY;
const chunk_intcoord_t z = chunks_indices[i][2] + chunkZ;
if(x < 0 || y < 0 || z < 0 || x > 1023 || y > 1023 || z > 1023) continue;
nExplored++;
const chunk_index_t index = Chunk::calculateIndex(x, y, z);
ChunkTable::accessor a;
if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new
Chunk::Chunk(glm::vec3(x,y,z))));
}
/* Update all the chunks */
oneapi::tbb::parallel_for(chunks.range(), [&](ChunkTable::range_type &r){
for(ChunkTable::iterator a = r.begin(); a != r.end(); a++){
Chunk::Chunk* c = a->second;
int x = c->getPosition().x;
int y = c->getPosition().y;
int z = c->getPosition().z;
int distx = x - chunkX;
int disty = y - chunkY;
int distz = z - chunkZ;
// Local variables avoid continously having to call atomic variables
int gen{0}, mesh{0}, unload{0};
if(
distx >= -RENDER_DISTANCE && distx < RENDER_DISTANCE &&
disty >= -RENDER_DISTANCE && disty < RENDER_DISTANCE &&
distz >= -RENDER_DISTANCE && distz < RENDER_DISTANCE
){
// If within distance
// Reset out-of-view flags
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false);
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
// If not yet generated
if(!c->getState(Chunk::CHUNK_STATE_GENERATED)){
if(c->isFree()){
// Generate
// Mark as present in the queue before sending to avoid strange
// a chunk being marked as in the queue after it was already
// processed
c->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, true);
chunks_to_generate_queue.push(std::make_pair(c, GENERATION_PRIORITY_NORMAL));
}
}else{
gen++;
// If generated but not yet meshed
if(!c->getState(Chunk::CHUNK_STATE_MESHED)){
ChunkTable::accessor a1;
// Checking if nearby chunks have been generated allows for seamless
// borders between chunks
if(c->isFree() &&
(distx+1 >= RENDER_DISTANCE || x + 1 > 1023 || (chunks.find(a1, Chunk::calculateIndex(x+1, y, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(distx-1 < -RENDER_DISTANCE || x - 1 < 0 || (chunks.find(a1, Chunk::calculateIndex(x-1, y, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(disty+1 >= RENDER_DISTANCE || y + 1 > 1023 || (chunks.find(a1, Chunk::calculateIndex(x, y+1, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(disty-1 < -RENDER_DISTANCE || y - 1 < 0|| (chunks.find(a1, Chunk::calculateIndex(x, y-1, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(distz+1 >= RENDER_DISTANCE || z + 1 > 1023 || (chunks.find(a1, Chunk::calculateIndex(x, y, z+1)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(distz-1 < -RENDER_DISTANCE || z - 1 < 0|| (chunks.find(a1, Chunk::calculateIndex(x, y, z-1)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED)))
)
{
// Mesh
// Mark as present in the queue before sending to avoid strange
// a chunk being marked as in the queue after it was already
// processed
send_to_chunk_meshing_thread(c, MESHING_PRIORITY_NORMAL);
}
}else mesh++;
}
}else{
// If not within distance
if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){
// If enough time has passed, set to be deleted
if(c->isFree() && glfwGetTime() - c->unload_timer >= UNLOAD_TIMEOUT){
c->setState(Chunk::CHUNK_STATE_IN_DELETING_QUEUE, true);
chunks_todelete.push(c->getIndex());
unload++;
}
}else{
// Mark as out of view, and start waiting time
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true);
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
c->unload_timer = glfwGetTime();
}
}
if (!out)
{
toGpu++;
chunkmesher::draw(c, model);
}
}
}
c->mutex_state.unlock();
}
// Update atomic variables only once at the end
nGenerated += gen;
nMeshed += mesh;
nMarkUnload += unload;
}
});
debug::window::set_parameter("update_chunks_total", (int)chunks.size());
debug::window::set_parameter("update_chunks_generated", (int) nGenerated);
debug::window::set_parameter("update_chunks_meshed", (int) nMeshed);
debug::window::set_parameter("update_chunks_freed", (int) nUnloaded);
debug::window::set_parameter("update_chunks_explored", (int) nExplored);
}
}
void blockpick(bool place){
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
void stop() {
should_run=false;
std::cout << "Waiting for secondary threads to shut down" << std::endl;
update_thread.join();
std::cout << "Update thread has terminated" << std::endl;
gen_thread.join();
std::cout << "Generation thread has terminated" << std::endl;
mesh_thread.join();
std::cout << "Meshing thread has terminated" << std::endl;
}
void destroy(){
for(const auto& n : chunks){
delete n.second;
}
chunks.clear();
}
glm::vec3 ray_intersect(glm::vec3 startposition, glm::vec3 startdir){
int old_bx{0}, old_by{0}, old_bz{0};
int old_px{0}, old_py{0}, old_pz{0};
Chunk::Chunk* old_chunk{nullptr};
glm::vec3 old_pos;
// cast a ray from the camera in the direction pointed by the camera itself
glm::vec3 pos = cameraPos;
glm::vec3 origin = startposition;
glm::vec3 pos = origin;
glm::vec3 front = startdir;
for(float t = 0.0; t <= 10.0; t += 0.5){
// traverse the ray a block at the time
pos = theCamera.getPos() + t * theCamera.getFront();
pos = origin + t*front;
// get which chunk and block the ray is at
int px = ((int)(pos.x))/CHUNK_SIZE;
int py = ((int)(pos.y))/CHUNK_SIZE;
@ -285,75 +306,219 @@ namespace chunkmanager
int by = pos.y - py*CHUNK_SIZE;
int bz = pos.z - pz*CHUNK_SIZE;
if(bx == old_bx && by == old_by && bz == old_bz) continue;
// exit early if the position is invalid or the chunk does not exist
if(px < 0 || py < 0 || pz < 0) return;
if(chunks.find(calculateIndex(px, py, pz)) == chunks.end()) return;
if(px < 0 || py < 0 || pz < 0 || px >= 1024 || py >= 1024 || pz >= 1024) continue;
ChunkTable::const_accessor a;
if(!chunks.find(a, Chunk::calculateIndex(px, py, pz))) continue;
Chunk::Chunk* c = a->second;
if(!c->isFree() || !c->getState(Chunk::CHUNK_STATE_GENERATED)){
a.release();
continue;
}
Chunk::Chunk* c = chunks.at(calculateIndex(px, py, pz));
Block b = c->getBlock(bx, by, bz);
a.release();
// if the block is non empty
if(b != Block::AIR){
if(b != Block::AIR) return pos;
// if placing a new block
if(place){
// Go half a block backwards on the ray, to check the block where the ray was
// coming from
// Doing this and not using normal adds the unexpected (and unwanted) ability to
// place blocks diagonally, without faces colliding with the block that has
// been clicked
pos -= theCamera.getFront()*0.5f;
int px1 = ((int)(pos.x))/CHUNK_SIZE;
int py1 = ((int)(pos.y))/CHUNK_SIZE;
int pz1 = ((int)(pos.z))/CHUNK_SIZE;
int bx1 = pos.x - px1*CHUNK_SIZE;
int by1 = pos.y - py1*CHUNK_SIZE;
int bz1 = pos.z - pz1*CHUNK_SIZE;
old_chunk = c;
old_bx = bx;
old_by = by;
old_bz = bz;
old_px = px;
old_py = py;
old_pz = pz;
old_pos = pos;
// exit early if the position is invalid or the chunk does not exist
if(px1 < 0 || py1 < 0 || pz1 < 0) return;
if(chunks.find(calculateIndex(px1, py1, pz1)) == chunks.end()) return;
}
return glm::vec3(-1);
}
Chunk::Chunk* c1 = chunks.at(calculateIndex(px1, py1, pz1));
// place the new block (only stone for now)
c1->setBlock( Block::STONE, bx1, by1, bz1);
void blockpick(WorldUpdateMsg& msg){
//std::cout << glm::to_string(ray_intersect(msg.cameraPos, msg.cameraFront)) << std::endl;
glm::vec3 ray_pos = ray_intersect(msg.cameraPos, msg.cameraFront);
if(ray_pos == glm::vec3(-1)) return;
// update the mesh of the chunk
chunkmesher::mesh(c1);
// mark the mesh of the chunk the be updated on the gpu
c1->setState(Chunk::CHUNK_STATE_MESH_LOADED, false);
// Chunk in which the blockpick is happening
int chunkx = (int)(ray_pos.x) / CHUNK_SIZE;
int chunky = (int)(ray_pos.y) / CHUNK_SIZE;
int chunkz = (int)(ray_pos.z) / CHUNK_SIZE;
// Block (chunk coord) in which the blockpick is happening
int blockx = ray_pos.x - chunkx*CHUNK_SIZE;
int blocky = ray_pos.y - chunky*CHUNK_SIZE;
int blockz = ray_pos.z - chunkz*CHUNK_SIZE;
// The chunk must exist, otherwise ray_intersect would have returned an error
// Also, the block must be different from AIR
ChunkTable::accessor a;
if(!chunks.find(a, Chunk::calculateIndex(chunkx, chunky, chunkz))) return;
Chunk::Chunk* c = a->second;
if(!(c->isFree() && c->getState(Chunk::CHUNK_STATE_GENERATED))) return;
if(msg.msg_type == WorldUpdateMsgType::BLOCKPICK_BREAK){
c->setBlock(Block::AIR, blockx, blocky, blockz);
send_to_chunk_meshing_thread(c, MESHING_PRIORITY_PLAYER_EDIT);
}else{
// Traverse voxel using Amanatides&Woo traversal algorithm
// http://www.cse.yorku.ca/~amana/research/grid.pdf
glm::vec3 pos = msg.cameraPos;
glm::vec3 front = glm::normalize(pos - ray_pos);
// Original chunk in which the blockpick started
const int ochunkX=chunkx, ochunkY = chunky, ochunkZ = chunkz;
// The ray has equation pos + t*front
// Initialize phase
// Origin integer voxel coordinates
// Avoid floating point accuracy errors: work as close to 0 as possible, translate
// everything later
int basex = std::floor(ray_pos.x);
int basey = std::floor(ray_pos.y);
int basez = std::floor(ray_pos.z);
double x = ray_pos.x - basex;
double y = ray_pos.y - basey;
double z = ray_pos.z - basez;
auto sign = [=](double f){ return f > 0 ? 1 : f < 0 ? -1 : 0; };
auto tmax = [=](double p, double dir){
int s = sign(dir);
if(s > 0)
return (1 - p) / dir;
else if(s < 0)
return -(p) / dir;
return 0.0;
};
// Step
int stepX = sign(front.x);
int stepY = sign(front.y);
int stepZ = sign(front.z);
// tMax: the value of t at which the ray crosses the first voxel boundary
double tMaxX = tmax(x, front.x);
double tMaxY = tmax(y, front.y);
double tMaxZ = tmax(z, front.z);
// tDelta: how far along the ray along they ray (in units of t) for the _ component of such
// a movement to equal the width of a voxel
double tDeltaX = stepX / front.x;
double tDeltaY = stepY / front.y;
double tDeltaZ = stepZ / front.z;
for(int i = 0; i < 10; i++){
if(tMaxX < tMaxY){
if(tMaxX < tMaxZ) {
x += stepX;
tMaxX += tDeltaX;
}else{
z += stepZ;
tMaxZ += tDeltaZ;
}
}else{
// replace the current block with air to remove it
c->setBlock( Block::AIR, bx, by, bz);
// update the mesh of the chunk
chunkmesher::mesh(c);
// mark the mesh of the chunk the be updated on the gpu
c->setState(Chunk::CHUNK_STATE_MESH_LOADED, false);
if(tMaxY < tMaxZ){
y += stepY;
tMaxY += tDeltaY;
}else{
z += stepZ;
tMaxZ += tDeltaZ;
}
}
int realx = basex + x;
int realy = basey + y;
int realz = basez + z;
chunkx = realx / CHUNK_SIZE;
chunky = realy / CHUNK_SIZE;
chunkz = realz / CHUNK_SIZE;
if(chunkx < 0 || chunky < 0 || chunkz < 0 || chunkx > 1023 || chunky > 1023 ||
chunkz > 1023) continue;
blockx = realx - chunkx*CHUNK_SIZE;
blocky = realy - chunky*CHUNK_SIZE;
blockz = realz - chunkz*CHUNK_SIZE;
Chunk::Chunk* chunk;
ChunkTable::accessor b;
if(chunkx != ochunkX || chunky != ochunkY || chunkz != ochunkZ){
if(!chunks.find(b, Chunk::calculateIndex(chunkx, chunky, chunkz)))
continue;
chunk = b->second;
if(!(chunk->isFree() && chunk->getState(Chunk::CHUNK_STATE_GENERATED)))
continue;
}else{
chunk = c;
}
if(chunk->getBlock(blockx, blocky, blockz) != Block::AIR) continue;
chunk->setBlock(msg.block, blockx, blocky, blockz);
send_to_chunk_meshing_thread(chunk, MESHING_PRIORITY_PLAYER_EDIT);
break;
}
}
// Release the chunk in which the blockpick started to avoid locks
a.release();
// When necessary, also mesh nearby chunks
ChunkTable::accessor a1, a2, b1, b2, c1, c2;
if(blockx == 0 && chunkx - 1 >= 0 && chunks.find(a1, Chunk::calculateIndex(chunkx - 1, chunky, chunkz)))
send_to_chunk_meshing_thread(a1->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blocky == 0 && chunky - 1 >= 0 && chunks.find(b1, Chunk::calculateIndex(chunkx, chunky - 1, chunkz)))
send_to_chunk_meshing_thread(b1->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blockz == 0 && chunkz - 1 >= 0 && chunks.find(c1, Chunk::calculateIndex(chunkx, chunky, chunkz - 1)))
send_to_chunk_meshing_thread(c1->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blockx == CHUNK_SIZE - 1 && chunkx +1 < 1024 && chunks.find(a2, Chunk::calculateIndex(chunkx +1, chunky, chunkz)))
send_to_chunk_meshing_thread(a2->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blocky == CHUNK_SIZE - 1 && chunky +1 < 1024 && chunks.find(b2, Chunk::calculateIndex(chunkx, chunky +1, chunkz)))
send_to_chunk_meshing_thread(b2->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blockz == CHUNK_SIZE - 1 && chunkz +1 < 1024 && chunks.find(c2, Chunk::calculateIndex(chunkx, chunky, chunkz +1)))
send_to_chunk_meshing_thread(c2->second, MESHING_PRIORITY_PLAYER_EDIT);
// Update debugging information
debug::window::set_parameter("block_last_action", msg.msg_type ==
WorldUpdateMsgType::BLOCKPICK_PLACE);
debug::window::set_parameter("block_last_action_block_type", (int)(msg.msg_type ==
WorldUpdateMsgType::BLOCKPICK_PLACE ? msg.block : Block::AIR));
debug::window::set_parameter("block_last_action_x", chunkx*CHUNK_SIZE+blockx);
debug::window::set_parameter("block_last_action_y", chunky*CHUNK_SIZE+blocky);
debug::window::set_parameter("block_last_action_z", chunkz*CHUNK_SIZE+blockz);
}
// uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1024). There's actually two spare bits
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k){
return i | (j << 10) | (k << 20);
}
Block getBlockAtPos(int x, int y, int z){
if(x < 0 || y < 0 || z < 0) return Block::NULLBLK;
void destroy()
{
for (auto &n : chunks)
delete n.second;
}
int cx = static_cast<int>(x / CHUNK_SIZE);
int cy = static_cast<int>(y / CHUNK_SIZE);
int cz = static_cast<int>(z / CHUNK_SIZE);
void stopGenThread(){
generate_should_run = false;
}
if(cx < 0 || cy < 0 || cz < 0 || cx > 1023 || cy > 1023 || cz > 1023) return Block::NULLBLK;
void stopMeshThread(){
mesh_should_run = false;
}
//std::cout << "Block at " << x << ", " << y << ", " << z << " is in chunk " << cx << "," << cy << "," << cz << "\n";
ChunkTable::accessor a;
if(!chunks.find(a, Chunk::calculateIndex(cx, cy, cz))) return Block::NULLBLK;
else {
int bx = x % CHUNK_SIZE;
int by = y % CHUNK_SIZE;
int bz = z % CHUNK_SIZE;
Block b = a->second->getBlock(bx, by, bz);
//std::cout << "Block is at " << bx << "," << by << "," << bz << "(" << (int)b << ")\n";
return b;
}
}
};

View File

@ -1,21 +1,35 @@
#include "chunkmesher.hpp"
#include <array>
#include <memory>
#include "block.hpp"
#include "chunk.hpp"
#include "chunkmesher.hpp"
#include "chunkmanager.hpp"
#include "globals.hpp"
#include "renderer.hpp"
#include "spacefilling.hpp"
#include "utils.hpp"
#include <unordered_map>
#include <memory>
#include <tuple>
#define CHUNK_MESH_DATA_QUANTITY 100
#define CHUNK_MESH_WORLD_LIMIT_BORDERS 0
namespace chunkmesher{
int indexCount{0};
ChunkMeshDataQueue MeshDataQueue;
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
void init()
{
for(int i = 0; i < CHUNK_MESH_DATA_QUANTITY; i++)
MeshDataQueue.push(new ChunkMeshData{});
}
void mesh(Chunk::Chunk* chunk)
{
ChunkMeshData* mesh_data;
if(!MeshDataQueue.try_pop(mesh_data)) return;
/*
* Taking inspiration from 0fps and the jme3 porting at
@ -33,30 +47,27 @@ void mesh(Chunk::Chunk* chunk)
*/
// Cleanup previous data
chunk->vertices_map.clear();
chunk->index_to_vertex.clear();
indexCount = 0;
chunk->vertices.clear();
chunk->indices.clear();
chunk->colors.clear();
chunk->nIndices = 0;
// Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) return;
mesh_data->clear();
mesh_data->message_type = ChunkMeshDataType::MESH_UPDATE;
mesh_data->index = chunk->getIndex();
mesh_data->position = chunk->getPosition();
// convert tree to array since it is easier to work with it
int length{0};
std::unique_ptr<Block[]> blocks = chunk->getBlocksArray(&length);
if(length == 0) {
return;
}
std::unique_ptr<Block[]> blocks;
int k, l, u, v, w, h, n, j, i;
int x[]{0, 0, 0};
int q[]{0, 0, 0};
int du[]{0, 0, 0};
int dv[]{0, 0, 0};
// Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) goto end;
blocks = chunk->getBlocksArray(&length);
if(length == 0) goto end;
std::array<Block, CHUNK_SIZE * CHUNK_SIZE> mask;
for (bool backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b)
{
@ -86,25 +97,48 @@ void mesh(Chunk::Chunk* chunk)
{
for (x[u] = 0; x[u] < CHUNK_SIZE; x[u]++)
{
Block b1 = (x[dim] >= 0) ? blocks[HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]] : Block::NULLBLK;
Block b2 = (x[dim] < CHUNK_SIZE - 1)
? blocks[HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1] + q[1]][x[2] + q[2]]]
: Block::NULLBLK;
Block b1, b2;
if(x[dim] >= 0) b1 = blocks[HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]];
else{
int cx = chunk->getPosition().x*CHUNK_SIZE;
int cy = chunk->getPosition().y*CHUNK_SIZE;
int cz = chunk->getPosition().z*CHUNK_SIZE;
// This is the original line taken from rob's code, readapted (replace voxelFace
// with b1 and b2).
// mask[n++] = ((voxelFace != Block::NULLBLK && voxelFace1 != Block::NULLBLK &&
// voxelFace.equals(voxelFace1))) ? Block::NULLBLK : backFace ? voxelFace1 : voxelFace;
int bx = cx+x[0];
int by = cy+x[1];
int bz = cz+x[2];
// Additionally checking whether b1 and b2 are AIR or Block::NULLBLK allows face culling,
// thus not rendering faces that cannot be seen
// Removing the control for Block::NULLBLK disables chunk borders, which is
// not always wanted and needs further checking
// This can be surely refactored in something that isn't such a big one-liner
mask[n++] = b1 != Block::NULLBLK && b2 != Block::NULLBLK && b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::AIR || b1 == Block::NULLBLK ? b2 : Block::NULLBLK
: b2 == Block::AIR || b2 == Block::NULLBLK ? b1
: Block::NULLBLK;
b1 = chunkmanager::getBlockAtPos(bx, by, bz);
}
if(x[dim] < CHUNK_SIZE - 1) b2 = blocks[HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1]
+ q[1]][x[2] + q[2]]];
else{
int cx = chunk->getPosition().x*CHUNK_SIZE;
int cy = chunk->getPosition().y*CHUNK_SIZE;
int cz = chunk->getPosition().z*CHUNK_SIZE;
int bx = cx+x[0] + q[0];
int by = cy+x[1] + q[1];
int bz = cz+x[2] + q[2];
b2 = chunkmanager::getBlockAtPos(bx, by, bz);
}
// Compute the mask
// Checking if b1==b2 is needed to generate a single quad
// The else case provides face culling for adjacent solid faces
// Checking for NULLBLK avoids creating empty faces if nearby chunk was not
// yet generated
#if CHUNK_MESH_WORLD_LIMIT_BORDERS == 1
mask[n++] = b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::NULLBLK || b1 == Block::AIR ? b2 : Block::NULLBLK
: b2 == Block::NULLBLK || b2 == Block::AIR ? b1 : Block::NULLBLK;
#else
mask[n++] = b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::AIR ? b2 : Block::NULLBLK
: b2 == Block::AIR ? b1 : Block::NULLBLK;
#endif
}
}
@ -153,13 +187,19 @@ void mesh(Chunk::Chunk* chunk)
dv[2] = 0;
dv[v] = h;
quad(chunk, glm::vec3(x[0], x[1], x[2]),
glm::vec3(x[0] + du[0], x[1] + du[1], x[2] + du[2]),
glm::vec3(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1],
x[2] + du[2] + dv[2]),
glm::vec3(x[0] + dv[0], x[1] + dv[1], x[2] + dv[2]),
glm::vec3(backFace ? q[0] : -q[0], backFace ? q[1] : -q[1], backFace ? q[2] : -q[2] ),
mask[n], backFace);
// bottom left
mesh_data->vertices.push_back(x[0]); //bottomLeft.x
mesh_data->vertices.push_back(x[1]); //bottomLeft.y
mesh_data->vertices.push_back(x[2]); //bottomLeft.z
// extents, use normals for now
mesh_data->extents.push_back(du[0] + dv[0]);
mesh_data->extents.push_back(du[1] + dv[1]);
mesh_data->extents.push_back(du[2] + dv[2]);
mesh_data->texinfo.push_back(backFace ? 0.0 : 1.0);
mesh_data->texinfo.push_back((int)(mask[n]) - 2);
mesh_data->num_vertices++;
}
for (l = 0; l < h; ++l)
@ -187,174 +227,9 @@ void mesh(Chunk::Chunk* chunk)
}
}
}
end:
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
renderer::getMeshDataQueue().push(mesh_data);
}
void sendtogpu(Chunk::Chunk* chunk)
{
if (chunk->indices.size() > 0)
{
for(int i = 0; i < chunk->index_to_vertex.size(); i++){
glm::vec3 v = chunk->index_to_vertex[i];
auto t = chunk->vertices_map.at(v);;
glm::vec3 n = std::get<1>(t);
glm::vec3 c = std::get<2>(t);
chunk->vertices.push_back(v.x);
chunk->vertices.push_back(v.y);
chunk->vertices.push_back(v.z);
chunk->vertices.push_back(n.x);
chunk->vertices.push_back(n.y);
chunk->vertices.push_back(n.z);
chunk->colors.push_back(c.x);
chunk->colors.push_back(c.y);
chunk->colors.push_back(c.z);
}
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(chunk->VAO);
glBindBuffer(GL_ARRAY_BUFFER, chunk->VBO);
glBufferData(GL_ARRAY_BUFFER, chunk->vertices.size() * sizeof(GLfloat), &(chunk->vertices[0]), GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
// normal attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3*
sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, chunk->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, chunk->indices.size() * sizeof(GLuint), &(chunk->indices[0]), GL_STATIC_DRAW);
// color attribute
glBindBuffer(GL_ARRAY_BUFFER, chunk->colorBuffer);
glBufferData(GL_ARRAY_BUFFER, chunk->colors.size() * sizeof(GLfloat), &(chunk->colors[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glBindVertexArray(0);
// save the number of indices of the mesh, it is needed later for drawing
chunk->nIndices = (GLuint)(chunk->indices.size());
// once data has been sent to the GPU, it can be cleared from system RAM
chunk->vertices_map.clear();
chunk->index_to_vertex.clear();
chunk->vertices.clear();
chunk->indices.clear();
chunk->colors.clear();
}
// mark the chunk mesh has loaded on GPU
chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true);
}
void draw(Chunk::Chunk* chunk, glm::mat4 model)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode
if(chunk->getState(Chunk::CHUNK_STATE_MESH_LOADED))
{
theShader->use();
theShader->setMat4("model", model);
theShader->setMat4("view", theCamera.getView());
theShader->setMat4("projection", theCamera.getProjection());
glBindVertexArray(chunk->VAO);
glDrawElements(GL_TRIANGLES, chunk->nIndices , GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
}
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight,
glm::vec3 bottomRight, glm::vec3 normal, Block block, bool backFace)
{
// ugly switch case for colors
glm::vec3 color = glm::vec3(0.0f);
switch (block)
{
case Block::STONE:
color = glm::vec3(0.588f);
break;
case Block::GRASS:
color = glm::vec3(0.05f, 0.725f, 0.0f);
break;
case Block::DIRT:
color = glm::vec3(0.152f, 0.056f, 0.056f);
break;
}
int ibottomLeft, ibottomRight, itopLeft, itopRight;
if(chunk->vertices_map.find(bottomLeft) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(bottomLeft);
ibottomLeft = indexCount++;
chunk->vertices_map[bottomLeft] = std::make_tuple(ibottomLeft, normal, color);
}else{
auto vm = chunk->vertices_map[bottomLeft];
ibottomLeft = std::get<0>(vm);
chunk->vertices_map[bottomLeft] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
if(chunk->vertices_map.find(bottomRight) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(bottomRight);
ibottomRight = indexCount++;
chunk->vertices_map[bottomRight] = std::make_tuple(ibottomRight, normal, color);
}else{
auto vm = chunk->vertices_map[bottomRight];
ibottomRight = std::get<0>(vm);
chunk->vertices_map[bottomRight] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
if(chunk->vertices_map.find(topLeft) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(topLeft);
itopLeft = indexCount++;
chunk->vertices_map[topLeft] = std::make_tuple(itopLeft, normal, color);
}else{
auto vm = chunk->vertices_map[topLeft];
itopLeft = std::get<0>(vm);
chunk->vertices_map[topLeft] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
if(chunk->vertices_map.find(topRight) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(topRight);
itopRight = indexCount++;
chunk->vertices_map[topRight] = std::make_tuple(itopRight, normal, color);
}else{
auto vm = chunk->vertices_map[topRight];
itopRight = std::get<0>(vm);
chunk->vertices_map[topRight] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
// bottomLeft, bottomRight, topLeft, topRight
if (backFace)
{
chunk->indices.push_back(itopLeft);
chunk->indices.push_back(ibottomLeft);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(itopRight);
chunk->indices.push_back(itopLeft);
}
else
{
chunk->indices.push_back(itopLeft);
chunk->indices.push_back(itopRight);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(ibottomLeft);
chunk->indices.push_back(itopLeft);
}
}
};

66
src/controls.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "controls.hpp"
#include "camera.hpp"
#include "chunkmanager.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
namespace controls{
/* Block picking */
int block_to_place{2};
float lastBlockPick=0.0;
bool blockpick = false;
/* Cursor */
bool cursor = false;
void init(){
debug::window::set_parameter("block_type_return", &block_to_place);
}
void update(GLFWwindow* window){
float current_time = glfwGetTime();
/* BlockPicking */
// Reset blockpicking if enough time has passed
if(current_time - lastBlockPick > BLOCKPICK_TIMEOUT) blockpick = false;
// Reset blockpicking if both mouse buttons are released
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_RELEASE && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_RELEASE) blockpick = false;
// Process block picking if a mouse button is pressed
if((glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS ||
glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2 == GLFW_PRESS)) && !blockpick){
// Start timeout for next block pick action
blockpick = true;
lastBlockPick = current_time;
// Construct the message to send to chunkmanager
// WorldUpdateMsg is allocated on the stack
// unlike ChunkMeshData, the fields of WorldUpdateMsg are few and light, so there's no
// problem in passing them by value each time.
// It also has the advantage of having less memory to manage, since I'm not allocating
// anything on the heap
WorldUpdateMsg msg{};
msg.cameraPos = theCamera.getPos();
msg.cameraFront = theCamera.getFront();
msg.time = current_time;
msg.msg_type = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS ?
WorldUpdateMsgType::BLOCKPICK_PLACE : WorldUpdateMsgType::BLOCKPICK_BREAK;
msg.block = (Block)(block_to_place);
// Send to chunk manager
chunkmanager::getWorldUpdateQueue().push(msg);
}
/* SCREENSHOTS */
if(glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) renderer::saveScreenshot();
if(glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) renderer::saveScreenshot(true);
if(glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS) {
cursor = !cursor;
glfwSetInputMode(window, GLFW_CURSOR, cursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
}
};

136
src/debugwindow.cpp Normal file
View File

@ -0,0 +1,136 @@
#include "debugwindow.hpp"
#include <imgui/imgui.h>
#include <imgui/imgui_impl_opengl3.h>
#include <imgui/imgui_impl_glfw.h>
#include <imgui_stdlib.h>
#include <iostream>
#include <string>
#include <unordered_map>
namespace debug{
namespace window{
void show_debug_window();
constexpr int frametimes_array_size = 20;
float frametimes_array[frametimes_array_size]{};
std::unordered_map<std::string, std::any> parameters;
void init(GLFWwindow* window){
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true); // Second param install_callback=true will install GLFW callbacks and chain to existing ones.
ImGui_ImplOpenGL3_Init();
}
void prerender(){
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
//ImGui::ShowDemoWindow(); // Show demo window! :)
show_debug_window();
}
void render(){
// (Your code clears your framebuffer, renders your other stuff etc.)
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// (Your code calls glfwSwapBuffers() etc.)
}
void destroy(){
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
void set_parameter(std::string key, std::any value){
parameters[key] = value;
}
void show_debug_window(){
ImGui::Begin("Debug Window");
ImGui::PushItemWidth(ImGui::GetFontSize() * -12);
try{
if (ImGui::CollapsingHeader("Frametimes")){
ImGui::Text("FPS: %d", std::any_cast<int>(parameters.at("fps")));
ImGui::Text("Frametime (ms): %f",
std::any_cast<float>(parameters.at("frametime"))*1000);
ImGui::Text("GPU: (%s) %s",
std::any_cast<const GLubyte*>(parameters.at("gpu_vendor")),
std::any_cast<const GLubyte*>(parameters.at("gpu_renderer")));
//ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr);
}
if(ImGui::CollapsingHeader("Player")){
ImGui::Text("X: %f, Y: %f, Z: %f",
std::any_cast<float>(parameters.at("px")),std::any_cast<float>(parameters.at("py")),std::any_cast<float>(parameters.at("pz")) );
ImGui::Text("X: %d, Y: %d, Z: %d (chunk)", std::any_cast<int>(parameters.at("cx")),std::any_cast<int>(parameters.at("cy")),std::any_cast<int>(parameters.at("cz")) );
ImGui::Text("Pointing in direction: %f, %f, %f",
std::any_cast<float>(parameters.at("lx")),std::any_cast<float>(parameters.at("ly")),std::any_cast<float>(parameters.at("lz")) );
ImGui::SliderInt("Crosshair type",
std::any_cast<int*>(parameters.at("crosshair_type_return")), 0, 1);
ImGui::SliderInt("Block to place",
std::any_cast<int*>(parameters.at("block_type_return")), 2, 6);
if(parameters.find("block_last_action") != parameters.end()){
ImGui::Text("Last Block action: %s",
std::any_cast<bool>(parameters.at("block_last_action")) ? "place" : "destroy");
ImGui::Text("Last Block action block type: %d",
std::any_cast<int>(parameters.at("block_last_action_block_type")));
ImGui::Text("Last Block action position: X: %d, Y: %d, Z: %d",
std::any_cast<int>(parameters.at("block_last_action_x")),std::any_cast<int>(parameters.at("block_last_action_y")),std::any_cast<int>(parameters.at("block_last_action_z")) );
}
}
if(ImGui::CollapsingHeader("Mesh")){
ImGui::Text("Total chunk meshed: %d",
std::any_cast<int>(parameters.at("render_chunks_total")));
ImGui::Text("Of which renderable (not empty): %d",
std::any_cast<int>(parameters.at("render_chunks_renderable")));
ImGui::Text("Chunks rendered: %d",
std::any_cast<int>(parameters.at("render_chunks_rendered")));
ImGui::Text("Frustum culled: %d",
std::any_cast<int>(parameters.at("render_chunks_culled")));
ImGui::Text("Total vertices in the scene: %d",
std::any_cast<int>(parameters.at("render_chunks_vertices")));
ImGui::Checkbox("Wireframe",
std::any_cast<bool*>(parameters.at("wireframe_return")));
}
if(ImGui::CollapsingHeader("Chunks")){
ImGui::Text("Total chunks present: %d",
std::any_cast<int>(parameters.at("update_chunks_total")));
ImGui::Text("Chunks generated: %d",
std::any_cast<int>(parameters.at("update_chunks_generated")));
ImGui::Text("Chunks meshed: %d",
std::any_cast<int>(parameters.at("update_chunks_meshed")));
ImGui::Text("Chunks actually freed from memory: %d",
std::any_cast<int>(parameters.at("update_chunks_freed")));
ImGui::Text("Chunks explored: %d",
std::any_cast<int>(parameters.at("update_chunks_explored")));
}
}catch(const std::bad_any_cast& e){
std::cout << e.what() << std::endl;
}catch(const std::out_of_range& e){
std::cout << e.what() << std::endl;
}
ImGui::End();
}
}
}

View File

@ -1,25 +1,23 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "main.hpp"
#include <iostream>
#include <thread>
#include "chunkmanager.hpp"
#include "main.hpp"
#include "spacefilling.hpp"
#include "shader.hpp"
#define GLOBALS_DEFINER
#include "globals.hpp"
#undef GLOBALS_DEFINER
#include "chunkmanager.hpp"
#include "controls.hpp"
#include "debugwindow.hpp"
#include "renderer.hpp"
#include "shader.hpp"
#include "spacefilling.hpp"
float deltaTime = 0.0f; // Time between current frame and last frame
float lastFrame = 0.0f; // Time of last frame
float lastFPSFrame = 0.0f;
int frames = 0;
float lastBlockPick=0.0;
bool blockpick = false;
int main()
{
@ -42,6 +40,7 @@ int main()
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(0);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
@ -53,17 +52,23 @@ int main()
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
//glEnable(GL_FRAMEBUFFER_SRGB); //gamma correction done in fragment shader
//glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
std::cout << "Using GPU: " << glGetString(GL_VENDOR) << " " << glGetString(GL_RENDERER) << "\n";
debug::window::set_parameter("gpu_vendor", glGetString(GL_VENDOR));
debug::window::set_parameter("gpu_renderer", glGetString(GL_RENDERER));
for(int i = 0; i < 360; i++){
sines[i] = sin(3.14 / 180 * i);
cosines[i] = cos(3.14 / 180 * i);
}
SpaceFilling::initLUT();
controls::init();
chunkmanager::init();
std::thread genThread = chunkmanager::initGenThread();
std::thread meshThread = chunkmanager::initMeshThread();
theShader = new Shader{"shaders/shader.vs", "shaders/shader.fs"};
chunkmesher::init();
debug::window::init(window);
renderer::init(window);
while (!glfwWindowShouldClose(window))
{
@ -72,30 +77,42 @@ int main()
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
debug::window::set_parameter("frametime", deltaTime);
// FPS Counter
frames++;
if(currentFrame - lastFPSFrame >= 1.0f){
std::cout << "FPS: " << frames << " Frametime: " << deltaTime << std::endl;
//std::cout << "FPS: " << frames << " Frametime: " << deltaTime << std::endl;
debug::window::set_parameter("fps", frames);
frames = 0;
lastFPSFrame = currentFrame;
}
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Input handling
// Only close event is handles by main
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
// the rest of input processing is handled by controls.cpp
// Input processing
processInput(window);
controls::update(window);
// Camera
theCamera.update(window, deltaTime);
theShader->setFloat("u_time", currentFrame);
theShader->setVec3("viewPos", theCamera.getPos());
// Reset blockping timeout if 200ms have passed
if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false;
debug::window::set_parameter("px", theCamera.getPos().x);
debug::window::set_parameter("py", theCamera.getPos().y);
debug::window::set_parameter("pz", theCamera.getPos().z);
debug::window::set_parameter("cx", (int)(theCamera.getPos().x / CHUNK_SIZE));
debug::window::set_parameter("cy", (int)(theCamera.getPos().y / CHUNK_SIZE));
debug::window::set_parameter("cz", (int)(theCamera.getPos().z / CHUNK_SIZE));
debug::window::set_parameter("lx", theCamera.getFront().x);
debug::window::set_parameter("ly", theCamera.getFront().y);
debug::window::set_parameter("lz", theCamera.getFront().z);
// ChunkManager
chunkmanager::update(deltaTime);
// Render pass
renderer::render();
// Swap buffers to avoid tearing
glfwSwapBuffers(window);
@ -103,15 +120,11 @@ int main()
}
// Stop threads and wait for them to finish
chunkmanager::stopGenThread();
chunkmanager::stopMeshThread();
genThread.join();
meshThread.join();
chunkmanager::stop();
// Cleanup allocated memory
chunkmanager::destroy();
delete theShader;
renderer::destroy();
glfwTerminate();
return 0;
@ -121,31 +134,10 @@ void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
glViewport(0, 0, width, height);
theCamera.viewPortCallBack(window, width, height);
renderer::framebuffer_size_callback(window, width, height);
}
void mouse_callback(GLFWwindow *window, double xpos, double ypos)
{
theCamera.mouseCallback(window, xpos, ypos);
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){
chunkmanager::blockpick(false);
blockpick=true;
lastBlockPick=glfwGetTime();
}
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){
chunkmanager::blockpick(true);
blockpick=true;
lastBlockPick=glfwGetTime();
}
// Reset blockpicking if enough time has passed
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_RELEASE && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_RELEASE) blockpick = false;
}

344
src/renderer.cpp Normal file
View File

@ -0,0 +1,344 @@
#include "renderer.hpp"
#include <glm/ext.hpp>
#include <glm/gtx/string_cast.hpp>
#include <oneapi/tbb/concurrent_hash_map.h>
#include "chunkmanager.hpp"
#include "chunkmesher.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
namespace renderer{
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, RenderInfo*> RenderTable;
RenderTable ChunksToRender;
ChunkMeshDataQueue MeshDataQueue;
IndexQueue MeshDataToDelete;
Shader* theShader, *quadShader;
GLuint chunkTexture;
Shader* getRenderShader() { return theShader; }
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
IndexQueue& getDeleteIndexQueue(){ return MeshDataToDelete; }
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
int screenWidth, screenHeight;
int crosshair_type{0};
bool wireframe{false};
void init(GLFWwindow* window){
// Setup rendering
// We will render the image to a texture, then display the texture on a quad that fills the
// entire screen.
// This makes it easy to capture screenshots or apply filters to the final image (e.g.
// over-impress HUD elements like a crosshair)
glfwGetWindowSize(window, &screenWidth, &screenHeight);
glGenFramebuffers(1, &renderTexFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
// Depth buffer
glGenRenderbuffers(1, &renderTexDepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, screenWidth, screenHeight); //Support up to
//full-hd for now
// Attach it to the frame buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
renderTexDepthBuffer);
// Create texture to render to
// The texture we're going to render to
glGenTextures(1, &renderTex);
glBindTexture(GL_TEXTURE_2D, renderTex);
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, screenWidth, screenHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support
// up to
// full-hd
// for now
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Set the texture as a render attachment for the framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0);
// Create the quad to render the texture to
float vertices[] = {
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f
};
glGenBuffers(1, &quadVBO);
glGenVertexArrays(1, &quadVAO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
// Rendering of the world
// Create Shader
theShader = new Shader{"shaders/shader-texture.gs", "shaders/shader-texture.vs", "shaders/shader-texture.fs"};
quadShader = new Shader{nullptr, "shaders/shader-quad.vs", "shaders/shader-quad.fs"};
// Block textures
// Create 3d array texture
constexpr int layerCount = 5;
glGenTextures(1, &chunkTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, chunkTexture);
int width, height, nrChannels;
unsigned char *texels = stbi_load("textures/cobblestone.png", &width, &height, &nrChannels, 0);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height, layerCount, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels);
unsigned char *texels1 = stbi_load("textures/dirt.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels1);
unsigned char *texels2 = stbi_load("textures/grass_top.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, width, height, 1, GL_RGB, GL_UNSIGNED_BYTE, texels2);
unsigned char *texels3 = stbi_load("textures/wood.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 3, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels3);
unsigned char *texels4 = stbi_load("textures/leaves.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 4, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels4);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_REPEAT);
debug::window::set_parameter("crosshair_type_return", &crosshair_type);
debug::window::set_parameter("wireframe_return", &wireframe);
}
void render(){
// Bind the frame buffer to render to the texture
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
glViewport(0, 0, screenWidth, screenHeight);
glEnable(GL_DEPTH_TEST);
if(wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// Clear the screen
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* UPDATE IMGUI */
debug::window::prerender();
/* RENDER THE WORLD TO TEXTURE */
int total{0}, toGpu{0}, oof{0}, vertices{0};
glm::vec4 frustumPlanes[6];
theCamera.getFrustumPlanes(frustumPlanes, true);
glm::vec3 cameraPos = theCamera.getPos();
glm::vec3 cameraChunkPos = cameraPos / static_cast<float>(CHUNK_SIZE);
theShader->use();
theShader->setVec3("viewPos", cameraPos);
/* Process incoming mesh data */
ChunkMeshData* m;
while(MeshDataQueue.try_pop(m)){
RenderTable::accessor a;
RenderInfo* render_info;
if(ChunksToRender.find(a, m->index)){
render_info = a->second;
render_info->position = m->position;
render_info->num_vertices = m->num_vertices;
// Always updated the mesh, even if it's empty
// This should solve the problem of having floating quads when destroying a block
// near chunk borders
send_chunk_to_gpu(m, render_info);
}else{
render_info = new RenderInfo();
render_info->index = m->index;
render_info->position = m->position;
render_info->num_vertices = m->num_vertices;
ChunksToRender.emplace(a, std::make_pair(render_info->index, render_info));
// Only send the mesh to the GPU if it's not empty
if(render_info->num_vertices > 0) send_chunk_to_gpu(m, render_info);
}
chunkmesher::getMeshDataQueue().push(m);
}
/* Process chunks to be removed */
chunk_index_t queue_index;
while(MeshDataToDelete.try_pop(queue_index)){
RenderTable::accessor a;
if(ChunksToRender.find(a, queue_index)){
RenderInfo* render_info = a->second;
render_info->deallocateBuffers();
delete render_info;
ChunksToRender.erase(a);
}
}
/* Render the chunks */
// parallel_for cannot be used since all the rendering needs to happen in a single thread
for(RenderTable::iterator i = ChunksToRender.begin(); i != ChunksToRender.end(); i++){
RenderInfo* render_info = i->second;
if(render_info->num_vertices > 0)
{
total++;
// Increase total vertex count
vertices += render_info->num_vertices;
// Perform frustum culling and eventually render
glm::vec3 chunk = render_info->position;
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0);
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
// Check if all the corners of the chunk are outside any of the planes
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
// plane against each corner of the chunk
bool out=false;
int a{0};
for(int p = 0; p < 6; p++){
a = 0;
for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
+ ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0;
if(a==8){
out=true;
break;
}
}
if (!out)
{
theShader->setMat4("model", model);
theShader->setMat4("view", theCamera.getView());
theShader->setMat4("projection", theCamera.getProjection());
glBindVertexArray(render_info->VAO);
glDrawArrays(GL_POINTS, 0, render_info->num_vertices);
glBindVertexArray(0);
toGpu++;
}
}
}
debug::window::set_parameter("render_chunks_total", (int)(ChunksToRender.size()));
debug::window::set_parameter("render_chunks_rendered", toGpu);
debug::window::set_parameter("render_chunks_renderable", total);
debug::window::set_parameter("render_chunks_culled", total-toGpu);
debug::window::set_parameter("render_chunks_vertices", vertices);
/* DISPLAY TEXTURE ON A QUAD THAT FILLS THE SCREEN */
// Now to render the quad, with the texture on top
// Switch to the default frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, renderTex);
quadShader->use();
quadShader->setInt("screenWidth", screenWidth);
quadShader->setInt("screenHeight", screenHeight);
quadShader->setInt("crosshairType", crosshair_type);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
debug::window::render();
}
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info)
{
if(!render_info->buffers_allocated) render_info->allocateBuffers();
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(render_info->VAO);
// TODO: change GL_STATIC_DRAW to the one that means "few redraws and further in between"
// position attribute
glBindBuffer(GL_ARRAY_BUFFER, render_info->VBO);
glBufferData(GL_ARRAY_BUFFER, mesh_data->vertices.size() * sizeof(GLfloat), &(mesh_data->vertices[0]), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
// normal attribute
glBindBuffer(GL_ARRAY_BUFFER, render_info->extentsBuffer);
glBufferData(GL_ARRAY_BUFFER, mesh_data->extents.size() * sizeof(GLfloat), &(mesh_data->extents[0]), GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)(0));
glEnableVertexAttribArray(1);
// texcoords attribute
glBindBuffer(GL_ARRAY_BUFFER, render_info->texinfoBuffer);
glBufferData(GL_ARRAY_BUFFER, mesh_data->texinfo.size() * sizeof(GLfloat), &(mesh_data->texinfo[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
glBindVertexArray(0);
}
void framebuffer_size_callback(GLFWwindow *window, int width, int height){
resize_framebuffer(width, height);
}
void resize_framebuffer(int width, int height){
screenWidth = width;
screenHeight = height;
theCamera.viewPortCallBack(nullptr, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
glBindTexture(GL_TEXTURE_2D, renderTex);
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0);
glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); //Support up to
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
renderTexDepthBuffer);
}
void saveScreenshot(bool forceFullHD){
int old_screenWidth = screenWidth;
int old_screenHeight = screenHeight;
if(forceFullHD){
resize_framebuffer(1920, 1080);
// Do a render pass
render();
}
// Bind the render frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
// Save the framebuffer in a byte array
GLubyte data[screenWidth*screenHeight*3];
glReadPixels(0, 0, screenWidth, screenHeight, GL_RGB, GL_UNSIGNED_BYTE, data);
// Save the byte array onto a texture
stbi_flip_vertically_on_write(1);
stbi_write_png(forceFullHD ? "screenshot_fullhd.png" : "screenshot.png", screenWidth,
screenHeight, 3, data, screenWidth*3);
if(forceFullHD) resize_framebuffer(old_screenWidth, old_screenHeight);
}
void destroy(){
delete theShader;
delete quadShader;
}
};

BIN
textures/cobblestone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

BIN
textures/dirt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

BIN
textures/grass_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

BIN
textures/leaves.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

BIN
textures/wood.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B