diff --git a/client/source/core/ClientApplication.cpp b/client/source/core/ClientApplication.cpp index 1d411b97..dad16853 100644 --- a/client/source/core/ClientApplication.cpp +++ b/client/source/core/ClientApplication.cpp @@ -30,6 +30,7 @@ #include #include +#include "BlockGeometry.hpp" #include "ClientApplication.hpp" #include "Config.hpp" #include "EngineConfig.hpp" @@ -42,6 +43,7 @@ namespace fs = ghc::filesystem; ClientApplication::ClientApplication(int argc, char **argv) : gk::CoreApplication(argc, argv) { + BlockGeometry::initOrientation(); } void ClientApplication::init() { diff --git a/client/source/gui/InventoryCube.cpp b/client/source/gui/InventoryCube.cpp index feb22e59..77733a8d 100644 --- a/client/source/gui/InventoryCube.cpp +++ b/client/source/gui/InventoryCube.cpp @@ -29,9 +29,11 @@ #include #include #include +#include #include #include "Block.hpp" +#include "BlockGeometry.hpp" #include "Config.hpp" #include "EngineConfig.hpp" #include "InventoryCube.hpp" @@ -49,98 +51,76 @@ InventoryCube::InventoryCube(float size) : m_textureAtlas(gk::ResourceHandler::g m_transform.rotate(135.0f, {0, 0, 1}); } +using namespace BlockGeometry; + void InventoryCube::updateVertexBuffer(const Block &block) { if (!block.id()) return; - // Same order as enum BlockFace in TilesDef.hpp - gk::Vertex vertices[6][4] = { - // West - { - {{0, m_size, 0, 2}}, - {{0, 0, 0, 2}}, - {{0, 0, m_size, 2}}, - {{0, m_size, m_size, 2}}, - }, + gk::Vertex vertices[nFaces][nVertsPerFace]; - // East - { - {{m_size, 0, 0, 2}}, - {{m_size, m_size, 0, 2}}, - {{m_size, m_size, m_size, 2}}, - {{m_size, 0, m_size, 2}}, - }, - - // South - { - {{0, 0, 0, 4}}, - {{m_size, 0, 0, 4}}, - {{m_size, 0, m_size, 4}}, - {{0, 0, m_size, 4}}, - }, - - // North - { - {{m_size, m_size, 0, 4}}, - {{0, m_size, 0, 4}}, - {{0, m_size, m_size, 4}}, - {{m_size, m_size, m_size, 4}}, - }, - - // Bottom - { - {{m_size, 0, 0, -1}}, - {{0, 0, 0, -1}}, - {{0, m_size, 0, -1}}, - {{m_size, m_size, 0, -1}}, - }, - - // Top - { - {{m_size, m_size, m_size, 3}}, - {{0, m_size, m_size, 3}}, - {{0, 0, m_size, 3}}, - {{m_size, 0, m_size, 3}}, - }, + glm::vec3 vertexPos[nVertsPerCube] { + // Order is important. It matches the bit order defined in BlockGeometry::cubeVerts. + {0, 0, 0}, + {m_size, 0, 0}, + {0, m_size, 0}, + {m_size, m_size, 0}, + {0, 0, m_size}, + {m_size, 0, m_size}, + {0, m_size, m_size}, + {m_size, m_size, m_size}, }; - for (u8 i = 0 ; i < 6 ; ++i) { - const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(block.tiles().getTextureForFace(i)); - float faceTexCoords[2 * 4] = { - blockTexCoords.x, blockTexCoords.y + blockTexCoords.sizeY, - blockTexCoords.x + blockTexCoords.sizeX, blockTexCoords.y + blockTexCoords.sizeY, - blockTexCoords.x + blockTexCoords.sizeX, blockTexCoords.y, - blockTexCoords.x, blockTexCoords.y - }; + const gk::FloatBox &boundingBox = block.boundingBox(); - for(u8 j = 0 ; j < 4 ; j++) { - if (block.drawType() == BlockDrawType::BoundingBox) { - vertices[i][j].coord3d[0] = vertices[i][j].coord3d[0] * block.boundingBox().sizeX + block.boundingBox().x; - vertices[i][j].coord3d[1] = vertices[i][j].coord3d[1] * block.boundingBox().sizeY + block.boundingBox().y; - vertices[i][j].coord3d[2] = vertices[i][j].coord3d[2] * block.boundingBox().sizeZ + block.boundingBox().z; + constexpr s8f faceValue[nFaces]{2, 2, 4, 4, -1, 3}; + + for (u8 f = 0; f < nFaces; ++f) { + // Calculate UV's + // These are tough to obtain. Note that texture Y grows in the up-down direction, and so does V. + // Vertex index in the bitmap array and U/V correspondence is: + // U0V0 -> 3 2 <- U1V0 + // U0V1 -> 0 1 <- U1V1 + float U0, V0, U1, V1; + if (block.drawType() == BlockDrawType::Cactus) { + U0 = 0.f; + V0 = 0.f; + U1 = 1.f; + V1 = 1.f; + } + else { + U0 = (f == 0) ? 1.f - (boundingBox.y + boundingBox.sizeY) : (f == 1) ? boundingBox.y : + (f == 3) ? 1.f - (boundingBox.x + boundingBox.sizeX) : boundingBox.x; + V0 = (f <= 3) ? 1.f - (boundingBox.z + boundingBox.sizeZ) : (f == 4) ? boundingBox.y : 1.f - (boundingBox.y + boundingBox.sizeY); + U1 = (f == 0) ? 1.f - boundingBox.y : (f == 1) ? boundingBox.y + boundingBox.sizeY : + (f == 3) ? 1.f - boundingBox.x : boundingBox.x + boundingBox.sizeX; + V1 = (f <= 3) ? 1.f - boundingBox.z : (f == 4) ? boundingBox.y + boundingBox.sizeY : 1.f - boundingBox.y; + } + + const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(block.tiles().getTextureForFace(f)); + + for (u8f v = 0; v < nVertsPerFace; ++v) { + if (block.drawType() == BlockDrawType::Cactus) { + vertices[f][v].coord3d[0] = vertexPos[cubeVerts[f][v]].x - boundingBox.x * faceNormals[f][0] * m_size; + vertices[f][v].coord3d[1] = vertexPos[cubeVerts[f][v]].y - boundingBox.y * faceNormals[f][1] * m_size; + vertices[f][v].coord3d[2] = vertexPos[cubeVerts[f][v]].z - boundingBox.z * faceNormals[f][2] * m_size; } - else if (block.drawType() == BlockDrawType::Cactus) { - static constexpr s8 normals[6][3] = { - {-1, 0, 0}, - {1, 0, 0}, - {0, -1, 0}, - {0, 1, 0}, - {0, 0, -1}, - {0, 0, 1} - }; - - vertices[i][j].coord3d[0] = vertices[i][j].coord3d[0] + block.boundingBox().x * -normals[i][0] * m_size; - vertices[i][j].coord3d[1] = vertices[i][j].coord3d[1] + block.boundingBox().y * -normals[i][1] * m_size; - vertices[i][j].coord3d[2] = vertices[i][j].coord3d[2] + block.boundingBox().z * -normals[i][2] * m_size; + else { + vertices[f][v].coord3d[0] = vertexPos[cubeVerts[f][v]].x * boundingBox.sizeX + boundingBox.x; + vertices[f][v].coord3d[1] = vertexPos[cubeVerts[f][v]].y * boundingBox.sizeY + boundingBox.y; + vertices[f][v].coord3d[2] = vertexPos[cubeVerts[f][v]].z * boundingBox.sizeZ + boundingBox.z; } + vertices[f][v].coord3d[3] = faceValue[f]; - vertices[i][j].texCoord[0] = faceTexCoords[j * 2]; - vertices[i][j].texCoord[1] = faceTexCoords[j * 2 + 1]; + float U = (v == 0 || v == 3) ? U0 : U1; + float V = (v >= 2) ? V0 : V1; + vertices[f][v].texCoord[0] = gk::qlerp(blockTexCoords.x, blockTexCoords.x + blockTexCoords.sizeX, U); + vertices[f][v].texCoord[1] = gk::qlerp(blockTexCoords.y, blockTexCoords.y + blockTexCoords.sizeY, V); const gk::Color &colorMultiplier = block.colorMultiplier(); - vertices[i][j].color[0] = colorMultiplier.r; - vertices[i][j].color[1] = colorMultiplier.g; - vertices[i][j].color[2] = colorMultiplier.b; - vertices[i][j].color[3] = colorMultiplier.a; + vertices[f][v].color[0] = colorMultiplier.r; + vertices[f][v].color[1] = colorMultiplier.g; + vertices[f][v].color[2] = colorMultiplier.b; + vertices[f][v].color[3] = colorMultiplier.a; } } @@ -159,23 +139,16 @@ void InventoryCube::draw(gk::RenderTarget &target, gk::RenderStates states) cons states.viewMatrix = gk::Transform::Identity; - // NOTE: This matrix has Y inverted as well as Z, causing the default - // rotation to show the bottom of the item instead of the top. - states.projectionMatrix = glm::ortho(0.0f, (float)Config::screenWidth, (float)Config::screenHeight, 0.0f, -40.0f, DIST_FAR); + // NOTE: This matrix has Y inverted as well as Z. This means that + // negative Z is closer to the user, and that the bottom side is visible + // at start. + states.projectionMatrix = glm::ortho(0.0f, (float)Config::screenWidth, (float)Config::screenHeight, 0.0f, DIST_2D_FAR, DIST_2D_NEAR); states.texture = &m_textureAtlas.texture(); states.vertexAttributes = gk::VertexAttribute::Only2d; - glCheck(glDisable(GL_CULL_FACE)); - glCheck(glDisable(GL_DEPTH_TEST)); - - target.draw(m_vbo, GL_QUADS, 4 * BlockFace::Top, 4, states); - // target.draw(m_vbo, GL_QUADS, 4 * 1, 4, states); - target.draw(m_vbo, GL_QUADS, 4 * BlockFace::West, 4, states); - // target.draw(m_vbo, GL_QUADS, 4 * 3, 4, states); - target.draw(m_vbo, GL_QUADS, 4 * BlockFace::North, 4, states); - // target.draw(m_vbo, GL_QUADS, 4 * 5, 4, states); - glCheck(glEnable(GL_CULL_FACE)); glCheck(glEnable(GL_DEPTH_TEST)); + + target.draw(m_vbo, GL_QUADS, 0, nFaces * nVertsPerFace, states); } diff --git a/client/source/gui/MouseItemWidget.cpp b/client/source/gui/MouseItemWidget.cpp index 37665279..6e9a7da3 100644 --- a/client/source/gui/MouseItemWidget.cpp +++ b/client/source/gui/MouseItemWidget.cpp @@ -239,6 +239,6 @@ void MouseItemWidget::updatePosition(float x, float y) { x -= m_parent->getPosition().x + 10 * m_parent->getScale().x; y -= m_parent->getPosition().y + 10 * m_parent->getScale().y; - setPosition(x / m_parent->getScale().x, y / m_parent->getScale().y, 0); + setPosition(x / m_parent->getScale().x, y / m_parent->getScale().y, -20); } diff --git a/client/source/hud/BlockCursor.cpp b/client/source/hud/BlockCursor.cpp index 5d24a98c..c8c94110 100644 --- a/client/source/hud/BlockCursor.cpp +++ b/client/source/hud/BlockCursor.cpp @@ -41,46 +41,6 @@ #include "Hotbar.hpp" #include "Registry.hpp" -// Same order as enum BlockFace in TilesDef.hpp -static float cubeCoords[6 * 4 * 3] = { - // West - 0, 1, 0, - 0, 0, 0, - 0, 0, 1, - 0, 1, 1, - - // East - 1, 0, 0, - 1, 1, 0, - 1, 1, 1, - 1, 0, 1, - - // South - 0, 0, 0, - 1, 0, 0, - 1, 0, 1, - 0, 0, 1, - - // North - 1, 1, 0, - 0, 1, 0, - 0, 1, 1, - 1, 1, 1, - - // Bottom - 1, 0, 0, - 0, 0, 0, - 0, 1, 0, - 1, 1, 0, - - // Top - 0, 0, 1, - 1, 0, 1, - 1, 1, 1, - 0, 1, 1, - -}; - BlockCursor::BlockCursor(ClientPlayer &player, ClientWorld &world, ClientCommandHandler &client) : m_player(player), m_world(world), m_client(client) { @@ -114,12 +74,13 @@ void BlockCursor::onEvent(const SDL_Event &event, const Hotbar &hotbar) { s32 y = m_selectedBlock.y; s32 z = m_selectedBlock.z; - if(face == 0) x++; - if(face == 3) x--; - if(face == 1) y++; - if(face == 4) y--; - if(face == 2) z++; - if(face == 5) z--; + // FIXME: Document where these face numbers come from + if (face == 0) ++x; + if (face == 3) --x; + if (face == 1) ++y; + if (face == 4) --y; + if (face == 2) ++z; + if (face == 5) --z; // First, we check if the new block is not replacing another block u32 blockId = m_world.getBlock(x, y, z); @@ -134,7 +95,7 @@ void BlockCursor::onEvent(const SDL_Event &event, const Hotbar &hotbar) { u32 block = hotbar.currentItem(); if (newBlock.isRotatable()) { - static constexpr u8 dirToFaceDir[4] = {0, 2, 1, 3}; + static constexpr u8 dirToFaceDir[4]{0, 2, 1, 3}; u16 data = dirToFaceDir[m_player.getOppositeDirection() & 0x3]; m_world.setData(x, y, z, data); @@ -198,55 +159,90 @@ void BlockCursor::update(const Hotbar &hotbar) { } } + u8f orientation = m_currentBlock->isRotatable() ? m_world.getData(selectedBlock.x, selectedBlock.y, selectedBlock.z) & 0x1F : 0; + if (m_selectedBlock.w != -1) - updateVertexBuffer(*m_currentBlock); + updateVertexBuffer(*m_currentBlock, orientation); else m_currentBlock = nullptr; if (m_animationStart && m_currentBlock) - updateAnimationVertexBuffer(*m_currentBlock, (gk::GameClock::getTicks() - m_animationStart) / (timeToBreak * 100)); + updateAnimationVertexBuffer(*m_currentBlock, orientation, + (gk::GameClock::getTicks() - m_animationStart) / (timeToBreak * 100)); } -void BlockCursor::updateVertexBuffer(const Block &block) { - gk::Vertex vertices[24]; - for (u8 i = 0 ; i < 24 ; ++i) { - vertices[i].coord3d[0] = cubeCoords[i * 3] * block.boundingBox().sizeX + block.boundingBox().x; - vertices[i].coord3d[1] = cubeCoords[i * 3 + 1] * block.boundingBox().sizeY + block.boundingBox().y; - vertices[i].coord3d[2] = cubeCoords[i * 3 + 2] * block.boundingBox().sizeZ + block.boundingBox().z; - vertices[i].coord3d[3] = -1; +using namespace BlockGeometry; + +void BlockCursor::updateVBOCoords(gk::Vertex vertices[nFaces][nVertsPerFace], const Block &block, + float face, u8f orientation) +{ + glm::vec3 bottomLeft{block.boundingBox().x, block.boundingBox().y, block.boundingBox().z}; + glm::vec3 topRight{block.boundingBox().sizeX, block.boundingBox().sizeY, block.boundingBox().sizeZ}; + topRight += bottomLeft; + + const glm::mat3 &orientMatrix = orientMatrices[orientation]; + + glm::vec3 vertexPos[nVertsPerCube]{ + // Order is important. It matches the bit order defined in BlockGeometry::cubeVerts. + {bottomLeft.x, bottomLeft.y, bottomLeft.z}, + {topRight.x, bottomLeft.y, bottomLeft.z}, + {bottomLeft.x, topRight.y, bottomLeft.z}, + {topRight.x, topRight.y, bottomLeft.z}, + {bottomLeft.x, bottomLeft.y, topRight.z}, + {topRight.x, bottomLeft.y, topRight.z}, + {bottomLeft.x, topRight.y, topRight.z}, + {topRight.x, topRight.y, topRight.z}, + }; + + if (orientation != 0) { + static const glm::vec3 half{0.5, 0.5, 0.5}; + // Rotate each vertex coordinate around the centre of the cube + for (int i = 0; i < nVertsPerCube; ++i) { + vertexPos[i] = orientMatrix * (vertexPos[i] - half) + half; + } } + for (u8f f = 0 ; f < nFaces; ++f) { + for (u8f v = 0; v < nVertsPerFace; ++v) { + vertices[f][v].coord3d[0] = vertexPos[cubeVerts[f][v]].x; + vertices[f][v].coord3d[1] = vertexPos[cubeVerts[f][v]].y; + vertices[f][v].coord3d[2] = vertexPos[cubeVerts[f][v]].z; + vertices[f][v].coord3d[3] = face; + } + } +} + +void BlockCursor::updateVertexBuffer(const Block &block, u8f orientation) { + gk::Vertex vertices[nFaces][nVertsPerFace]; + updateVBOCoords(vertices, block, -1, orientation); + gk::VertexBuffer::bind(&m_vbo); m_vbo.setData(sizeof(vertices), vertices, GL_DYNAMIC_DRAW); gk::VertexBuffer::bind(nullptr); } -void BlockCursor::updateAnimationVertexBuffer(const Block &block, int animationPos) { - gk::Vertex vertices[24]; - for (u8 i = 0 ; i < 24 ; ++i) { - vertices[i].coord3d[0] = cubeCoords[i * 3] * block.boundingBox().sizeX + block.boundingBox().x; - vertices[i].coord3d[1] = cubeCoords[i * 3 + 1] * block.boundingBox().sizeY + block.boundingBox().y; - vertices[i].coord3d[2] = cubeCoords[i * 3 + 2] * block.boundingBox().sizeZ + block.boundingBox().z; - vertices[i].coord3d[3] = -2; - } +void BlockCursor::updateAnimationVertexBuffer(const Block &block, u8f orientation, int animationPos) { + gk::Vertex vertices[nFaces][nVertsPerFace]; + updateVBOCoords(vertices, block, -2, orientation); GLfloat color[4] = {1, 1, 1, 0.5}; - for (int i = 0 ; i < 24 ; ++i) - memcpy(vertices[i].color, color, 4 * sizeof(GLfloat)); + for (u8f f = 0; f < nFaces; ++f) + for (u8f v = 0; v < nVertsPerFace; ++v) + memcpy(&vertices[f][v].color, &color[0], 4 * sizeof(GLfloat)); if (animationPos != -1) { glm::vec4 blockTexCoords{0.1f * animationPos, 0.0, 0.1f + 0.1f * animationPos, 1.0}; - float faceTexCoords[2 * 4] = { - blockTexCoords.x, blockTexCoords.w, - blockTexCoords.z, blockTexCoords.w, - blockTexCoords.z, blockTexCoords.y, - blockTexCoords.x, blockTexCoords.y + float faceTexCoords[nVertsPerFace][nCoordsPerUV] = { + {blockTexCoords.x, blockTexCoords.w}, + {blockTexCoords.z, blockTexCoords.w}, + {blockTexCoords.z, blockTexCoords.y}, + {blockTexCoords.x, blockTexCoords.y}, }; - for (u8 i = 0 ; i < 6 ; ++i) { - for(u8 j = 0 ; j < 4 ; j++) { - vertices[j + i * 4].texCoord[0] = faceTexCoords[j * 2]; - vertices[j + i * 4].texCoord[1] = faceTexCoords[j * 2 + 1]; + for (u8f f = 0 ; f < nFaces ; ++f) { + for(u8f v = 0 ; v < nVertsPerFace ; v++) { + vertices[f][v].texCoord[0] = faceTexCoords[v][0]; + vertices[f][v].texCoord[1] = faceTexCoords[v][1]; } } } @@ -267,7 +263,7 @@ void BlockCursor::draw(gk::RenderTarget &target, gk::RenderStates states) const states.transform.translate(m_selectedBlock.x - cameraPosition.x, m_selectedBlock.y - cameraPosition.y, m_selectedBlock.z - cameraPosition.z); glCheck(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); - target.draw(m_vbo, GL_QUADS, 0, 24, states); + target.draw(m_vbo, GL_QUADS, 0, nFaces * nVertsPerFace, states); glCheck(glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); if (m_animationStart > 0) { @@ -276,7 +272,7 @@ void BlockCursor::draw(gk::RenderTarget &target, gk::RenderStates states) const states.texture = m_blockDestroyTexture; - target.draw(m_animationVBO, GL_QUADS, 0, 24, states); + target.draw(m_animationVBO, GL_QUADS, 0, nFaces * nVertsPerFace, states); glCheck(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); } @@ -289,12 +285,12 @@ glm::ivec4 BlockCursor::findSelectedBlock() const { m_player.camera().getDPosition().y, m_player.camera().getDPosition().z}; - int_fast32_t bestX = int_fast32_t(floor(position.x)); - int_fast32_t bestY = int_fast32_t(floor(position.y)); - int_fast32_t bestZ = int_fast32_t(floor(position.z)); + s32f bestX = s32f(floor(position.x)); + s32f bestY = s32f(floor(position.y)); + s32f bestZ = s32f(floor(position.z)); // Deal with a degenerate case: camera in the middle of a block - uint_fast32_t blockID = m_world.getBlock(bestX, bestY, bestZ); + u32f blockID = m_world.getBlock(bestX, bestY, bestZ); const Block &block = Registry::getInstance().getBlock(blockID); if (blockID && block.drawType() != BlockDrawType::Liquid) { // We're inside a node, therefore there's no face, but we still need @@ -327,7 +323,7 @@ glm::ivec4 BlockCursor::findSelectedBlock() const { // Ray casting algorithm to find out which block we are looking at const double maxReach = 10.; double bestDepth; - int_fast8_t bestFace = -1; + s8f bestFace = -1; glm::dvec3 lookAtN = glm::normalize(lookAt); @@ -337,4 +333,3 @@ glm::ivec4 BlockCursor::findSelectedBlock() const { return glm::ivec4{bestX, bestY, bestZ, bestFace}; } - diff --git a/client/source/hud/BlockCursor.hpp b/client/source/hud/BlockCursor.hpp index ed5d5aba..9d6a29d9 100644 --- a/client/source/hud/BlockCursor.hpp +++ b/client/source/hud/BlockCursor.hpp @@ -31,6 +31,7 @@ #include "ClientWorld.hpp" #include "Inventory.hpp" +#include "BlockGeometry.hpp" class ClientCommandHandler; class ClientPlayer; @@ -47,8 +48,10 @@ class BlockCursor : public gk::Drawable { const Block *currentBlock() const { return m_currentBlock; } private: - void updateVertexBuffer(const Block &block); - void updateAnimationVertexBuffer(const Block &block, int animationPos = -1); + void updateVertexBuffer(const Block &block, const u8f orientation); + void updateAnimationVertexBuffer(const Block &block, const u8f orientation, int animationPos = -1); + void updateVBOCoords(gk::Vertex vertices[BlockGeometry::nFaces][BlockGeometry::nVertsPerFace], + const Block &block, float face, u8f orientation); void draw(gk::RenderTarget &target, gk::RenderStates states) const override; diff --git a/client/source/hud/HUD.cpp b/client/source/hud/HUD.cpp index 8e8f0675..d33475c7 100644 --- a/client/source/hud/HUD.cpp +++ b/client/source/hud/HUD.cpp @@ -51,7 +51,7 @@ HUD::HUD(ClientPlayer &player, ClientWorld &world, ClientCommandHandler &client) } void HUD::setup() { - m_orthoMatrix = glm::ortho(0.0f, (float)Config::screenWidth, (float)Config::screenHeight, 0.0f); + m_orthoMatrix = glm::ortho(0.0f, (float)Config::screenWidth, (float)Config::screenHeight, 0.0f, DIST_2D_FAR, DIST_2D_NEAR); m_hotbar.setPosition(Config::screenWidth / getScale().x / 2 - m_hotbar.width() / 2, Config::screenHeight / getScale().y - m_hotbar.height(), 0); diff --git a/client/source/math/BlockCursorRaycast.cpp b/client/source/math/BlockCursorRaycast.cpp index a0429b01..155697e3 100644 --- a/client/source/math/BlockCursorRaycast.cpp +++ b/client/source/math/BlockCursorRaycast.cpp @@ -25,6 +25,7 @@ * ===================================================================================== */ #include "BlockCursorRaycast.hpp" +#include "BlockGeometry.hpp" #include "ClientWorld.hpp" #include "Registry.hpp" @@ -47,16 +48,10 @@ static inline glm::dvec3 intersectAxisPlane(const Axis axis, const double coord, static inline void recordHit(const glm::dvec3 &position, const glm::dvec3 &isect, - const Axis axis, - const bool neg, - const int_fast32_t nx, - const int_fast32_t ny, - const int_fast32_t nz, - int_fast32_t &bestX, - int_fast32_t &bestY, - int_fast32_t &bestZ, - int_fast8_t &bestFace, - double &bestDepth, + const Axis axis, const bool neg, + const s32f nx, const s32f ny, const s32f nz, + s32f &bestX, s32f &bestY, s32f &bestZ, + s8f &bestFace, double &bestDepth, bool &hit) { // Check if we have a record @@ -76,11 +71,9 @@ static inline void recordHit(const glm::dvec3 &position, } void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &position, - const glm::dvec3 &lookAt, - const double maxReach, - int_fast32_t &bestX, int_fast32_t &bestY, - int_fast32_t &bestZ, - int_fast8_t &bestFace, double &bestDepth, + const glm::dvec3 &lookAt, const double maxReach, + s32f &bestX, s32f &bestY, s32f &bestZ, + s8f &bestFace, double &bestDepth, const ClientWorld &world) { glm::dvec3 isect; @@ -90,7 +83,7 @@ void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &positi // of 'lookAt' with length 'maxReach' crosses several nodes in // the X direction at integer positions. Determine the first and // last such positions. - switch(axis) { + switch (axis) { case AXIS_X: posCoord = position.x; lookAtCoord = lookAt.x; @@ -105,8 +98,8 @@ void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &positi break; } - int_fast32_t firstNodeRow = lookAtCoord < 0. ? int_fast32_t(floor(posCoord)) : int_fast32_t(ceil(posCoord)) - 1; - int_fast32_t lastNodeRow = int_fast32_t(floor(posCoord + lookAtCoord * maxReach)); + s32f firstNodeRow = lookAtCoord < 0. ? s32f(floor(posCoord)) : s32f(ceil(posCoord)) - 1; + s32f lastNodeRow = s32f(floor(posCoord + lookAtCoord * maxReach)); int_fast8_t dir = (lookAtCoord > 0.) - (lookAtCoord < 0.); if (!dir) { @@ -114,39 +107,58 @@ void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &positi return; } - for(int_fast32_t nodeRow = firstNodeRow + dir; - dir > 0 ? (nodeRow <= lastNodeRow) : (nodeRow >= lastNodeRow); nodeRow += dir) + for (s32f nodeRow = firstNodeRow + dir; + dir > 0 ? (nodeRow <= lastNodeRow) : (nodeRow >= lastNodeRow); nodeRow += dir) { isect = intersectAxisPlane(axis, double(nodeRow + (dir < 0)), position, lookAt); - int_fast32_t nx, ny, nz; + s32f nx, ny, nz; nx = axis == AXIS_X ? nodeRow : floor(isect.x); ny = axis == AXIS_Y ? nodeRow : floor(isect.y); nz = axis == AXIS_Z ? nodeRow : floor(isect.z); u32 blockID = world.getBlock(nx, ny, nz); const Block &block = Registry::getInstance().getBlock(blockID); - if(blockID && block.drawType() != BlockDrawType::Liquid) { + + u8f orientation = block.isRotatable() ? world.getData(nx, ny, nz) & 0x1F : 0; + + const gk::FloatBox &boundingBox = block.boundingBox(); + glm::vec3 localCorner1{boundingBox.x, boundingBox.y, boundingBox.z}; + glm::vec3 localCorner2{boundingBox.sizeX, boundingBox.sizeY, boundingBox.sizeZ}; + localCorner2 += localCorner1; + + if (orientation) { + const glm::vec3 half{0.5, 0.5, 0.5}; + localCorner1 = BlockGeometry::orientMatrices[orientation] * (localCorner1 - half) + half; + localCorner2 = BlockGeometry::orientMatrices[orientation] * (localCorner2 - half) + half; + if (localCorner2.x < localCorner1.x) std::swap(localCorner1.x, localCorner2.x); + if (localCorner2.y < localCorner1.y) std::swap(localCorner1.y, localCorner2.y); + if (localCorner2.z < localCorner1.z) std::swap(localCorner1.z, localCorner2.z); + } + + if (blockID && block.drawType() != BlockDrawType::Liquid) { // Check bounding box; this should loop over all selection boxes // when they are implemented - gk::DoubleBox selBox = block.boundingBox() + gk::Vector3d{double(nx), double(ny), double(nz)}; + const glm::dvec3 cubePos{double(nx), double(ny), double(nz)}; + glm::dvec3 corner1 = glm::dvec3(localCorner1) + cubePos; + glm::dvec3 corner2 = glm::dvec3(localCorner2) + cubePos; bool hit = false; // Check if we hit any of the sides of the inner box - isect = intersectAxisPlane(AXIS_X, (lookAt.x < 0. ? selBox.x + selBox.sizeX : selBox.x), position, lookAt); - if (selBox.y <= isect.y && isect.y <= selBox.y + selBox.sizeY - && selBox.z <= isect.z && isect.z <= selBox.z + selBox.sizeZ) + isect = intersectAxisPlane(AXIS_X, (lookAt.x < 0. ? corner2.x : corner1.x), position, lookAt); + if (corner1.y <= isect.y && isect.y <= corner2.y + && corner1.z <= isect.z && isect.z <= corner2.z) recordHit(position, isect, AXIS_X, lookAt.x < 0., nx, ny, nz, bestX, bestY, bestZ, bestFace, bestDepth, hit); - isect = intersectAxisPlane(AXIS_Y, (lookAt.y < 0. ? selBox.y + selBox.sizeY : selBox.y), position, lookAt); - if (selBox.x <= isect.x && isect.x <= selBox.x + selBox.sizeX - && selBox.z <= isect.z && isect.z <= selBox.z + selBox.sizeZ) + isect = intersectAxisPlane(AXIS_Y, (lookAt.y < 0. ? corner2.y : corner1.y), position, lookAt); + if (corner1.x <= isect.x && isect.x <= corner2.x + && corner1.z <= isect.z && isect.z <= corner2.z) recordHit(position, isect, AXIS_Y, lookAt.y < 0., nx, ny, nz, bestX, bestY, bestZ, bestFace, bestDepth, hit); - isect = intersectAxisPlane(AXIS_Z, (lookAt.z < 0. ? selBox.z + selBox.sizeZ : selBox.z), position, lookAt); - if (selBox.x <= isect.x && isect.x <= selBox.x + selBox.sizeX - && selBox.y <= isect.y && isect.y <= selBox.y + selBox.sizeY) + isect = intersectAxisPlane(AXIS_Z, (lookAt.z < 0. ? corner2.z : corner1.z), position, lookAt); + if (corner1.x <= isect.x && isect.x <= corner2.x + && corner1.y <= isect.y && isect.y <= corner2.y) recordHit(position, isect, AXIS_Z, lookAt.z < 0., nx, ny, nz, bestX, bestY, bestZ, bestFace, bestDepth, hit); if (hit) @@ -154,4 +166,3 @@ void BlockCursorRaycast::rayCastToAxis(const Axis axis, const glm::dvec3 &positi } } } - diff --git a/client/source/math/BlockCursorRaycast.hpp b/client/source/math/BlockCursorRaycast.hpp index 08725e98..489fc768 100644 --- a/client/source/math/BlockCursorRaycast.hpp +++ b/client/source/math/BlockCursorRaycast.hpp @@ -27,6 +27,7 @@ #ifndef BLOCKCURSORRAYCAST_HPP_ #define BLOCKCURSORRAYCAST_HPP_ +#include #include enum Axis { @@ -39,12 +40,10 @@ class ClientWorld; namespace BlockCursorRaycast { void rayCastToAxis(const Axis axis, const glm::dvec3 &position, - const glm::dvec3 &lookAt, - const double maxReach, - int_fast32_t &bestX, int_fast32_t &bestY, - int_fast32_t &bestZ, - int_fast8_t &bestFace, double &bestDepth, + const glm::dvec3 &lookAt, const double maxReach, + s32f &bestX, s32f &bestY, s32f &bestZ, + s8f &bestFace, double &bestDepth, const ClientWorld &world); -} +} // namespace BlockCursorRaycast #endif // BLOCKCURSORRAYCAST_HPP_ diff --git a/client/source/states/InterfaceState.cpp b/client/source/states/InterfaceState.cpp index 1682ab5c..f4eb0366 100644 --- a/client/source/states/InterfaceState.cpp +++ b/client/source/states/InterfaceState.cpp @@ -29,6 +29,7 @@ #include #include "Config.hpp" +#include "EngineConfig.hpp" #include "InterfaceState.hpp" InterfaceState::InterfaceState(gk::ApplicationState *parent) : gk::ApplicationState(parent) { @@ -43,7 +44,7 @@ InterfaceState::InterfaceState(gk::ApplicationState *parent) : gk::ApplicationSt } void InterfaceState::setup() { - m_projectionMatrix = glm::ortho(0.0f, (float)Config::screenWidth, (float)Config::screenHeight, 0.0f); + m_projectionMatrix = glm::ortho(0.0f, (float)Config::screenWidth, (float)Config::screenHeight, 0.0f, DIST_2D_FAR, DIST_2D_NEAR); m_background.setSize(Config::screenWidth, Config::screenHeight); diff --git a/client/source/world/ChunkBuilder.cpp b/client/source/world/ChunkBuilder.cpp index 9b5a6378..4f41212e 100644 --- a/client/source/world/ChunkBuilder.cpp +++ b/client/source/world/ChunkBuilder.cpp @@ -24,114 +24,21 @@ * * ===================================================================================== */ +#include + +#include "BlockGeometry.hpp" #include "ClientChunk.hpp" #include "ChunkBuilder.hpp" #include "Registry.hpp" #include "TextureAtlas.hpp" -constexpr int nAxes = 6; -constexpr int nAxesP2 = 8; // Next power of 2 to nAxes -constexpr int nRots = 4; -constexpr int nFaces = 6; -constexpr int nCrossFaces = 2; -constexpr int nVertsPerFace = 4; -constexpr int nNormals = 1; -constexpr int nCoords = 3; -constexpr int nCoordsPerUV = 2; +using namespace BlockGeometry; -// Same order as enum BlockFace in TilesDef.hpp -static constexpr ChunkBuilder::tCubeCoord cubeCoords[nFaces][nVertsPerFace + nNormals][nCoords] = { - // West - { - {0, 1, 0}, - {0, 0, 0}, - {0, 0, 1}, - {0, 1, 1}, - - {-1, 0, 0}, // normal - }, - - // East - { - {1, 0, 0}, - {1, 1, 0}, - {1, 1, 1}, - {1, 0, 1}, - - {1, 0, 0}, - }, - - // South - { - {0, 0, 0}, - {1, 0, 0}, - {1, 0, 1}, - {0, 0, 1}, - - {0,-1, 0}, - }, - - // North - { - {1, 1, 0}, - {0, 1, 0}, - {0, 1, 1}, - {1, 1, 1}, - - {0, 1, 0}, - }, - - // Bottom - { - {1, 0, 0}, - {0, 0, 0}, - {0, 1, 0}, - {1, 1, 0}, - - {0, 0,-1}, - }, - - // Top - { - {1, 1, 1}, - {0, 1, 1}, - {0, 0, 1}, - {1, 0, 1}, - - {0, 0, 1}, - }, -}; - -static constexpr ChunkBuilder::tCubeCoord crossCoords[nCrossFaces][nVertsPerFace][nCoords] = { - { - {1, 1, 0}, - {0, 0, 0}, - {0, 0, 1}, - {1, 1, 1}, - }, - - { - {1, 0, 0}, - {0, 1, 0}, - {0, 1, 1}, - {1, 0, 1}, - }, -}; - -static ChunkBuilder::tCubeCoord orientCubeCoords[nAxesP2 * nRots][nFaces][nVertsPerFace + nNormals][nCoords]; - -ChunkBuilder::ChunkBuilder(TextureAtlas &textureAtlas) : m_textureAtlas(textureAtlas) { - static bool isOrientInitialized = false; - - if (!isOrientInitialized) { - initializeOrientation(); - isOrientInitialized = true; - } -} - -std::array ChunkBuilder::buildChunk(const ClientChunk &chunk, const std::array &vbo) { +std::array ChunkBuilder::buildChunk(const ClientChunk &chunk, + const std::array &vbo) +{ for (s8f i = 0 ; i < layers ; ++i) - m_vertices[i].reserve(CHUNK_WIDTH * CHUNK_DEPTH * CHUNK_HEIGHT * 6 * 4); + m_vertices[i].reserve(CHUNK_WIDTH * CHUNK_DEPTH * CHUNK_HEIGHT * nFaces * nVertsPerFace); for (s8f z = 0 ; z < CHUNK_HEIGHT ; z++) { for (s8f y = 0 ; y < CHUNK_DEPTH ; y++) { @@ -140,18 +47,94 @@ std::array ChunkBuilder::buildChunk(const Cli if (!block.id()) continue; if (!chunk.getBlock(x, y, z)) continue; + const gk::FloatBox &boundingBox = block.boundingBox(); + + u8f orientation = block.isRotatable() ? chunk.getData(x, y, z) & 0x1F : 0; + const glm::mat3 &orientMatrix = orientMatrices[orientation]; + if (block.drawType() == BlockDrawType::Solid || block.drawType() == BlockDrawType::Leaves || block.drawType() == BlockDrawType::Liquid || block.drawType() == BlockDrawType::Glass || block.drawType() == BlockDrawType::Cactus - || block.drawType() == BlockDrawType::BoundingBox) { - for (s8f i = 0 ; i < nFaces ; i++) { - addFace(x, y, z, i, chunk, &block); + || block.drawType() == BlockDrawType::BoundingBox) + { + glm::vec3 vertexPos[nVertsPerCube]{ + // Order is important. It matches the bit order defined in BlockGeometry::cubeVerts. + {boundingBox.x, boundingBox.y, boundingBox.z}, + {boundingBox.x + boundingBox.sizeX, boundingBox.y, boundingBox.z}, + {boundingBox.x, boundingBox.y + boundingBox.sizeY, boundingBox.z}, + {boundingBox.x + boundingBox.sizeX, boundingBox.y + boundingBox.sizeY, boundingBox.z}, + {boundingBox.x, boundingBox.y, boundingBox.z + boundingBox.sizeZ}, + {boundingBox.x + boundingBox.sizeX, boundingBox.y, boundingBox.z + boundingBox.sizeZ}, + {boundingBox.x, boundingBox.y + boundingBox.sizeY, boundingBox.z + boundingBox.sizeZ}, + {boundingBox.x + boundingBox.sizeX, boundingBox.y + boundingBox.sizeY, boundingBox.z + boundingBox.sizeZ}, + }; + + if (block.drawType() == BlockDrawType::Cactus) { + // Ignore bounding box, initialize it to full node coordinates + for (u8f i = 0; i < nVertsPerCube; ++i) { + vertexPos[i].x = (i >> 0) & 1; + vertexPos[i].y = (i >> 1) & 1; + vertexPos[i].z = (i >> 2) & 1; + } + } + + // vNeighbour is used to find neighbouring cubes per vertex. + // Same binary layout. + glm::vec3 vNeighbour[nVertsPerCube] = { + {-1,-1,-1}, { 1,-1,-1}, {-1, 1,-1}, { 1, 1,-1}, {-1,-1, 1}, { 1,-1, 1}, {-1, 1, 1}, {1, 1, 1}, + }; + + if (orientation) { // don't work extra if it's not oriented differently + static const glm::vec3 half{0.5, 0.5, 0.5}; + // Rotate each vertex coordinate around the centre of the + // cube, and each vertex neighbour around the origin + for (int i = 0; i < nVertsPerCube; ++i) { + vertexPos[i] = orientMatrix * (vertexPos[i] - half) + half; + vNeighbour[i] = orientMatrix * vNeighbour[i]; + } + } + + for (s8f f = 0; f < nFaces ; ++f) { + // Construct the normal vector to a face + const glm::vec3 glmNormal = orientMatrix * faceNormals[f]; + const gk::Vector3i normal{int(glmNormal.x), int(glmNormal.y), int(glmNormal.z)}; + + // Construct an array with the 4 vertex positions of this face + glm::vec3 *faceVerts[nVertsPerFace]{&vertexPos[cubeVerts[f][0]], &vertexPos[cubeVerts[f][1]], + &vertexPos[cubeVerts[f][2]], &vertexPos[cubeVerts[f][3]]}; + + // Construct an array with the 4 vertex neighbours of this face + // (as GameKit integer vectors) + const gk::Vector3i corner0{int(vNeighbour[cubeVerts[f][0]].x), int(vNeighbour[cubeVerts[f][0]].y), int(vNeighbour[cubeVerts[f][0]].z)}; + const gk::Vector3i corner1{int(vNeighbour[cubeVerts[f][1]].x), int(vNeighbour[cubeVerts[f][1]].y), int(vNeighbour[cubeVerts[f][1]].z)}; + const gk::Vector3i corner2{int(vNeighbour[cubeVerts[f][2]].x), int(vNeighbour[cubeVerts[f][2]].y), int(vNeighbour[cubeVerts[f][2]].z)}; + const gk::Vector3i corner3{int(vNeighbour[cubeVerts[f][3]].x), int(vNeighbour[cubeVerts[f][3]].y), int(vNeighbour[cubeVerts[f][3]].z)}; + + const gk::Vector3i *vFaceNeighbours[nVertsPerFace]{&corner0, &corner1, &corner2, &corner3}; + + addFace(x, y, z, f, chunk, block, normal, faceVerts, vFaceNeighbours); } } else if (block.drawType() == BlockDrawType::XShape) { - addCross(x, y, z, chunk, &block); + glm::vec3 vertexPos[nVertsPerCube]{ + {0, 0, 0}, + {1, 0, 0}, + {0, 1, 0}, + {1, 1, 0}, + {0, 0, 1}, + {1, 0, 1}, + {0, 1, 1}, + {1, 1, 1}, + }; + const glm::vec3 *const faceVertices[nCrossFaces][nVertsPerFace]{ + {&vertexPos[crossVerts[0][0]], &vertexPos[crossVerts[0][1]], + &vertexPos[crossVerts[0][2]], &vertexPos[crossVerts[0][3]]}, + {&vertexPos[crossVerts[1][0]], &vertexPos[crossVerts[1][1]], + &vertexPos[crossVerts[1][2]], &vertexPos[crossVerts[1][3]]}, + }; + addCross(x, y, z, chunk, block, faceVertices); } } } @@ -173,54 +156,61 @@ std::array ChunkBuilder::buildChunk(const Cli return verticesCount; } -inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const Block *block) { - u8 orientation = block->isRotatable() ? chunk.getData(x, y, z) & 0x1F : 0; - gk::Vector3i normal{orientCubeCoords[orientation][f][4][0], - orientCubeCoords[orientation][f][4][1], - orientCubeCoords[orientation][f][4][2]}; - - // Get surrounding block for that face +inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const Block &block, + const gk::Vector3i &normal, const glm::vec3 *const vertexPos[nVertsPerFace], + const gk::Vector3i *const neighbourOfs[nVertsPerFace]) +{ + // Get surrounding block for the face u16 surroundingBlockID = chunk.getBlock(x + normal.x, y + normal.y, z + normal.z); const Block *surroundingBlock = &Registry::getInstance().getBlock(surroundingBlockID); // Skip hidden faces if (surroundingBlock && surroundingBlock->id() - && ((block->drawType() == BlockDrawType::Solid && surroundingBlock->drawType() == BlockDrawType::Solid && surroundingBlock->isOpaque()) - || (block->id() == surroundingBlock->id() && (block->drawType() == BlockDrawType::Liquid || block->drawType() == BlockDrawType::Glass)) - || (block->drawType() == BlockDrawType::Liquid && surroundingBlock->drawType() == BlockDrawType::Solid) - || (block->drawType() == BlockDrawType::Cactus && surroundingBlock->id() == block->id()))) + && ((block.drawType() == BlockDrawType::Solid && surroundingBlock->drawType() == BlockDrawType::Solid && surroundingBlock->isOpaque()) + || (block.id() == surroundingBlock->id() && (block.drawType() == BlockDrawType::Liquid || block.drawType() == BlockDrawType::Glass)) + || (block.drawType() == BlockDrawType::Liquid && surroundingBlock->drawType() == BlockDrawType::Solid) + || (block.drawType() == BlockDrawType::Cactus && surroundingBlock->id() == block.id()))) return; + const gk::FloatBox &boundingBox = block.boundingBox(); + const BlockData *blockData = chunk.getBlockData(x, y, z); - const std::string &texture = block->tiles().getTextureForFace(f, blockData ? blockData->useAltTiles : false); + const std::string &texture = block.tiles().getTextureForFace(f, blockData ? blockData->useAltTiles : false); const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(texture); - float faceTexCoords[nVertsPerFace][nCoordsPerUV] = { - {blockTexCoords.x, blockTexCoords.y + blockTexCoords.sizeY}, - {blockTexCoords.x + blockTexCoords.sizeX, blockTexCoords.y + blockTexCoords.sizeY}, - {blockTexCoords.x + blockTexCoords.sizeX, blockTexCoords.y}, - {blockTexCoords.x, blockTexCoords.y}, - }; - const gk::FloatBox boundingBox = block->boundingBox(); + // Calculate UV's + // These are tough to obtain. Note that texture Y grows in the up-down direction, and so does V. + // Vertex index in the bitmap array and U/V correspondence is: + // U0V0 -> 3 2 <- U1V0 + // U0V1 -> 0 1 <- U1V1 + float U0, V0, U1, V1; + if (block.drawType() == BlockDrawType::Cactus) { + U0 = 0.f; + V0 = 0.f; + U1 = 1.f; + V1 = 1.f; + } + else { + U0 = (f == 0) ? 1.f - (boundingBox.y + boundingBox.sizeY) : (f == 1) ? boundingBox.y : + (f == 3) ? 1.f - (boundingBox.x + boundingBox.sizeX) : boundingBox.x; + V0 = (f <= 3) ? 1.f - (boundingBox.z + boundingBox.sizeZ) : (f == 4) ? boundingBox.y : 1.f - (boundingBox.y + boundingBox.sizeY); + U1 = (f == 0) ? 1.f - boundingBox.y : (f == 1) ? boundingBox.y + boundingBox.sizeY : + (f == 3) ? 1.f - boundingBox.x : boundingBox.x + boundingBox.sizeX; + V1 = (f <= 3) ? 1.f - boundingBox.z : (f == 4) ? boundingBox.y + boundingBox.sizeY : 1.f - boundingBox.y; + } - // Store vertex information + // Prepare vertex information for VBO gk::Vertex vertices[nVertsPerFace]; - for (s8f v = 0 ; v < nVertsPerFace; v++) { - tCubeCoord *vertexPosPtr = orientCubeCoords[orientation][f][v]; - if (block->drawType() == BlockDrawType::BoundingBox) { - vertices[v].coord3d[0] = x + vertexPosPtr[0] * boundingBox.sizeX + boundingBox.x; - vertices[v].coord3d[1] = y + vertexPosPtr[1] * boundingBox.sizeY + boundingBox.y; - vertices[v].coord3d[2] = z + vertexPosPtr[2] * boundingBox.sizeZ + boundingBox.z; - } - else if (block->drawType() == BlockDrawType::Cactus) { - vertices[v].coord3d[0] = x + vertexPosPtr[0] + boundingBox.x * -normal.x; - vertices[v].coord3d[1] = y + vertexPosPtr[1] + boundingBox.y * -normal.y; - vertices[v].coord3d[2] = z + vertexPosPtr[2] + boundingBox.z * -normal.z; + for (s8f v = 0; v < nVertsPerFace; ++v) { + if (block.drawType() == BlockDrawType::Cactus) { + vertices[v].coord3d[0] = x + vertexPos[v]->x - boundingBox.x * normal.x; + vertices[v].coord3d[1] = y + vertexPos[v]->y - boundingBox.y * normal.y; + vertices[v].coord3d[2] = z + vertexPos[v]->z - boundingBox.z * normal.z; } else { - vertices[v].coord3d[0] = x + vertexPosPtr[0]; - vertices[v].coord3d[1] = y + vertexPosPtr[1]; - vertices[v].coord3d[2] = z + vertexPosPtr[2]; + vertices[v].coord3d[0] = x + vertexPos[v]->x; + vertices[v].coord3d[1] = y + vertexPos[v]->y; + vertices[v].coord3d[2] = z + vertexPos[v]->z; } vertices[v].coord3d[3] = f; @@ -229,36 +219,38 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk vertices[v].normal[1] = normal.y; vertices[v].normal[2] = normal.z; - const gk::Color colorMultiplier = block->colorMultiplier(); + const gk::Color colorMultiplier = block.colorMultiplier(); vertices[v].color[0] = colorMultiplier.r; vertices[v].color[1] = colorMultiplier.g; vertices[v].color[2] = colorMultiplier.b; vertices[v].color[3] = colorMultiplier.a; - vertices[v].texCoord[0] = faceTexCoords[v][0]; - vertices[v].texCoord[1] = faceTexCoords[v][1]; + float U = (v == 0 || v == 3) ? U0 : U1; + float V = (v >= 2) ? V0 : V1; + vertices[v].texCoord[0] = gk::qlerp(blockTexCoords.x, blockTexCoords.x + blockTexCoords.sizeX, U); + vertices[v].texCoord[1] = gk::qlerp(blockTexCoords.y, blockTexCoords.y + blockTexCoords.sizeY, V); - if (Config::isSunSmoothLightingEnabled && block->drawType() != BlockDrawType::Liquid) - vertices[v].lightValue[0] = getLightForVertex(Light::Sun, x, y, z, vertexPosPtr, normal, chunk); + if (Config::isSunSmoothLightingEnabled && block.drawType() != BlockDrawType::Liquid) + vertices[v].lightValue[0] = getLightForVertex(Light::Sun, x, y, z, *neighbourOfs[v], normal, chunk); else vertices[v].lightValue[0] = chunk.lightmap().getSunlight(x + normal.x, y + normal.y, z + normal.z); int torchlight = chunk.lightmap().getTorchlight(x, y, z); - if (Config::isTorchSmoothLightingEnabled && torchlight == 0 && block->drawType() != BlockDrawType::Liquid) - vertices[v].lightValue[1] = getLightForVertex(Light::Torch, x, y, z, vertexPosPtr, normal, chunk); + if (Config::isTorchSmoothLightingEnabled && torchlight == 0 && block.drawType() != BlockDrawType::Liquid) + vertices[v].lightValue[1] = getLightForVertex(Light::Torch, x, y, z, *neighbourOfs[v], normal, chunk); else vertices[v].lightValue[1] = chunk.lightmap().getTorchlight(x + normal.x, y + normal.y, z + normal.z); - vertices[v].ambientOcclusion = getAmbientOcclusion(x, y, z, vertexPosPtr, chunk); + vertices[v].ambientOcclusion = getAmbientOcclusion(x, y, z, *neighbourOfs[v], chunk); } auto addVertex = [&](u8 v) { if (!Config::isAmbientOcclusionEnabled) vertices[v].ambientOcclusion = 5; - if (block->drawType() == BlockDrawType::Liquid) + if (block.drawType() == BlockDrawType::Liquid) m_vertices[Layer::Liquid].emplace_back(vertices[v]); - else if (block->drawType() == BlockDrawType::Glass) + else if (block.drawType() == BlockDrawType::Glass) m_vertices[Layer::Glass].emplace_back(vertices[v]); else m_vertices[Layer::Solid].emplace_back(vertices[v]); @@ -283,42 +275,40 @@ inline void ChunkBuilder::addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk } } -inline void ChunkBuilder::addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const Block *block) { - const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(block->tiles().getTextureForFace(0)); - float faceTexCoords[nFaces][nCoordsPerUV] = { +inline void ChunkBuilder::addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const Block &block, const glm::vec3 *const vertexPos[nCrossFaces][nVertsPerFace]) { + const gk::FloatRect &blockTexCoords = m_textureAtlas.getTexCoords(block.tiles().getTextureForFace(0)); + float faceTexCoords[nVertsPerFace][nCoordsPerUV] = { {blockTexCoords.x, blockTexCoords.y + blockTexCoords.sizeY}, {blockTexCoords.x + blockTexCoords.sizeX, blockTexCoords.y + blockTexCoords.sizeY}, {blockTexCoords.x + blockTexCoords.sizeX, blockTexCoords.y}, {blockTexCoords.x, blockTexCoords.y}, }; - static gk::Vector3i normal{0, 0, 0}; - - for (int i = 0 ; i < nCrossFaces ; ++i) { + for (int f = 0; f < nCrossFaces ; ++f) { gk::Vertex vertices[nVertsPerFace]; - for (int j = 0 ; j < nVertsPerFace ; ++j) { - vertices[j].coord3d[0] = x + crossCoords[i][j][0]; - vertices[j].coord3d[1] = y + crossCoords[i][j][1]; - vertices[j].coord3d[2] = z + crossCoords[i][j][2]; - vertices[j].coord3d[3] = 6; + for (int v = 0 ; v < nVertsPerFace ; ++v) { + vertices[v].coord3d[0] = x + vertexPos[f][v]->x; + vertices[v].coord3d[1] = y + vertexPos[f][v]->y; + vertices[v].coord3d[2] = z + vertexPos[f][v]->z; + vertices[v].coord3d[3] = 6; - vertices[j].normal[0] = normal.x; - vertices[j].normal[1] = normal.y; - vertices[j].normal[2] = normal.z; + vertices[v].normal[0] = 0; + vertices[v].normal[1] = 0; + vertices[v].normal[2] = 0; - const gk::Color colorMultiplier = block->colorMultiplier(); - vertices[j].color[0] = colorMultiplier.r; - vertices[j].color[1] = colorMultiplier.g; - vertices[j].color[2] = colorMultiplier.b; - vertices[j].color[3] = colorMultiplier.a; + const gk::Color colorMultiplier = block.colorMultiplier(); + vertices[v].color[0] = colorMultiplier.r; + vertices[v].color[1] = colorMultiplier.g; + vertices[v].color[2] = colorMultiplier.b; + vertices[v].color[3] = colorMultiplier.a; - vertices[j].texCoord[0] = faceTexCoords[j][0]; - vertices[j].texCoord[1] = faceTexCoords[j][1]; + vertices[v].texCoord[0] = faceTexCoords[v][0]; + vertices[v].texCoord[1] = faceTexCoords[v][1]; - vertices[j].lightValue[0] = chunk.lightmap().getSunlight(x, y, z); - vertices[j].lightValue[1] = chunk.lightmap().getTorchlight(x, y, z); + vertices[v].lightValue[0] = chunk.lightmap().getSunlight(x, y, z); + vertices[v].lightValue[1] = chunk.lightmap().getTorchlight(x, y, z); - vertices[j].ambientOcclusion = 5; + vertices[v].ambientOcclusion = 5; } m_vertices[Layer::Flora].emplace_back(vertices[0]); @@ -330,14 +320,8 @@ inline void ChunkBuilder::addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk } } -inline gk::Vector3i ChunkBuilder::getOffsetFromVertex(const tCubeCoord *const vertexPosPtr) const { - return gk::Vector3i{vertexPosPtr[0] * 2 - 1, vertexPosPtr[1] * 2 - 1, vertexPosPtr[2] * 2 - 1}; -} - // Based on this article: https://0fps.net/2013/07/03/ambient-occlusion-for-minecraft-like-worlds/ -inline u8 ChunkBuilder::getAmbientOcclusion(s8f x, s8f y, s8f z, const tCubeCoord *const vertexPosPtr, const ClientChunk &chunk) { - gk::Vector3i offset = getOffsetFromVertex(vertexPosPtr); - +inline u8 ChunkBuilder::getAmbientOcclusion(s8f x, s8f y, s8f z, const gk::Vector3i &offset, const ClientChunk &chunk) { const Block &block0 = Registry::getInstance().getBlock(chunk.getBlock(x + offset.x, y, z + offset.z)); const Block &block1 = Registry::getInstance().getBlock(chunk.getBlock(x, y + offset.y, z + offset.z)); const Block &block2 = Registry::getInstance().getBlock(chunk.getBlock(x + offset.x, y + offset.y, z + offset.z)); @@ -349,7 +333,7 @@ inline u8 ChunkBuilder::getAmbientOcclusion(s8f x, s8f y, s8f z, const tCubeCoor return (side1 && side2) ? 0 : 3 - (side1 + side2 + corner); } -inline u8 ChunkBuilder::getLightForVertex(Light light, s8f x, s8f y, s8f z, const tCubeCoord *const vertexPosPtr, const gk::Vector3i &normal, const ClientChunk &chunk) { +inline u8 ChunkBuilder::getLightForVertex(Light light, s8f x, s8f y, s8f z, const gk::Vector3i &offset, const gk::Vector3i &normal, const ClientChunk &chunk) { std::function getLight = [&](const Chunk *chunk, s8 x, s8 y, s8 z) -> s8 { if (x < 0) return chunk->getSurroundingChunk(0) && chunk->getSurroundingChunk(0)->isInitialized() ? getLight(chunk->getSurroundingChunk(0), x + CHUNK_WIDTH, y, z) : -1; if (x >= CHUNK_WIDTH) return chunk->getSurroundingChunk(1) && chunk->getSurroundingChunk(1)->isInitialized() ? getLight(chunk->getSurroundingChunk(1), x - CHUNK_WIDTH, y, z) : -1; @@ -364,8 +348,6 @@ inline u8 ChunkBuilder::getLightForVertex(Light light, s8f x, s8f y, s8f z, cons return chunk->isInitialized() ? chunk->lightmap().getTorchlight(x, y, z) : -1; }; - gk::Vector3i offset = getOffsetFromVertex(vertexPosPtr); - gk::Vector3i minOffset{ (normal.x != 0) ? offset.x : 0, (normal.y != 0) ? offset.y : 0, @@ -398,66 +380,3 @@ inline u8 ChunkBuilder::getLightForVertex(Light light, s8f x, s8f y, s8f z, cons else return 0; } - -void ChunkBuilder::initializeOrientation() { - // Build all 24 rotated versions of cubeCoords - - // Local rotation of top face - glm::mat3 topRotation = { - 1, 0, 0, - 0, 1, 0, - 0, 0, 1, - }; - // Matrix for a rotation of 90 degrees CCW around the Z axis, used to - // rotate the top face in 90 degree steps around its local Z axis. - const glm::mat3 rotate90Z = { - // Note glm matrices are column-major, so each row here is - // actually a column of the matrix. - 0, 1, 0, - -1, 0, 0, - 0, 0, 1, - }; - // Rotations that place the top face on each of the axes - const glm::mat3 rot2axis[nAxes] = { - { 1, 0, 0, 0, 1, 0, 0, 0, 1}, // top face on +Z, no rotation (identity matrix) - { 1, 0, 0, 0, -1, 0, 0, 0, -1}, // top face on -Z, rotating around the X axis - { 1, 0, 0, 0, 0, -1, 0, 1, 0}, // top face on +Y, rotating around the X axis - { 1, 0, 0, 0, 0, 1, 0, -1, 0}, // top face on -Y, rotating around the X axis - { 0, 0, 1, 0, 1, 0, -1, 0, 0}, // top face on +X, rotating around the Y axis - { 0, 0, -1, 0, 1, 0, 1, 0, 0}, // top face on -X, rotating around the Y axis - }; - - for (s8f axis = 0; axis < nAxes; ++axis) { - for (s8f angle = 0; angle < nRots; ++angle) { - glm::mat3 finalMat = rot2axis[axis] * topRotation; - - for (s8f face = 0; face < nFaces; ++face) { - for (s8f vNum = 0; vNum < nVertsPerFace + nNormals; ++vNum) { - glm::vec3 vertex{cubeCoords[face][vNum][0], cubeCoords[face][vNum][1], cubeCoords[face][vNum][2]}; - if (vNum < nVertsPerFace) - vertex -= glm::vec3{0.5, 0.5, 0.5}; // Translate by centre of the cube - vertex = finalMat * vertex; - tCubeCoord *pVertex = orientCubeCoords[axis * nRots + angle][face][vNum]; - if (vNum < nVertsPerFace) - vertex += glm::vec3{0.5, 0.5, 0.5}; // Translate back - pVertex[0] = tCubeCoord(vertex[0]); - pVertex[1] = tCubeCoord(vertex[1]); - pVertex[2] = tCubeCoord(vertex[2]); - } - } - - // Next top rotation - topRotation = rotate90Z * topRotation; - } - } - - // Since we use bit masking, we add eight null orientations to the normal - // set of 24 to complete a power of two, to make the code robust against - // invalid orientation values. - for (s8f axis = nAxes; axis < nAxesP2; ++axis) { - for (s8f angle = 0; angle < nRots; ++angle) { - memcpy(&orientCubeCoords[axis * nRots + angle], &orientCubeCoords[0], - nFaces * (nVertsPerFace + nNormals) * nCoords * sizeof(tCubeCoord)); - } - } -} diff --git a/client/source/world/ChunkBuilder.hpp b/client/source/world/ChunkBuilder.hpp index b6936c1f..89c1892e 100644 --- a/client/source/world/ChunkBuilder.hpp +++ b/client/source/world/ChunkBuilder.hpp @@ -42,9 +42,7 @@ class TextureAtlas; class ChunkBuilder { public: - ChunkBuilder(TextureAtlas &textureAtlas); - - using tCubeCoord = s8; + ChunkBuilder(TextureAtlas &textureAtlas) : m_textureAtlas(textureAtlas) {} static constexpr u8 layers = 4; @@ -58,19 +56,18 @@ class ChunkBuilder { }; private: - void addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const Block *block); - void addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const Block *block); - - gk::Vector3i getOffsetFromVertex(const tCubeCoord *const vertexPosPtr) const; - u8 getAmbientOcclusion(s8f x, s8f y, s8f z, const tCubeCoord *const vertexPosPtr, const ClientChunk &chunk); + void addFace(s8f x, s8f y, s8f z, s8f f, const ClientChunk &chunk, const Block &block, + const gk::Vector3i &normal, const glm::vec3 *const vertexPos[4], + const gk::Vector3i *const neighbourOfs[4]); + void addCross(s8f x, s8f y, s8f z, const ClientChunk &chunk, const Block &block, const glm::vec3 *const vertexPos[2][4]); enum class Light { Sun, Torch }; - u8 getLightForVertex(Light light, s8f x, s8f y, s8f z, const tCubeCoord *const vertexPosPtr, const gk::Vector3i &normal, const ClientChunk &chunk); - void initializeOrientation(); + u8 getAmbientOcclusion(s8f x, s8f y, s8f z, const gk::Vector3i &offset, const ClientChunk &chunk); + u8 getLightForVertex(Light light, s8f x, s8f y, s8f z, const gk::Vector3i &offset, const gk::Vector3i &normal, const ClientChunk &chunk); std::array, layers> m_vertices; diff --git a/common/source/core/BlockGeometry.cpp b/common/source/core/BlockGeometry.cpp new file mode 100644 index 00000000..1aa6a943 --- /dev/null +++ b/common/source/core/BlockGeometry.cpp @@ -0,0 +1,96 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#include + +#include "BlockGeometry.hpp" + +namespace BlockGeometry { + +glm::mat3 orientMatrices[nOrientations]; + +static void doInitOrientation(void); + +void initOrientation() { + static int orientInitialized = false; + static std::mutex orientInitMutex; + std::lock_guard lock(orientInitMutex); + + if (!orientInitialized) { + doInitOrientation(); + orientInitialized = true; + } +} + +static void doInitOrientation() { + // Build all 24 rotation matrices for the orientation field + + // Local rotation of top face + glm::mat3 topRotation{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + // Matrix for a rotation of 90 degrees CCW around the Z axis, used to + // rotate the top face in 90 degree steps around its local Z axis. + const glm::mat3 rotate90Z{ + // Note glm matrices are column-major, so each row here is + // actually a column of the matrix. + 0, 1, 0, + -1, 0, 0, + 0, 0, 1, + }; + // Rotations that place the top face on each of the axes + const glm::mat3 rot2axis[nAxes]{ + { 1, 0, 0, 0, 1, 0, 0, 0, 1}, // top face on +Z, no rotation (identity matrix) + { 1, 0, 0, 0, -1, 0, 0, 0, -1}, // top face on -Z, rotating around the X axis + { 1, 0, 0, 0, 0, -1, 0, 1, 0}, // top face on +Y, rotating around the X axis + { 1, 0, 0, 0, 0, 1, 0, -1, 0}, // top face on -Y, rotating around the X axis + { 0, 0, 1, 0, 1, 0, -1, 0, 0}, // top face on +X, rotating around the Y axis + { 0, 0, -1, 0, 1, 0, 1, 0, 0}, // top face on -X, rotating around the Y axis + }; + + for (int axis = 0; axis < nAxes; ++axis) { + for (int angle = 0; angle < nRots; ++angle) { + // Rotate the top around itself, then rotate it to each face + orientMatrices[axis * 4 + angle] = rot2axis[axis] * topRotation; + + // Next top rotation + topRotation = rotate90Z * topRotation; + } + } + + // Since we use bit masking, we add eight dummy orientations to the normal + // set of 24 to complete a power of two, to make the code robust against + // invalid orientation values. + for (int axis = nAxes; axis < nAxesP2; ++axis) { + for (int angle = 0; angle < nRots; ++angle) { + orientMatrices[axis * nRots + angle] = orientMatrices[0]; + } + } +} + +} // namespace BlockGeometry diff --git a/common/source/core/BlockGeometry.hpp b/common/source/core/BlockGeometry.hpp new file mode 100644 index 00000000..1b0713ab --- /dev/null +++ b/common/source/core/BlockGeometry.hpp @@ -0,0 +1,81 @@ +/* + * ===================================================================================== + * + * OpenMiner + * + * Copyright (C) 2018-2020 Unarelith, Quentin Bazin + * Copyright (C) 2019-2020 the OpenMiner contributors (see CONTRIBUTORS.md) + * + * This file is part of OpenMiner. + * + * OpenMiner is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * OpenMiner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenMiner; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * ===================================================================================== + */ +#ifndef BLOCKGEOMETRY_HPP_ +#define BLOCKGEOMETRY_HPP_ + +#include +#include + +namespace BlockGeometry { + +constexpr u8 nAxes = 6; +constexpr u8 nAxesP2 = 8; // nAxes rounded up to the next power of 2 +constexpr u8 nRots = 4; +constexpr u8 nFaces = 6; +constexpr u8 nCrossFaces = 2; +constexpr u8 nVertsPerFace = 4; +constexpr u8 nVertsPerCube = 8; +constexpr u8 nCoordsPerUV = 2; +constexpr u8 nOrientations = nAxesP2 * nRots; + +// Same order as enum BlockFace in TilesDef.hpp +constexpr u8f cubeVerts[nFaces][nVertsPerFace]{ + // Vertex numbers are encoded according to their binary digits, + // where bit 0 is X, bit 1 is Y and bit 2 is Z. + // ZYX ZYX ZYX ZYX + {0b010, 0b000, 0b100, 0b110}, // West + {0b001, 0b011, 0b111, 0b101}, // East + {0b000, 0b001, 0b101, 0b100}, // South + {0b011, 0b010, 0b110, 0b111}, // North + {0b010, 0b011, 0b001, 0b000}, // Bottom + {0b100, 0b101, 0b111, 0b110}, // Top +}; + +constexpr u8f crossVerts[nCrossFaces][nVertsPerFace]{ + // ZYX ZYX ZYX ZYX + {0b011, 0b000, 0b100, 0b111}, // NE/SW + {0b001, 0b010, 0b110, 0b101}, // NW/SE +}; + +// Normal of each face +// Same order as enum BlockFace in TilesDef.hpp +const glm::vec3 faceNormals[nFaces]{ + {-1, 0, 0}, // West + { 1, 0, 0}, // East + { 0,-1, 0}, // South + { 0, 1, 0}, // North + { 0, 0,-1}, // Bottom + { 0, 0, 1}, // Top +}; + +extern glm::mat3 orientMatrices[nOrientations]; + +void initOrientation(void); + +} // namespace BlockGeometry + +#endif // BLOCKGEOMETRY_HPP_ diff --git a/common/source/core/EngineConfig.hpp b/common/source/core/EngineConfig.hpp index 092b7f48..ff55b2cc 100644 --- a/common/source/core/EngineConfig.hpp +++ b/common/source/core/EngineConfig.hpp @@ -34,6 +34,9 @@ namespace { constexpr float DIST_NEAR = 0.1f; constexpr float DIST_FAR = 1000.0f; + constexpr float DIST_2D_NEAR = -512.0f; + constexpr float DIST_2D_FAR = 512.0f; + // Chunk size must be a power of two and fit in a signed byte constexpr int CHUNK_WIDTH = 16; constexpr int CHUNK_DEPTH = 16; diff --git a/server/source/core/ServerApplication.cpp b/server/source/core/ServerApplication.cpp index 125ab918..1c3c52b7 100644 --- a/server/source/core/ServerApplication.cpp +++ b/server/source/core/ServerApplication.cpp @@ -26,6 +26,7 @@ */ #include "filesystem.hpp" +#include "BlockGeometry.hpp" #include "ServerApplication.hpp" #include "ServerBlock.hpp" @@ -33,6 +34,7 @@ namespace fs = ghc::filesystem; ServerApplication::ServerApplication(int argc, char **argv) : m_argumentParser(argc, argv) { std::srand(std::time(nullptr)); + BlockGeometry::initOrientation(); } void ServerApplication::init() {