From 4e1c4fe98a4da343b2f809659631380065ac83cc Mon Sep 17 00:00:00 2001 From: WSAL Evan Date: Fri, 18 Oct 2024 10:07:14 -0400 Subject: [PATCH] Episode 4 - Water --- ScuffedMinecraft/ScuffedMinecraft.vcxproj | 11 +- .../ScuffedMinecraft.vcxproj.filters | 13 +- .../assets/shaders/billboard_frag.glsl | 27 + .../assets/shaders/billboard_vert.glsl | 19 + .../assets/shaders/framebuffer_frag.glsl | 32 ++ .../assets/shaders/framebuffer_vert.glsl | 12 + .../{fragment_shader.glsl => main_frag.glsl} | 0 .../{vertex_shader.glsl => main_vert.glsl} | 2 +- .../assets/shaders/water_frag.glsl | 26 + .../assets/shaders/water_vert.glsl | 47 ++ ScuffedMinecraft/assets/sprites/block_map.png | Bin 4492 -> 8941 bytes ScuffedMinecraft/assets/sprites/block_map.psd | Bin 72717 -> 132185 bytes ScuffedMinecraft/src/Application.cpp | 149 ++++- ScuffedMinecraft/src/Block.cpp | 8 +- ScuffedMinecraft/src/Block.h | 17 +- ScuffedMinecraft/src/Blocks.h | 32 +- ScuffedMinecraft/src/Chunk.cpp | 517 +++++++++++++----- ScuffedMinecraft/src/Chunk.h | 42 +- ScuffedMinecraft/src/Planet.cpp | 40 +- ScuffedMinecraft/src/Planet.h | 12 +- ScuffedMinecraft/src/Vertex.h | 58 ++ ScuffedMinecraft/src/WorldGen.cpp | 241 +++++--- 22 files changed, 1007 insertions(+), 298 deletions(-) create mode 100644 ScuffedMinecraft/assets/shaders/billboard_frag.glsl create mode 100644 ScuffedMinecraft/assets/shaders/billboard_vert.glsl create mode 100644 ScuffedMinecraft/assets/shaders/framebuffer_frag.glsl create mode 100644 ScuffedMinecraft/assets/shaders/framebuffer_vert.glsl rename ScuffedMinecraft/assets/shaders/{fragment_shader.glsl => main_frag.glsl} (100%) rename ScuffedMinecraft/assets/shaders/{vertex_shader.glsl => main_vert.glsl} (92%) create mode 100644 ScuffedMinecraft/assets/shaders/water_frag.glsl create mode 100644 ScuffedMinecraft/assets/shaders/water_vert.glsl create mode 100644 ScuffedMinecraft/src/Vertex.h diff --git a/ScuffedMinecraft/ScuffedMinecraft.vcxproj b/ScuffedMinecraft/ScuffedMinecraft.vcxproj index da168e1..1461acf 100644 --- a/ScuffedMinecraft/ScuffedMinecraft.vcxproj +++ b/ScuffedMinecraft/ScuffedMinecraft.vcxproj @@ -180,6 +180,7 @@ + @@ -193,8 +194,14 @@ - - + + + + + + + + diff --git a/ScuffedMinecraft/ScuffedMinecraft.vcxproj.filters b/ScuffedMinecraft/ScuffedMinecraft.vcxproj.filters index 92cc2ea..44ed770 100644 --- a/ScuffedMinecraft/ScuffedMinecraft.vcxproj.filters +++ b/ScuffedMinecraft/ScuffedMinecraft.vcxproj.filters @@ -131,10 +131,19 @@ Header Files + + Header Files + - - + + + + + + + + diff --git a/ScuffedMinecraft/assets/shaders/billboard_frag.glsl b/ScuffedMinecraft/assets/shaders/billboard_frag.glsl new file mode 100644 index 0000000..fbcfd1f --- /dev/null +++ b/ScuffedMinecraft/assets/shaders/billboard_frag.glsl @@ -0,0 +1,27 @@ +#version 330 core + +in vec2 TexCoord; + +out vec4 FragColor; + +uniform sampler2D tex; + +const vec3 ambient = vec3(.5); +const vec3 lightDirection = vec3(0.8, 1, 0.7); + +const vec3 normal = vec3( 0, -1, 0); + +void main() +{ + vec3 lightDir = normalize(-lightDirection); + + float diff = max(dot(normal, lightDir), 0.0); + vec3 diffuse = diff * vec3(1); + + vec4 result = vec4(ambient + diffuse, 1.0); + + vec4 texResult = texture(tex, TexCoord); + if (texResult.a == 0) + discard; + FragColor = texResult * result; +} \ No newline at end of file diff --git a/ScuffedMinecraft/assets/shaders/billboard_vert.glsl b/ScuffedMinecraft/assets/shaders/billboard_vert.glsl new file mode 100644 index 0000000..5be1325 --- /dev/null +++ b/ScuffedMinecraft/assets/shaders/billboard_vert.glsl @@ -0,0 +1,19 @@ +#version 330 core + +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; + +out vec2 TexCoord; +out vec3 Normal; + +uniform float texMultiplier; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(aPos, 1.0); + TexCoord = aTexCoord * texMultiplier; +} \ No newline at end of file diff --git a/ScuffedMinecraft/assets/shaders/framebuffer_frag.glsl b/ScuffedMinecraft/assets/shaders/framebuffer_frag.glsl new file mode 100644 index 0000000..878ea4b --- /dev/null +++ b/ScuffedMinecraft/assets/shaders/framebuffer_frag.glsl @@ -0,0 +1,32 @@ +#version 330 core + +out vec4 FragColor; +in vec2 TexCoords; + +uniform sampler2D screenTexture; +uniform sampler2D depthTexture; + +uniform bool underwater; + +const vec3 fogColor = vec3(0, 0, 0.25); +const float fogNear = 0.9; +const float fogFar = 1.0; + +void main() +{ + vec3 color = texture(screenTexture, TexCoords).rgb; + float depth = texture(depthTexture, TexCoords).r; + + vec3 finalColor = color; + + // Calculate fog + if (underwater) + { + float fogFactor = (fogFar - depth) / (fogFar - fogNear); + fogFactor = clamp(fogFactor, 0.0, 1.0); + + finalColor = mix(fogColor, color, fogFactor); + } + + FragColor = vec4(vec3(finalColor), 1.0); +} \ No newline at end of file diff --git a/ScuffedMinecraft/assets/shaders/framebuffer_vert.glsl b/ScuffedMinecraft/assets/shaders/framebuffer_vert.glsl new file mode 100644 index 0000000..3ec6042 --- /dev/null +++ b/ScuffedMinecraft/assets/shaders/framebuffer_vert.glsl @@ -0,0 +1,12 @@ +#version 330 core + +layout (location = 0) in vec2 inPos; +layout (location = 1) in vec2 inTexCoords; + +out vec2 TexCoords; + +void main() +{ + gl_Position = vec4(inPos.x, inPos.y, 0.0, 1.0); + TexCoords = inTexCoords; +} \ No newline at end of file diff --git a/ScuffedMinecraft/assets/shaders/fragment_shader.glsl b/ScuffedMinecraft/assets/shaders/main_frag.glsl similarity index 100% rename from ScuffedMinecraft/assets/shaders/fragment_shader.glsl rename to ScuffedMinecraft/assets/shaders/main_frag.glsl diff --git a/ScuffedMinecraft/assets/shaders/vertex_shader.glsl b/ScuffedMinecraft/assets/shaders/main_vert.glsl similarity index 92% rename from ScuffedMinecraft/assets/shaders/vertex_shader.glsl rename to ScuffedMinecraft/assets/shaders/main_vert.glsl index 46aea5d..cd3b298 100644 --- a/ScuffedMinecraft/assets/shaders/vertex_shader.glsl +++ b/ScuffedMinecraft/assets/shaders/main_vert.glsl @@ -1,6 +1,5 @@ #version 330 core - layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 aTexCoord; layout (location = 2) in int aDirection; @@ -13,6 +12,7 @@ uniform float texMultiplier; uniform mat4 model; uniform mat4 view; uniform mat4 projection; +uniform float time; // Array of possible normals based on direction const vec3 normals[] = vec3[]( diff --git a/ScuffedMinecraft/assets/shaders/water_frag.glsl b/ScuffedMinecraft/assets/shaders/water_frag.glsl new file mode 100644 index 0000000..890725d --- /dev/null +++ b/ScuffedMinecraft/assets/shaders/water_frag.glsl @@ -0,0 +1,26 @@ +#version 330 core + +in vec2 TexCoord; +in vec3 Normal; + +out vec4 FragColor; + +uniform sampler2D tex; + +vec3 ambient = vec3(.5); +vec3 lightDirection = vec3(0.8, 1, 0.7); + +void main() +{ + vec3 lightDir = normalize(-lightDirection); + + float diff = max(dot(Normal, lightDir), 0.0); + vec3 diffuse = diff * vec3(1); + + vec4 result = vec4(ambient + diffuse, 1.0); + + vec4 texResult = texture(tex, TexCoord); + if (texResult.a == 0) + discard; + FragColor = texResult * result; +} \ No newline at end of file diff --git a/ScuffedMinecraft/assets/shaders/water_vert.glsl b/ScuffedMinecraft/assets/shaders/water_vert.glsl new file mode 100644 index 0000000..5912972 --- /dev/null +++ b/ScuffedMinecraft/assets/shaders/water_vert.glsl @@ -0,0 +1,47 @@ +#version 330 core + +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; +layout (location = 2) in int aDirection; +layout (location = 3) in int aTop; + +out vec2 TexCoord; +out vec3 Normal; + +uniform float texMultiplier; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; +uniform float time; + +// Array of possible normals based on direction +const vec3 normals[] = vec3[]( + vec3( 0, 0, 1), // 0 + vec3( 0, 0, -1), // 1 + vec3( 1, 0, 0), // 2 + vec3(-1, 0, 0), // 3 + vec3( 0, 1, 0), // 4 + vec3( 0, -1, 0), // 5 + vec3( 0, -1, 0) // 6 +); + +const int aFrames = 32; +const float animationTime = 5; +const int texNum = 16; +void main() +{ + vec3 pos = aPos; + if (aTop == 1) + { + pos.y -= .1; + pos.y += (sin(pos.x * 3.1415926535 / 2 + time) + sin(pos.z * 3.1415926535 / 2 + time * 1.5)) * .05; + } + gl_Position = projection * view * model * vec4(pos, 1.0); + vec2 currentTex = aTexCoord; + currentTex.x += mod(floor(mod(time / animationTime, 1) * aFrames), texNum); + currentTex.y += floor(floor(mod(time / animationTime, 1) * aFrames) / texNum); + TexCoord = currentTex * texMultiplier; + + Normal = normals[aDirection]; +} \ No newline at end of file diff --git a/ScuffedMinecraft/assets/sprites/block_map.png b/ScuffedMinecraft/assets/sprites/block_map.png index 401851934d5515caf527068ec80c581c6ebcd56e..79efa496bf9579766c12096331c9eb13bf66b3b9 100644 GIT binary patch literal 8941 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 E0UXle;Q#;t diff --git a/ScuffedMinecraft/src/Application.cpp b/ScuffedMinecraft/src/Application.cpp index 9a6c43c..953a117 100644 --- a/ScuffedMinecraft/src/Application.cpp +++ b/ScuffedMinecraft/src/Application.cpp @@ -16,6 +16,7 @@ #include "Shader.h" #include "Camera.h" #include "Planet.h" +#include "Blocks.h" void framebufferSizeCallback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow* window); @@ -43,6 +44,18 @@ Camera camera; // Window options #define VSYNC 1 // 0 for off, 1 for on +float rectangleVertices[] = +{ + // Coords // TexCoords + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f +}; + int main() { // Initialize GLFW @@ -84,13 +97,71 @@ int main() glEnable(GL_DEPTH_TEST); - // Create shader - Shader shader("assets/shaders/vertex_shader.glsl", "assets/shaders/fragment_shader.glsl"); + // Create shaders + Shader shader("assets/shaders/main_vert.glsl", "assets/shaders/main_frag.glsl"); shader.use(); - shader.setFloat("texMultiplier", .25f); + shader.setFloat("texMultiplier", 0.0625f); - // Create texture + Shader waterShader("assets/shaders/water_vert.glsl", "assets/shaders/water_frag.glsl"); + waterShader.use(); + + waterShader.setFloat("texMultiplier", 0.0625f); + + Shader billboardShader("assets/shaders/billboard_vert.glsl", "assets/shaders/billboard_frag.glsl"); + billboardShader.use(); + + billboardShader.setFloat("texMultiplier", 0.0625f); + + Shader framebufferShader("assets/shaders/framebuffer_vert.glsl", "assets/shaders/framebuffer_frag.glsl"); + + // Create post-processing framebuffer + unsigned int FBO; + glGenFramebuffers(1, &FBO); + glBindFramebuffer(GL_FRAMEBUFFER, FBO); + + unsigned int framebufferTexture; + glGenTextures(1, &framebufferTexture); + glBindTexture(GL_TEXTURE_2D, framebufferTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, windowX, windowY, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, framebufferTexture, 0); + + unsigned int depthTexture; + glGenTextures(1, &depthTexture); + glBindTexture(GL_TEXTURE_2D, depthTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, windowX, windowY, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0); + + auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fboStatus != GL_FRAMEBUFFER_COMPLETE) + std::cout << "Framebuffer error: " << fboStatus << '\n'; + + unsigned int rectVAO, rectVBO; + glGenVertexArrays(1, &rectVAO); + glGenBuffers(1, &rectVBO); + glBindVertexArray(rectVAO); + glBindBuffer(GL_ARRAY_BUFFER, rectVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(rectangleVertices), &rectangleVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + + framebufferShader.use(); + glUniform1i(glGetUniformLocation(framebufferShader.ID, "screenTexture"), 0); + glUniform1i(glGetUniformLocation(framebufferShader.ID, "depthTexture"), 1); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Create terrain texture unsigned int texture; glGenTextures(1, &texture); glActiveTexture(GL_TEXTURE0); @@ -122,7 +193,7 @@ int main() glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - Planet::planet = new Planet(); + Planet::planet = new Planet(&shader, &waterShader, &billboardShader); // Initialize ImGui IMGUI_CHECKVERSION(); @@ -158,11 +229,19 @@ int main() fpsStartTime = currentTimePoint; } + waterShader.use(); + waterShader.setFloat("time", currentFrame); + // Input processInput(window); // Rendering + glEnable(GL_DEPTH_TEST); + glBindFramebuffer(GL_FRAMEBUFFER, FBO); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); // New ImGui frame ImGui_ImplOpenGL3_NewFrame(); @@ -173,15 +252,69 @@ int main() glm::mat4 view = camera.GetViewMatrix(); glm::mat4 projection; - projection = glm::perspective(glm::radians(camera.Zoom), windowX / windowY, 0.1f, 100.0f); + projection = glm::perspective(glm::radians(camera.Zoom), windowX / windowY, 0.1f, 1000.0f); + + shader.use(); unsigned int viewLoc = glGetUniformLocation(shader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view)); unsigned int projectionLoc = glGetUniformLocation(shader.ID, "projection"); glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection)); - unsigned int modelLoc = glGetUniformLocation(shader.ID, "model"); + waterShader.use(); + viewLoc = glGetUniformLocation(waterShader.ID, "view"); + glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view)); + projectionLoc = glGetUniformLocation(waterShader.ID, "projection"); + glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection)); - Planet::planet->Update(camera.Position.x, camera.Position.y, camera.Position.z, modelLoc); + billboardShader.use(); + viewLoc = glGetUniformLocation(billboardShader.ID, "view"); + glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view)); + projectionLoc = glGetUniformLocation(billboardShader.ID, "projection"); + glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection)); + + Planet::planet->Update(camera.Position.x, camera.Position.y, camera.Position.z); + + framebufferShader.use(); + + // Check if player is underwater + int blockX = camera.Position.x < 0 ? camera.Position.x - 1 : camera.Position.x; + int blockY = camera.Position.y < 0 ? camera.Position.y - 1 : camera.Position.y; + int blockZ = camera.Position.z < 0 ? camera.Position.z - 1 : camera.Position.z; + + int chunkX = blockX < 0 ? floorf(blockX / (float)Planet::chunkSize) : blockX / (int)Planet::chunkSize; + int chunkY = blockY < 0 ? floorf(blockY / (float)Planet::chunkSize) : blockY / (int)Planet::chunkSize; + int chunkZ = blockZ < 0 ? floorf(blockZ / (float)Planet::chunkSize) : blockZ / (int)Planet::chunkSize; + + int localBlockX = blockX - (chunkX * Planet::chunkSize); + int localBlockY = blockY - (chunkY * Planet::chunkSize); + int localBlockZ = blockZ - (chunkZ * Planet::chunkSize); + + Chunk* chunk = Planet::planet->GetChunk(chunkX, chunkY, chunkZ); + if (chunk != nullptr) + { + unsigned int blockType = chunk->GetBlockAtPos( + localBlockX, + localBlockY, + localBlockZ); + + if (Blocks::blocks[blockType].blockType == Block::LIQUID) + { + framebufferShader.setBool("underwater", true); + } + else + { + framebufferShader.setBool("underwater", false); + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindVertexArray(rectVAO); + glDisable(GL_DEPTH_TEST); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, framebufferTexture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depthTexture); + glDrawArrays(GL_TRIANGLES, 0, 6); // Draw ImGui UI ImGui::Begin("Test"); diff --git a/ScuffedMinecraft/src/Block.cpp b/ScuffedMinecraft/src/Block.cpp index 0552839..7ab0153 100644 --- a/ScuffedMinecraft/src/Block.cpp +++ b/ScuffedMinecraft/src/Block.cpp @@ -1,7 +1,7 @@ #include "Block.h" -Block::Block(char minX, char minY, char maxX, char maxY, bool transparent, bool billboard) - : transparent(transparent), billboard(billboard) +Block::Block(char minX, char minY, char maxX, char maxY, BLOCK_TYPE blockType) + : blockType(blockType) { topMinX = minX; topMinY = minY; @@ -21,8 +21,8 @@ Block::Block(char minX, char minY, char maxX, char maxY, bool transparent, bool Block::Block(char topMinX, char topMinY, char topMaxX, char topMaxY, char bottomMinX, char bottomMinY, char bottomMaxX, char bottomMaxY, - char sideMinX, char sideMinY, char sideMaxX, char sideMaxY, bool transparent, bool billboard) - : transparent(transparent), billboard(billboard) + char sideMinX, char sideMinY, char sideMaxX, char sideMaxY, BLOCK_TYPE blockType) + : blockType(blockType) { this->topMinX = topMinX; this->topMinY = topMinY; diff --git a/ScuffedMinecraft/src/Block.h b/ScuffedMinecraft/src/Block.h index 051f22d..e828c4a 100644 --- a/ScuffedMinecraft/src/Block.h +++ b/ScuffedMinecraft/src/Block.h @@ -3,14 +3,23 @@ struct Block { public: + enum BLOCK_TYPE + { + SOLID, + TRANSPARENT, + LEAVES, + BILLBOARD, + LIQUID + }; + char topMinX, topMinY, topMaxX, topMaxY; char bottomMinX, bottomMinY, bottomMaxX, bottomMaxY; char sideMinX, sideMinY, sideMaxX, sideMaxY; - bool transparent; - bool billboard; + BLOCK_TYPE blockType; - Block(char minX, char minY, char maxX, char maxY, bool transparent = false, bool billboard = false); + Block(char minX, char minY, char maxX, char maxY, BLOCK_TYPE blockType); Block(char topMinX, char topMinY, char topMaxX, char topMaxY, char bottomMinX, char bottomMinY, char bottomMaxX, char bottomMaxY, - char sideMinX, char sideMinY, char sideMaxX, char sideMaxY, bool transparent = false, bool billboard = false); + char sideMinX, char sideMinY, char sideMaxX, char sideMaxY, BLOCK_TYPE blockType); + }; \ No newline at end of file diff --git a/ScuffedMinecraft/src/Blocks.h b/ScuffedMinecraft/src/Blocks.h index f2a58bd..ae0a4eb 100644 --- a/ScuffedMinecraft/src/Blocks.h +++ b/ScuffedMinecraft/src/Blocks.h @@ -7,27 +7,28 @@ namespace Blocks { const std::vector blocks{ - Block(0, 0, 0, 0, true), // Air block - Block(0, 0, 1, 1), // Dirt block + Block(0, 0, 0, 0, Block::TRANSPARENT), // Air block + Block(0, 0, 1, 1, Block::SOLID), // Dirt block - Block(1, 1, 2, 2, // Grass block + Block(1, 1, 2, 2, // Grass block 0, 0, 1, 1, - 1, 0, 2, 1), + 1, 0, 2, 1, Block::SOLID), - Block(0, 1, 1, 2), // Stone block + Block(0, 1, 1, 2, Block::SOLID), // Stone block - Block(2, 1, 3, 2, // Log + Block(2, 1, 3, 2, // Log 2, 1, 3, 2, - 2, 0, 3, 1), + 2, 0, 3, 1, Block::SOLID), - Block(0, 2, 1, 3, true), // Leaves - Block(1, 2, 2, 3, true, true), // Grass - Block(3, 0, 4, 1, true, true), // Tall Grass Bottom - Block(3, 1, 4, 2, true, true), // Tall Grass Top - Block(0, 3, 1, 4, true, true), // Poppy - Block(2, 2, 3, 3, true, true), // White Tulip - Block(3, 2, 4, 3, true, true), // Pink Tulip - Block(1, 3, 2, 4, true, true), // Orange Tulip + Block(0, 2, 1, 3, Block::LEAVES), // Leaves + Block(1, 2, 2, 3, Block::BILLBOARD), // Grass + Block(3, 0, 4, 1, Block::BILLBOARD), // Tall Grass Bottom + Block(3, 1, 4, 2, Block::BILLBOARD), // Tall Grass Top + Block(0, 3, 1, 4, Block::BILLBOARD), // Poppy + Block(2, 2, 3, 3, Block::BILLBOARD), // White Tulip + Block(3, 2, 4, 3, Block::BILLBOARD), // Pink Tulip + Block(1, 3, 2, 4, Block::BILLBOARD), // Orange Tulip + Block(0, 4, 1, 5, Block::LIQUID) // Water }; enum BLOCKS @@ -45,5 +46,6 @@ namespace Blocks WHITE_TULIP = 10, PINK_TULIP = 11, ORANGE_TULIP = 12, + WATER = 13 }; } \ No newline at end of file diff --git a/ScuffedMinecraft/src/Chunk.cpp b/ScuffedMinecraft/src/Chunk.cpp index 9759b22..4b7e95a 100644 --- a/ScuffedMinecraft/src/Chunk.cpp +++ b/ScuffedMinecraft/src/Chunk.cpp @@ -2,18 +2,17 @@ #include #include +#include #include #include -#include #include "Planet.h" #include "Blocks.h" #include "WorldGen.h" -Chunk::Chunk(unsigned int chunkSize, glm::vec3 chunkPos) +Chunk::Chunk(unsigned int chunkSize, glm::vec3 chunkPos, Shader* shader, Shader* waterShader) + : chunkSize(chunkSize), chunkPos(chunkPos) { - this->chunkSize = chunkSize; - this->chunkPos = chunkPos; worldPos = glm::vec3(chunkPos.x * chunkSize, chunkPos.y * chunkSize, chunkPos.z * chunkSize); ready = false; @@ -26,9 +25,17 @@ Chunk::~Chunk() if (chunkThread.joinable()) chunkThread.join(); - glDeleteBuffers(1, &vbo); - glDeleteBuffers(1, &ebo); - glDeleteVertexArrays(1, &vertexArrayObject); + glDeleteBuffers(1, &mainVBO); + glDeleteBuffers(1, &mainEBO); + glDeleteVertexArrays(1, &mainVAO); + + glDeleteBuffers(1, &waterVBO); + glDeleteBuffers(1, &waterEBO); + glDeleteVertexArrays(1, &waterVAO); + + glDeleteBuffers(1, &billboardVBO); + glDeleteBuffers(1, &billboardEBO); + glDeleteVertexArrays(1, &billboardVAO); } void Chunk::GenerateChunk() @@ -48,6 +55,8 @@ void Chunk::GenerateChunk() //std::cout << "Got chunk data in thread: " << std::this_thread::get_id() << '\n'; unsigned int currentVertex = 0; + unsigned int currentLiquidVertex = 0; + unsigned int currentBillboardVertex = 0; for (char x = 0; x < chunkSize; x++) { for (char z = 0; z < chunkSize; z++) @@ -60,59 +69,48 @@ void Chunk::GenerateChunk() const Block* block = &Blocks::blocks[chunkData[index]]; - if (block->billboard) + int topBlock; + if (y < chunkSize - 1) { - vertices.push_back(Vertex(x + .85355f, y + 0, z + .85355f, block->sideMinX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .14645f, y + 0, z + .14645f, block->sideMaxX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .85355f, y + 1, z + .85355f, block->sideMinX, block->sideMaxY, 6)); - vertices.push_back(Vertex(x + .14645f, y + 1, z + .14645f, block->sideMaxX, block->sideMaxY, 6)); + int blockIndex = x * chunkSize * chunkSize + z * chunkSize + (y + 1); + topBlock = chunkData[blockIndex]; + } + else + { + int blockIndex = x * chunkSize * chunkSize + z * chunkSize + 0; + topBlock = upData[blockIndex]; + } - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + const Block* topBlockType = &Blocks::blocks[topBlock]; + char waterTopValue = topBlockType->blockType == Block::TRANSPARENT ? 1 : 0; - vertices.push_back(Vertex(x + .14645f, y + 0, z + .14645f, block->sideMinX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .85355f, y + 0, z + .85355f, block->sideMaxX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .14645f, y + 1, z + .14645f, block->sideMinX, block->sideMaxY, 6)); - vertices.push_back(Vertex(x + .85355f, y + 1, z + .85355f, block->sideMaxX, block->sideMaxY, 6)); + if (block->blockType == Block::BILLBOARD) + { + billboardVertices.push_back(BillboardVertex(x + .85355f, y + 0, z + .85355f, block->sideMinX, block->sideMinY)); + billboardVertices.push_back(BillboardVertex(x + .14645f, y + 0, z + .14645f, block->sideMaxX, block->sideMinY)); + billboardVertices.push_back(BillboardVertex(x + .85355f, y + 1, z + .85355f, block->sideMinX, block->sideMaxY)); + billboardVertices.push_back(BillboardVertex(x + .14645f, y + 1, z + .14645f, block->sideMaxX, block->sideMaxY)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + billboardIndices.push_back(currentBillboardVertex + 0); + billboardIndices.push_back(currentBillboardVertex + 3); + billboardIndices.push_back(currentBillboardVertex + 1); + billboardIndices.push_back(currentBillboardVertex + 0); + billboardIndices.push_back(currentBillboardVertex + 2); + billboardIndices.push_back(currentBillboardVertex + 3); + currentBillboardVertex += 4; - vertices.push_back(Vertex(x + .14645f, y + 0, z + .85355f, block->sideMinX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .85355f, y + 0, z + .14645f, block->sideMaxX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .14645f, y + 1, z + .85355f, block->sideMinX, block->sideMaxY, 6)); - vertices.push_back(Vertex(x + .85355f, y + 1, z + .14645f, block->sideMaxX, block->sideMaxY, 6)); + billboardVertices.push_back(BillboardVertex(x + .14645f, y + 0, z + .85355f, block->sideMinX, block->sideMinY)); + billboardVertices.push_back(BillboardVertex(x + .85355f, y + 0, z + .14645f, block->sideMaxX, block->sideMinY)); + billboardVertices.push_back(BillboardVertex(x + .14645f, y + 1, z + .85355f, block->sideMinX, block->sideMaxY)); + billboardVertices.push_back(BillboardVertex(x + .85355f, y + 1, z + .14645f, block->sideMaxX, block->sideMaxY)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; - - vertices.push_back(Vertex(x + .85355f, y + 0, z + .14645f, block->sideMinX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .14645f, y + 0, z + .85355f, block->sideMaxX, block->sideMinY, 6)); - vertices.push_back(Vertex(x + .85355f, y + 1, z + .14645f, block->sideMinX, block->sideMaxY, 6)); - vertices.push_back(Vertex(x + .14645f, y + 1, z + .85355f, block->sideMaxX, block->sideMaxY, 6)); - - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + billboardIndices.push_back(currentBillboardVertex + 0); + billboardIndices.push_back(currentBillboardVertex + 3); + billboardIndices.push_back(currentBillboardVertex + 1); + billboardIndices.push_back(currentBillboardVertex + 0); + billboardIndices.push_back(currentBillboardVertex + 2); + billboardIndices.push_back(currentBillboardVertex + 3); + currentBillboardVertex += 4; } else { @@ -132,20 +130,41 @@ void Chunk::GenerateChunk() const Block* northBlockType = &Blocks::blocks[northBlock]; - if (northBlockType->transparent) + if (northBlockType->blockType == Block::LEAVES + || northBlockType->blockType == Block::TRANSPARENT + || northBlockType->blockType == Block::BILLBOARD + || (northBlockType->blockType == Block::LIQUID && block->blockType != Block::LIQUID)) { - vertices.push_back(Vertex(x + 1, y + 0, z + 0, block->sideMinX, block->sideMinY, 0)); - vertices.push_back(Vertex(x + 0, y + 0, z + 0, block->sideMaxX, block->sideMinY, 0)); - vertices.push_back(Vertex(x + 1, y + 1, z + 0, block->sideMinX, block->sideMaxY, 0)); - vertices.push_back(Vertex(x + 0, y + 1, z + 0, block->sideMaxX, block->sideMaxY, 0)); + if (block->blockType == Block::LIQUID) + { + waterVertices.push_back(WaterVertex(x + 1, y + 0, z + 0, block->sideMinX, block->sideMinY, 0, 0)); + waterVertices.push_back(WaterVertex(x + 0, y + 0, z + 0, block->sideMaxX, block->sideMinY, 0, 0)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 0, block->sideMinX, block->sideMaxY, 0, waterTopValue)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 0, block->sideMaxX, block->sideMaxY, 0, waterTopValue)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + } + else + { + mainVertices.push_back(Vertex(x + 1, y + 0, z + 0, block->sideMinX, block->sideMinY, 0)); + mainVertices.push_back(Vertex(x + 0, y + 0, z + 0, block->sideMaxX, block->sideMinY, 0)); + mainVertices.push_back(Vertex(x + 1, y + 1, z + 0, block->sideMinX, block->sideMaxY, 0)); + mainVertices.push_back(Vertex(x + 0, y + 1, z + 0, block->sideMaxX, block->sideMaxY, 0)); + + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 1); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 2); + mianIndices.push_back(currentVertex + 3); + currentVertex += 4; + } } } @@ -165,20 +184,41 @@ void Chunk::GenerateChunk() const Block* southBlockType = &Blocks::blocks[southBlock]; - if (southBlockType->transparent) + if (southBlockType->blockType == Block::LEAVES + || southBlockType->blockType == Block::TRANSPARENT + || southBlockType->blockType == Block::BILLBOARD + || (southBlockType->blockType == Block::LIQUID && block->blockType != Block::LIQUID)) { - vertices.push_back(Vertex(x + 0, y + 0, z + 1, block->sideMinX, block->sideMinY, 1)); - vertices.push_back(Vertex(x + 1, y + 0, z + 1, block->sideMaxX, block->sideMinY, 1)); - vertices.push_back(Vertex(x + 0, y + 1, z + 1, block->sideMinX, block->sideMaxY, 1)); - vertices.push_back(Vertex(x + 1, y + 1, z + 1, block->sideMaxX, block->sideMaxY, 1)); + if (block->blockType == Block::LIQUID) + { + waterVertices.push_back(WaterVertex(x + 0, y + 0, z + 1, block->sideMinX, block->sideMinY, 1, 0)); + waterVertices.push_back(WaterVertex(x + 1, y + 0, z + 1, block->sideMaxX, block->sideMinY, 1, 0)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 1, block->sideMinX, block->sideMaxY, 1, waterTopValue)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 1, block->sideMaxX, block->sideMaxY, 1, waterTopValue)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + } + else + { + mainVertices.push_back(Vertex(x + 0, y + 0, z + 1, block->sideMinX, block->sideMinY, 1)); + mainVertices.push_back(Vertex(x + 1, y + 0, z + 1, block->sideMaxX, block->sideMinY, 1)); + mainVertices.push_back(Vertex(x + 0, y + 1, z + 1, block->sideMinX, block->sideMaxY, 1)); + mainVertices.push_back(Vertex(x + 1, y + 1, z + 1, block->sideMaxX, block->sideMaxY, 1)); + + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 1); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 2); + mianIndices.push_back(currentVertex + 3); + currentVertex += 4; + } } } @@ -198,20 +238,41 @@ void Chunk::GenerateChunk() const Block* westBlockType = &Blocks::blocks[westBlock]; - if (westBlockType->transparent) + if (westBlockType->blockType == Block::LEAVES + || westBlockType->blockType == Block::TRANSPARENT + || westBlockType->blockType == Block::BILLBOARD + || (westBlockType->blockType == Block::LIQUID && block->blockType != Block::LIQUID)) { - vertices.push_back(Vertex(x + 0, y + 0, z + 0, block->sideMinX, block->sideMinY, 2)); - vertices.push_back(Vertex(x + 0, y + 0, z + 1, block->sideMaxX, block->sideMinY, 2)); - vertices.push_back(Vertex(x + 0, y + 1, z + 0, block->sideMinX, block->sideMaxY, 2)); - vertices.push_back(Vertex(x + 0, y + 1, z + 1, block->sideMaxX, block->sideMaxY, 2)); + if (block->blockType == Block::LIQUID) + { + waterVertices.push_back(WaterVertex(x + 0, y + 0, z + 0, block->sideMinX, block->sideMinY, 2, 0)); + waterVertices.push_back(WaterVertex(x + 0, y + 0, z + 1, block->sideMaxX, block->sideMinY, 2, 0)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 0, block->sideMinX, block->sideMaxY, 2, waterTopValue)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 1, block->sideMaxX, block->sideMaxY, 2, waterTopValue)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + } + else + { + mainVertices.push_back(Vertex(x + 0, y + 0, z + 0, block->sideMinX, block->sideMinY, 2)); + mainVertices.push_back(Vertex(x + 0, y + 0, z + 1, block->sideMaxX, block->sideMinY, 2)); + mainVertices.push_back(Vertex(x + 0, y + 1, z + 0, block->sideMinX, block->sideMaxY, 2)); + mainVertices.push_back(Vertex(x + 0, y + 1, z + 1, block->sideMaxX, block->sideMaxY, 2)); + + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 1); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 2); + mianIndices.push_back(currentVertex + 3); + currentVertex += 4; + } } } @@ -231,20 +292,41 @@ void Chunk::GenerateChunk() const Block* eastBlockType = &Blocks::blocks[eastBlock]; - if (eastBlockType->transparent) + if (eastBlockType->blockType == Block::LEAVES + || eastBlockType->blockType == Block::TRANSPARENT + || eastBlockType->blockType == Block::BILLBOARD + || (eastBlockType->blockType == Block::LIQUID && block->blockType != Block::LIQUID)) { - vertices.push_back(Vertex(x + 1, y + 0, z + 1, block->sideMinX, block->sideMinY, 3)); - vertices.push_back(Vertex(x + 1, y + 0, z + 0, block->sideMaxX, block->sideMinY, 3)); - vertices.push_back(Vertex(x + 1, y + 1, z + 1, block->sideMinX, block->sideMaxY, 3)); - vertices.push_back(Vertex(x + 1, y + 1, z + 0, block->sideMaxX, block->sideMaxY, 3)); + if (block->blockType == Block::LIQUID) + { + waterVertices.push_back(WaterVertex(x + 1, y + 0, z + 1, block->sideMinX, block->sideMinY, 3, 0)); + waterVertices.push_back(WaterVertex(x + 1, y + 0, z + 0, block->sideMaxX, block->sideMinY, 3, 0)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 1, block->sideMinX, block->sideMaxY, 3, waterTopValue)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 0, block->sideMaxX, block->sideMaxY, 3, waterTopValue)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + } + else + { + mainVertices.push_back(Vertex(x + 1, y + 0, z + 1, block->sideMinX, block->sideMinY, 3)); + mainVertices.push_back(Vertex(x + 1, y + 0, z + 0, block->sideMaxX, block->sideMinY, 3)); + mainVertices.push_back(Vertex(x + 1, y + 1, z + 1, block->sideMinX, block->sideMaxY, 3)); + mainVertices.push_back(Vertex(x + 1, y + 1, z + 0, block->sideMaxX, block->sideMaxY, 3)); + + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 1); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 2); + mianIndices.push_back(currentVertex + 3); + currentVertex += 4; + } } } @@ -264,52 +346,93 @@ void Chunk::GenerateChunk() const Block* bottomBlockType = &Blocks::blocks[bottomBlock]; - if (bottomBlockType->transparent) + if (bottomBlockType->blockType == Block::LEAVES + || bottomBlockType->blockType == Block::TRANSPARENT + || bottomBlockType->blockType == Block::BILLBOARD + || (bottomBlockType->blockType == Block::LIQUID && block->blockType != Block::LIQUID)) { - vertices.push_back(Vertex(x + 1, y + 0, z + 1, block->bottomMinX, block->bottomMinY, 4)); - vertices.push_back(Vertex(x + 0, y + 0, z + 1, block->bottomMaxX, block->bottomMinY, 4)); - vertices.push_back(Vertex(x + 1, y + 0, z + 0, block->bottomMinX, block->bottomMaxY, 4)); - vertices.push_back(Vertex(x + 0, y + 0, z + 0, block->bottomMaxX, block->bottomMaxY, 4)); + if (block->blockType == Block::LIQUID) + { + waterVertices.push_back(WaterVertex(x + 1, y + 0, z + 1, block->bottomMinX, block->bottomMinY, 4, 0)); + waterVertices.push_back(WaterVertex(x + 0, y + 0, z + 1, block->bottomMaxX, block->bottomMinY, 4, 0)); + waterVertices.push_back(WaterVertex(x + 1, y + 0, z + 0, block->bottomMinX, block->bottomMaxY, 4, 0)); + waterVertices.push_back(WaterVertex(x + 0, y + 0, z + 0, block->bottomMaxX, block->bottomMaxY, 4, 0)); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); - currentVertex += 4; + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + } + else + { + mainVertices.push_back(Vertex(x + 1, y + 0, z + 1, block->bottomMinX, block->bottomMinY, 4)); + mainVertices.push_back(Vertex(x + 0, y + 0, z + 1, block->bottomMaxX, block->bottomMinY, 4)); + mainVertices.push_back(Vertex(x + 1, y + 0, z + 0, block->bottomMinX, block->bottomMaxY, 4)); + mainVertices.push_back(Vertex(x + 0, y + 0, z + 0, block->bottomMaxX, block->bottomMaxY, 4)); + + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 1); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 2); + mianIndices.push_back(currentVertex + 3); + currentVertex += 4; + } } } // Top { - int topBlock; - if (y < chunkSize - 1) + if (block->blockType == Block::LIQUID) { - int blockIndex = x * chunkSize * chunkSize + z * chunkSize + (y + 1); - topBlock = chunkData[blockIndex]; + if (topBlockType->blockType != Block::LIQUID) + { + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 1, block->topMinX, block->topMinY, 5, 1)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 1, block->topMaxX, block->topMinY, 5, 1)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 0, block->topMinX, block->topMaxY, 5, 1)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 0, block->topMaxX, block->topMaxY, 5, 1)); + + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 1, block->topMinX, block->topMinY, 5, 1)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 1, block->topMaxX, block->topMinY, 5, 1)); + waterVertices.push_back(WaterVertex(x + 1, y + 1, z + 0, block->topMinX, block->topMaxY, 5, 1)); + waterVertices.push_back(WaterVertex(x + 0, y + 1, z + 0, block->topMaxX, block->topMaxY, 5, 1)); + + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 3); + waterIndices.push_back(currentLiquidVertex + 1); + waterIndices.push_back(currentLiquidVertex + 0); + waterIndices.push_back(currentLiquidVertex + 2); + waterIndices.push_back(currentLiquidVertex + 3); + currentLiquidVertex += 4; + } } - else + else if (topBlockType->blockType == Block::LEAVES + || topBlockType->blockType == Block::TRANSPARENT + || topBlockType->blockType == Block::BILLBOARD + || topBlockType->blockType == Block::LIQUID) { - int blockIndex = x * chunkSize * chunkSize + z * chunkSize + 0; - topBlock = upData[blockIndex]; - } + mainVertices.push_back(Vertex(x + 0, y + 1, z + 1, block->topMinX, block->topMinY, 5)); + mainVertices.push_back(Vertex(x + 1, y + 1, z + 1, block->topMaxX, block->topMinY, 5)); + mainVertices.push_back(Vertex(x + 0, y + 1, z + 0, block->topMinX, block->topMaxY, 5)); + mainVertices.push_back(Vertex(x + 1, y + 1, z + 0, block->topMaxX, block->topMaxY, 5)); - const Block* topBlockType = &Blocks::blocks[topBlock]; - - if (topBlockType->transparent) - { - vertices.push_back(Vertex(x + 0, y + 1, z + 1, block->topMinX, block->topMinY, 5)); - vertices.push_back(Vertex(x + 1, y + 1, z + 1, block->topMaxX, block->topMinY, 5)); - vertices.push_back(Vertex(x + 0, y + 1, z + 0, block->topMinX, block->topMaxY, 5)); - vertices.push_back(Vertex(x + 1, y + 1, z + 0, block->topMaxX, block->topMaxY, 5)); - - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 3); - indices.push_back(currentVertex + 1); - indices.push_back(currentVertex + 0); - indices.push_back(currentVertex + 2); - indices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 3); + mianIndices.push_back(currentVertex + 1); + mianIndices.push_back(currentVertex + 0); + mianIndices.push_back(currentVertex + 2); + mianIndices.push_back(currentVertex + 3); currentVertex += 4; } } @@ -325,45 +448,135 @@ void Chunk::GenerateChunk() //std::cout << "Generated: " << generated << '\n'; } -void Chunk::Render(unsigned int modelLoc) +void Chunk::Render(Shader* mainShader, Shader* billboardShader) { if (!ready) { if (generated) { - numTriangles = indices.size(); + // Solid + numTrianglesMain = mianIndices.size(); - glGenVertexArrays(1, &vertexArrayObject); - glBindVertexArray(vertexArrayObject); + glGenVertexArrays(1, &mainVAO); + glGenBuffers(1, &mainVBO); + glGenBuffers(1, &mainEBO); - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW); + glBindVertexArray(mainVAO); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, posX)); + glBindBuffer(GL_ARRAY_BUFFER, mainVBO); + glBufferData(GL_ARRAY_BUFFER, mainVertices.size() * sizeof(Vertex), mainVertices.data(), GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mainEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mianIndices.size() * sizeof(unsigned int), mianIndices.data(), GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_BYTE, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, posX)); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_BYTE, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texGridX)); glEnableVertexAttribArray(1); glVertexAttribIPointer(2, 1, GL_BYTE, sizeof(Vertex), (void*)offsetof(Vertex, direction)); - glEnableVertexAttribArray(2); + glEnableVertexAttribArray(2); + + // Water + numTrianglesWater = waterIndices.size(); - glGenBuffers(1, &ebo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); + glGenVertexArrays(1, &waterVAO); + glGenBuffers(1, &waterVBO); + glGenBuffers(1, &waterEBO); + + glBindVertexArray(waterVAO); + + glBindBuffer(GL_ARRAY_BUFFER, waterVBO); + glBufferData(GL_ARRAY_BUFFER, waterVertices.size() * sizeof(WaterVertex), waterVertices.data(), GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, waterEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, waterIndices.size() * sizeof(unsigned int), waterIndices.data(), GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_BYTE, GL_FALSE, sizeof(WaterVertex), (void*)offsetof(WaterVertex, posX)); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_BYTE, GL_FALSE, sizeof(WaterVertex), (void*)offsetof(WaterVertex, texGridX)); + glEnableVertexAttribArray(1); + glVertexAttribIPointer(2, 1, GL_BYTE, sizeof(WaterVertex), (void*)offsetof(WaterVertex, direction)); + glEnableVertexAttribArray(2); + glVertexAttribIPointer(3, 1, GL_BYTE, sizeof(WaterVertex), (void*)offsetof(WaterVertex, top)); + glEnableVertexAttribArray(3); + ready = true; + + // Billboard + numTrianglesBillboard = billboardIndices.size(); + + glGenVertexArrays(1, &billboardVAO); + glGenBuffers(1, &billboardVBO); + glGenBuffers(1, &billboardEBO); + + glBindVertexArray(billboardVAO); + + glBindBuffer(GL_ARRAY_BUFFER, billboardVBO); + glBufferData(GL_ARRAY_BUFFER, billboardVertices.size() * sizeof(BillboardVertex), billboardVertices.data(), GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, billboardEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, billboardIndices.size() * sizeof(unsigned int), billboardIndices.data(), GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(BillboardVertex), (void*)offsetof(BillboardVertex, posX)); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_BYTE, GL_FALSE, sizeof(BillboardVertex), (void*)offsetof(BillboardVertex, texGridX)); + glEnableVertexAttribArray(1); ready = true; } - + return; } //std::cout << "Rendering chunk " << chunkPos.x << ", " << chunkPos.y << ", " << chunkPos.z << '\n' // << "Chunk VAO: " << vertexArrayObject << '\n' << "Triangles: " << numTriangles << '\n'; - glBindVertexArray(vertexArrayObject); + // Calculate model matrix + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, worldPos); + + // Render main mesh + mainShader->use(); + + modelLoc = glGetUniformLocation(mainShader->ID, "model"); + glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); + + glBindVertexArray(mainVAO); + glDrawElements(GL_TRIANGLES, numTrianglesMain, GL_UNSIGNED_INT, 0); + + // Render billboard mesh + billboardShader->use(); + + modelLoc = glGetUniformLocation(billboardShader->ID, "model"); + glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); + + glDisable(GL_CULL_FACE); + glBindVertexArray(billboardVAO); + glDrawElements(GL_TRIANGLES, numTrianglesBillboard, GL_UNSIGNED_INT, 0); + glEnable(GL_CULL_FACE); +} + +void Chunk::RenderWater(Shader* shader) +{ + if (!ready) + return; + + //std::cout << "Rendering chunk " << chunkPos.x << ", " << chunkPos.y << ", " << chunkPos.z << '\n' + // << "Chunk VAO: " << vertexArrayObject << '\n' << "Triangles: " << numTriangles << '\n'; + + modelLoc = glGetUniformLocation(shader->ID, "model"); + glBindVertexArray(waterVAO); glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, worldPos); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); - glDrawElements(GL_TRIANGLES, numTriangles, GL_UNSIGNED_INT, 0); + glDrawElements(GL_TRIANGLES, numTrianglesWater, GL_UNSIGNED_INT, 0); } + +unsigned int Chunk::GetBlockAtPos(int x, int y, int z) +{ + if (!ready) + return 0; + + int index = x * chunkSize * chunkSize + z * chunkSize + y; + return chunkData[index]; +} \ No newline at end of file diff --git a/ScuffedMinecraft/src/Chunk.h b/ScuffedMinecraft/src/Chunk.h index bbf8152..1c380f2 100644 --- a/ScuffedMinecraft/src/Chunk.h +++ b/ScuffedMinecraft/src/Chunk.h @@ -4,33 +4,19 @@ #include #include -struct Vertex -{ - float posX, posY, posZ; - char texGridX, texGridY; - char direction; - - Vertex(float _posX, float _posY, float _posZ, char _texGridX, char _texGridY, char _direction) - { - posX = _posX; - posY = _posY; - posZ = _posZ; - - texGridX = _texGridX; - texGridY = _texGridY; - - direction = _direction; - } -}; +#include "Shader.h" +#include "Vertex.h" class Chunk { public: - Chunk(unsigned int chunkSize, glm::vec3 chunkPos); + Chunk(unsigned int chunkSize, glm::vec3 chunkPos, Shader* shader, Shader* waterShader); ~Chunk(); void GenerateChunk(); - void Render(unsigned int modelLoc); + void Render(Shader* mainShader, Shader* billboardShader); + void RenderWater(Shader* shader); + unsigned int GetBlockAtPos(int x, int y, int z); public: std::vector chunkData; @@ -39,13 +25,19 @@ public: bool generated; private: - unsigned int vertexArrayObject; - unsigned int vbo, ebo; unsigned int chunkSize; - unsigned int numTriangles; glm::vec3 worldPos; std::thread chunkThread; - std::vector vertices; - std::vector indices; + std::vector mainVertices; + std::vector mianIndices; + std::vector waterVertices; + std::vector waterIndices; + std::vector billboardVertices; + std::vector billboardIndices; + + unsigned int mainVAO, waterVAO, billboardVAO; + unsigned int mainVBO, mainEBO, waterVBO, waterEBO, billboardVBO, billboardEBO; + unsigned int numTrianglesMain, numTrianglesWater, numTrianglesBillboard; + unsigned int modelLoc; }; \ No newline at end of file diff --git a/ScuffedMinecraft/src/Planet.cpp b/ScuffedMinecraft/src/Planet.cpp index 9d2d72f..8d1eb87 100644 --- a/ScuffedMinecraft/src/Planet.cpp +++ b/ScuffedMinecraft/src/Planet.cpp @@ -1,11 +1,14 @@ #include "Planet.h" #include +#include +#include Planet* Planet::planet = nullptr; // Public -Planet::Planet() +Planet::Planet(Shader* solidShader, Shader* waterShader, Shader* billboardShader) + : solidShader(solidShader), waterShader(waterShader), billboardShader(billboardShader) { } @@ -31,7 +34,7 @@ std::vector Planet::GetChunkData(int chunkX, int chunkY, int chunk } } -void Planet::Update(float camX, float camY, float camZ, unsigned int modelLoc) +void Planet::Update(float camX, float camY, float camZ) { int camChunkX = camX < 0 ? floor(camX / chunkSize) : camX / chunkSize; int camChunkY = camY < 0 ? floor(camY / chunkSize) : camY / chunkSize; @@ -132,11 +135,13 @@ void Planet::Update(float camX, float camY, float camZ, unsigned int modelLoc) if (chunks.find(chunkTuple) == chunks.end()) { chunks.try_emplace(chunkTuple, - chunkSize, next + chunkSize, next, solidShader, waterShader ); } } + glDisable(GL_BLEND); + chunksLoading = 0; numChunks = 0; numChunksRendered = 0; @@ -154,14 +159,39 @@ void Planet::Update(float camX, float camY, float camZ, unsigned int modelLoc) abs(chunkY - camChunkY) > renderDistance || abs(chunkZ - camChunkZ) > renderDistance)) { - it->second.~Chunk(); it = chunks.erase(it); } else { numChunksRendered++; - it->second.Render(modelLoc); + it->second.Render(solidShader, billboardShader); ++it; } } + + glEnable(GL_BLEND); + waterShader->use(); + for (auto it = chunks.begin(); it != chunks.end(); ) + { + int chunkX = it->second.chunkPos.x; + int chunkY = it->second.chunkPos.y; + int chunkZ = it->second.chunkPos.z; + + it->second.RenderWater(waterShader); + ++it; + } +} + +Chunk* Planet::GetChunk(int chunkX, int chunkY, int chunkZ) +{ + std::tuple chunkTuple{ chunkX, chunkY, chunkZ }; + + if (chunks.find(chunkTuple) == chunks.end()) + { + return nullptr; + } + else + { + return &chunks.at(chunkTuple); + } } \ No newline at end of file diff --git a/ScuffedMinecraft/src/Planet.h b/ScuffedMinecraft/src/Planet.h index 97bc214..ad39d0a 100644 --- a/ScuffedMinecraft/src/Planet.h +++ b/ScuffedMinecraft/src/Planet.h @@ -13,23 +13,29 @@ class Planet { // Methods public: - Planet(); + Planet(Shader* solidShader, Shader* waterShader, Shader* billboardShader); ~Planet(); std::vector GetChunkData(int chunkX, int chunkY, int chunkZ); - void Update(float camX, float camY, float camZ, unsigned int modelLoc); + void Update(float camX, float camY, float camZ); + + Chunk* GetChunk(int chunkX, int chunkY, int chunkZ); // Variables public: static Planet* planet; unsigned int numChunks = 0, numChunksRendered = 0; + static const unsigned int chunkSize = 32; private: std::unordered_map, Chunk> chunks; std::queue chunkQueue; int renderDistance = 3; int renderHeight = 1; - unsigned int chunkSize = 32; unsigned int chunksLoading = 0; int lastCamX = -100, lastCamY = -100, lastCamZ = -100; + + Shader* solidShader; + Shader* waterShader; + Shader* billboardShader; }; \ No newline at end of file diff --git a/ScuffedMinecraft/src/Vertex.h b/ScuffedMinecraft/src/Vertex.h new file mode 100644 index 0000000..4dc83bc --- /dev/null +++ b/ScuffedMinecraft/src/Vertex.h @@ -0,0 +1,58 @@ +#pragma once + +struct Vertex +{ + char posX, posY, posZ; + char texGridX, texGridY; + char direction; + + Vertex(char _posX, char _posY, char _posZ, char _texGridX, char _texGridY, char _direction) + { + posX = _posX; + posY = _posY; + posZ = _posZ; + + texGridX = _texGridX; + texGridY = _texGridY; + + direction = _direction; + } +}; + +struct WaterVertex +{ + char posX, posY, posZ; + char texGridX, texGridY; + char direction; + char top; + + WaterVertex(char _posX, char _posY, char _posZ, char _texGridX, char _texGridY, char _direction, char _top) + { + posX = _posX; + posY = _posY; + posZ = _posZ; + + texGridX = _texGridX; + texGridY = _texGridY; + + direction = _direction; + + top = _top; + } +}; + +struct BillboardVertex +{ + float posX, posY, posZ; + char texGridX, texGridY; + + BillboardVertex(float _posX, float _posY, float _posZ, char _texGridX, char _texGridY) + { + posX = _posX; + posY = _posY; + posZ = _posZ; + + texGridX = _texGridX; + texGridY = _texGridY; + } +}; \ No newline at end of file diff --git a/ScuffedMinecraft/src/WorldGen.cpp b/ScuffedMinecraft/src/WorldGen.cpp index 87d9036..6d434a4 100644 --- a/ScuffedMinecraft/src/WorldGen.cpp +++ b/ScuffedMinecraft/src/WorldGen.cpp @@ -15,7 +15,7 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi // Init noise settings static NoiseSettings surfaceSettings[]{ { 0.01f, 20.0f, 0 }, - { 0.1f, 3.0f, 0 } + { 0.05f, 3.0f, 0 } }; static int surfaceSettingsLength = sizeof(surfaceSettings) / sizeof(*surfaceSettings); @@ -30,70 +30,151 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi static int oreSettingsLength = sizeof(oreSettings) / sizeof(*oreSettings); static SurfaceFeature surfaceFeatures[]{ + // Pond + { + { 0.43f, 1.0f, 2.35f, .85f, 1, 0 }, // Noise + { // Blocks + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 13, 13, 0, 0, + 0, 0, 13, 13, 13, 0, 0, + 0, 0, 0, 13, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + + 0, 2, 13, 13, 2, 0, 0, + 0, 2, 13, 13, 13, 2, 0, + 2, 13, 13, 13, 13, 13, 2, + 2, 13, 13, 13, 13, 13, 2, + 2, 13, 13, 13, 13, 13, 2, + 0, 2, 13, 13, 13, 2, 0, + 0, 0, 2, 13, 2, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + }, + { // Replace? + false, false, false, false, false, false, false, + false, false, false, false, false, false, false, + false, false, false, true, true, false, false, + false, false, true, true, true, false, false, + false, false, false, true, false, false, false, + false, false, false, false, false, false, false, + false, false, false, false, false, false, false, + + false, false, true, true, false, false, false, + false, false, true, true, true, false, false, + false, true, true, true, true, true, false, + false, true, true, true, true, true, false, + false, true, true, true, true, true, false, + false, false, true, true, true, false, false, + false, false, false, true, false, false, false, + + false, false, true, true, false, false, false, + false, false, true, true, true, true, false, + false, true, true, true, true, true, false, + false, true, true, true, true, true, false, + false, true, true, true, true, true, false, + false, false, true, true, true, false, false, + false, false, false, true, false, false, false, + }, + 7, 3, 7, // Size + -3, -2, -3 // Offset + }, // Tree { { 4.23f, 1.0f, 8.54f, .8f, 1, 0 }, { - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 5, 0, - 0, 0, 0, 5, 5, 5, 5, - 0, 0, 0, 5, 5, 5, 0, - 0, 0, 0, 5, 5, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 5, 5, - 1, 4, 4, 4, 4, 5, 5, - 0, 0, 0, 5, 5, 5, 5, - 0, 0, 0, 5, 5, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 5, 0, - 0, 0, 0, 5, 5, 5, 5, - 0, 0, 0, 5, 5, 5, 0, - 0, 0, 0, 5, 5, 0, 0, + 0, 5, 5, 5, 0, + 5, 5, 5, 5, 5, + 5, 5, 4, 5, 5, + 5, 5, 5, 5, 5, + 0, 5, 5, 5, 0, + + 0, 5, 5, 5, 0, + 5, 5, 5, 5, 5, + 5, 5, 4, 5, 5, + 5, 5, 5, 5, 5, + 0, 5, 5, 5, 0, + + 0, 0, 0, 0, 0, + 0, 0, 5, 0, 0, + 0, 5, 5, 5, 0, + 0, 0, 5, 0, 0, + 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, + 0, 0, 5, 0, 0, + 0, 5, 5, 5, 0, + 0, 0, 5, 0, 0, + 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 5, 5, 0, 0, - 0, 0, 0, 0, 0, 0, 0, }, { - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, true, false, false, + false, false, false, false, false, + false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, true, false, false, + false, false, false, false, false, + false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - true, true, true, true, true, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, true, false, false, + false, false, false, false, false, + false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, true, false, false, + false, false, false, false, false, + false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - false, false, false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, true, false, false, + false, false, false, false, false, + false, false, false, false, false, + + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, + false, false, false, false, false, }, 5, 7, @@ -102,22 +183,6 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi 0, -2 }, - // Grass - { - { 2.65f, 1.0f, 8.54f, .5f, 1, 0 }, - { - 2, 6 - }, - { - false, false - }, - 1, - 2, - 1, - 0, - 0, - 0 - }, // Tall Grass { { 1.23f, 1.0f, 4.34f, .6f, 1, 0 }, @@ -134,6 +199,22 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi 0, 0 }, + // Grass + { + { 2.65f, 1.0f, 8.54f, .5f, 1, 0 }, + { + 2, 6 + }, + { + false, false + }, + 1, + 2, + 1, + 0, + 0, + 0 + }, // Poppy { { 5.32f, 1.0f, 3.67f, .8f, 1, 0 }, @@ -201,6 +282,8 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi }; static int surfaceFeaturesLength = sizeof(surfaceFeatures) / sizeof(*surfaceFeatures); + static int waterLevel = 20; + // Set vector size chunkData->reserve(chunkSize * chunkSize * chunkSize); @@ -249,9 +332,14 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi // Sky and Caves if (y + startY > noiseY) - chunkData->push_back(0); + { + if (y + startY <= waterLevel) + chunkData->push_back(Blocks::WATER); + else + chunkData->push_back(Blocks::AIR); + } else if (cave) - chunkData->push_back(0); + chunkData->push_back(Blocks::AIR); // Ground else { @@ -307,6 +395,10 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi if (noiseY + surfaceFeatures[i].offsetY > startY + 32 || noiseY + surfaceFeatures[i].sizeY + surfaceFeatures[i].offsetY < startY) continue; + + // Check if it's in water + if (noiseY < waterLevel) + continue; // Check if it's in a cave bool cave = false; @@ -361,9 +453,9 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi if (localZ >= 32 || localZ < 0) continue; - int featureIndex = fX * surfaceFeatures[i].sizeZ * surfaceFeatures[i].sizeY + - fZ * surfaceFeatures[i].sizeY + - fY; + int featureIndex = fY * surfaceFeatures[i].sizeX * surfaceFeatures[i].sizeZ + + fX * surfaceFeatures[i].sizeZ + + fZ; //std::cout << "Feature Index: " << featureIndex << '\n'; int localIndex = localX * chunkSize * chunkSize + localZ * chunkSize + localY; //std::cout << "Local Index: " << localIndex << ", Max Index: " << chunkData->size() << '\n'; @@ -373,11 +465,6 @@ void WorldGen::GenerateChunkData(int chunkX, int chunkY, int chunkZ, int chunkSi } } } - - //int index = x * chunkSize * chunkSize + z * chunkSize + noiseY; - //chunkData->at(index) = surfaceFeatures[i].block; - //index = x * chunkSize * chunkSize + z * chunkSize + noiseY + 1; - //chunkData->at(index) = surfaceFeatures[i].block; } } }