diff --git a/client/include/world/ClientPlayer.hpp b/client/include/world/ClientPlayer.hpp index 613bd910d..7e2855688 100644 --- a/client/include/world/ClientPlayer.hpp +++ b/client/include/world/ClientPlayer.hpp @@ -68,7 +68,7 @@ class ClientPlayer : public Player { void setPosition(float x, float y, float z); - const gk::Camera &camera() { return m_camera; } + gk::Camera &camera() { return m_camera; } private: void testPoint(const ClientWorld &world, float x, float y, float z, glm::vec3 &speed); diff --git a/client/include/world/ClientWorld.hpp b/client/include/world/ClientWorld.hpp index 89861c2b3..0cf8bf2f3 100644 --- a/client/include/world/ClientWorld.hpp +++ b/client/include/world/ClientWorld.hpp @@ -25,6 +25,8 @@ #include +#include + #include "ClientChunk.hpp" #include "Network.hpp" #include "World.hpp" @@ -48,6 +50,7 @@ class ClientWorld : public World, public gk::Drawable { Chunk *getChunk(int cx, int cy, int cz) const override; void setClient(ClientCommandHandler &client) { m_client = &client; } + void setCamera(gk::Camera &camera) { m_camera = &camera; } std::size_t loadedChunkCount() const { return m_chunks.size(); } @@ -61,6 +64,7 @@ class ClientWorld : public World, public gk::Drawable { TextureAtlas &m_textureAtlas; ClientCommandHandler *m_client = nullptr; + gk::Camera *m_camera = nullptr; mutable float m_ud = 1000; mutable s32 m_ux; diff --git a/client/source/hud/BlockCursor.cpp b/client/source/hud/BlockCursor.cpp index 2038e4622..d9e8d9ed3 100644 --- a/client/source/hud/BlockCursor.cpp +++ b/client/source/hud/BlockCursor.cpp @@ -241,7 +241,9 @@ void BlockCursor::draw(gk::RenderTarget &target, gk::RenderStates states) const glCheck(glDisable(GL_POLYGON_OFFSET_FILL)); glCheck(glDisable(GL_CULL_FACE)); - states.transform.translate(m_selectedBlock.x, m_selectedBlock.y, m_selectedBlock.z); + // Subtract the camera position - see comment in ClientWorld::draw() + gk::Vector3d cameraPosition = m_player.camera().getPosition(); + states.transform.translate(m_selectedBlock.x - cameraPosition.x, m_selectedBlock.y - cameraPosition.y, m_selectedBlock.z - cameraPosition.z); target.draw(m_vbo, GL_LINES, 0, 24, states); diff --git a/client/source/states/GameState.cpp b/client/source/states/GameState.cpp index 7e0453d55..8e8643cdc 100644 --- a/client/source/states/GameState.cpp +++ b/client/source/states/GameState.cpp @@ -64,6 +64,7 @@ GameState::GameState(const std::string &host, int port) { m_world.setClient(m_clientCommandHandler); m_player.setClientID(m_client.id()); + m_world.setCamera(m_player.camera()); } void GameState::onEvent(const SDL_Event &event) { diff --git a/client/source/world/ClientWorld.cpp b/client/source/world/ClientWorld.cpp index 0348d39be..f1e5be7d2 100644 --- a/client/source/world/ClientWorld.cpp +++ b/client/source/world/ClientWorld.cpp @@ -21,6 +21,7 @@ * ===================================================================================== */ #include +#include #include #include @@ -62,7 +63,7 @@ void ClientWorld::update() { void ClientWorld::sendChunkRequests() { // If we have a chunk marked for initialization - if (m_ud < 1000) { + if (m_ud < 1000000.0) { ClientChunk *chunk = (ClientChunk *)getChunk(m_ux, m_uy, m_uz); if(chunk && !chunk->hasBeenRequested()) { // Send a chunk request to the server @@ -188,7 +189,7 @@ void ClientWorld::createChunkNeighbours(ClientChunk *chunk) { } void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const { - if (!target.getView()) { + if (!target.getView() || !m_camera) { DEBUG("ERROR: Trying to draw world without a camera"); return; } @@ -197,21 +198,40 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const states.shader->setUniform("u_renderDistance", Config::renderDistance * CHUNK_WIDTH); gk::Shader::bind(nullptr); - m_ud = 1000.0; + m_ud = 1000000.0; m_ux = 0; m_uy = 0; m_uz = 0; + // Changing the values sent to the GPU to double precision is suicidal, + // performance wise, if possible at all. Therefore we want to keep the + // GL rendering numbers in single precision format. But that introduces + // an issue at larger coordinates, because the precision of floats + // quickly degrades as the numbers grow, with a random wobbling being + // very noticeable at e.g. coordinates >= 65536 or so, and the waving + // leaves effect being very jerky in conparison with the effect near the + // origin. + // + // To gain rendering precision, we subtract the camera position from the + // coordinates of the models to be rendered, to make them all small in + // relation to the camera, prior to converting them to floats. Then the + // camera itself is moved to (0, 0, 0) for rendering purposes. Now the + // vertex coordinates passed to the renderer are all small, and single + // precision floats suffice for the drawing. + + gk::Vector3d cameraPos(m_camera->getPosition()); + m_camera->setPosition(0, 0, 0); // Temporarily move the camera to the origin + std::vector> chunks; for(auto &it : m_chunks) { - states.transform = glm::translate(glm::mat4(1.0f), - glm::vec3(it.second->x() * CHUNK_WIDTH, - it.second->y() * CHUNK_DEPTH, - it.second->z() * CHUNK_HEIGHT)); + gk::Transform tf = glm::translate(glm::mat4(1.0f), + glm::vec3(it.second->x() * CHUNK_WIDTH - cameraPos.x, + it.second->y() * CHUNK_DEPTH - cameraPos.y, + it.second->z() * CHUNK_HEIGHT - cameraPos.z)); // Is the chunk close enough? glm::vec4 center = target.getView()->getViewTransform().getMatrix() - * states.transform.getMatrix() + * tf.getMatrix() * glm::vec4(CHUNK_WIDTH / 2, CHUNK_DEPTH / 2, CHUNK_HEIGHT / 2, 1); // Nope, too far, don't render it @@ -223,7 +243,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const it.second->setTooFar(false); // Is this chunk's centre on the screen? - float d = glm::length(center); + float d = glm::length2(center); center.x /= center.w; center.y /= center.w; @@ -231,7 +251,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const // Our screen coordinates are +X right, +Y up, and for a right-handed // coordinate system, depth must be negative Z, so anything with a // positive Z is behind the camera. - if(center.z > CHUNK_HEIGHT / 2) { + if (center.z > CHUNK_MAXSIZE / 2) { continue; } @@ -255,7 +275,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const continue; } - chunks.emplace_back(it.second.get(), states.transform); + chunks.emplace_back(it.second.get(), tf); } for (u8 i = 0 ; i < ChunkBuilder::layers ; ++i) { @@ -264,5 +284,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const it.first->drawLayer(target, states, i); } } + + m_camera->setPosition(cameraPos); // Restore the camera to its original position } diff --git a/common/include/core/EngineConfig.hpp b/common/include/core/EngineConfig.hpp index 450ea0e11..b15a67e0a 100644 --- a/common/include/core/EngineConfig.hpp +++ b/common/include/core/EngineConfig.hpp @@ -34,6 +34,8 @@ namespace { constexpr int CHUNK_DEPTH = 16; constexpr int CHUNK_HEIGHT = 32; + constexpr int CHUNK_MAXSIZE = CHUNK_WIDTH > CHUNK_DEPTH ? (CHUNK_HEIGHT > CHUNK_WIDTH ? CHUNK_HEIGHT : CHUNK_WIDTH) : (CHUNK_HEIGHT > CHUNK_DEPTH ? CHUNK_HEIGHT : CHUNK_DEPTH); + // Several parts of the code use & (CHUNK_xxx - 1) assuming they are powers of 2 static_assert((CHUNK_WIDTH & (CHUNK_WIDTH - 1)) == 0, "CHUNK_WIDTH is not a power of 2"); static_assert((CHUNK_DEPTH & (CHUNK_DEPTH - 1)) == 0, "CHUNK_DEPTH is not a power of 2");