diff --git a/assets/rgt_grass_top.png b/assets/rgt_grass_top.png new file mode 100644 index 0000000..ff39b7d Binary files /dev/null and b/assets/rgt_grass_top.png differ diff --git a/assets/rgt_oak_log_top.png b/assets/rgt_oak_log_top.png new file mode 100644 index 0000000..d1f0457 Binary files /dev/null and b/assets/rgt_oak_log_top.png differ diff --git a/assets/rgt_stone.png b/assets/rgt_stone.png new file mode 100644 index 0000000..ca8c5e9 Binary files /dev/null and b/assets/rgt_stone.png differ diff --git a/assets/rgt_stone_brick.png b/assets/rgt_stone_brick.png new file mode 100644 index 0000000..20bff15 Binary files /dev/null and b/assets/rgt_stone_brick.png differ diff --git a/assets/test_stone.png b/assets/test_stone.png deleted file mode 100644 index fccc21f..0000000 Binary files a/assets/test_stone.png and /dev/null differ diff --git a/src/client/ChunkRenderer.cpp b/src/client/ChunkRenderer.cpp index bd3afbf..7074da9 100644 --- a/src/client/ChunkRenderer.cpp +++ b/src/client/ChunkRenderer.cpp @@ -4,13 +4,13 @@ namespace Artifact { -void ChunkRenderer::render(glm::ivec3 pos, World::Chunk* chunk, WGPUCommandEncoder encoder, WGPUTextureView texture) { +void ChunkRenderer::render(WGPUCommandEncoder encoder, WGPUTextureView texture) { WGPURenderPassColorAttachment colorAttachment = {}; colorAttachment.view = texture; colorAttachment.loadOp = WGPULoadOp_Clear; colorAttachment.storeOp = WGPUStoreOp_Store; - colorAttachment.clearValue = {0, 0, 0, 1}; - colorAttachment.depthSlice = -1; + colorAttachment.clearValue = {0.53, 0.81, 0.92, 1}; + colorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; WGPURenderPassDepthStencilAttachment depthAttachment = {}; depthAttachment.view = engine->depthTexture->view; @@ -20,6 +20,7 @@ void ChunkRenderer::render(glm::ivec3 pos, World::Chunk* chunk, WGPUCommandEncod depthAttachment.depthReadOnly = false; depthAttachment.stencilLoadOp = WGPULoadOp_Undefined; depthAttachment.stencilStoreOp = WGPUStoreOp_Undefined; + depthAttachment.stencilReadOnly = true; WGPURenderPassDescriptor renderPassDesc = {}; renderPassDesc.colorAttachmentCount = 1; @@ -39,4 +40,131 @@ void ChunkRenderer::render(glm::ivec3 pos, World::Chunk* chunk, WGPUCommandEncod wgpuRenderPassEncoderEnd(cubeRenderPass); } +void ChunkRenderer::addChunk(glm::ivec3 pos, World::Chunk* chunk) { + generateChunkMesh(pos, chunk); +} + +void ChunkRenderer::removeChunk(glm::ivec3 pos) { + auto buffers = chunkBuffers.at(pos); + wgpuBufferDestroy(buffers.vertices); + wgpuBufferDestroy(buffers.indices); + chunkBuffers.erase(pos); +} + +void ChunkRenderer::generateChunkMesh(glm::ivec3 pos, World::Chunk* chunk) { + int vertexCount = 0; + int indexCount = 0; + + constexpr int SX = World::Chunk::SIZE; + constexpr int SY = World::Chunk::SIZE; + constexpr int SZ = World::Chunk::SIZE; + + // Temporary CPU-side vectors – we will write them only to compute size, + // then copy once at the end. + std::vector verts; + std::vector inds; + + verts.reserve(24 * 1024); // rough upper bound + inds.reserve (36 * 1024); + + // Helper to emit one quad (counter-clockwise when looking at the face) + auto emitQuad = [&verts, &inds](float x0, float y0, float z0, + float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float u0, float v0, float u1, float v1) { + uint32_t base = static_cast(verts.size()); + verts.push_back({{x0,y0,z0}, {u0,v0}}); + verts.push_back({{x1,y1,z1}, {u0,v1}}); + verts.push_back({{x2,y2,z2}, {u1,v1}}); + verts.push_back({{x3,y3,z3}, {u1,v0}}); + + inds.push_back(base + 0); inds.push_back(base + 1); inds.push_back(base + 2); + inds.push_back(base + 0); inds.push_back(base + 2); inds.push_back(base + 3); + }; + + for (int x = 0; x < SX; ++x) { + for (int y = 0; y < SY; ++y) { + for (int z = 0; z < SZ; ++z) { + uint16_t id = chunk->getNodeRaw(x, y, z); + + float fx0 = static_cast(x) + pos.x * 16; + float fy0 = static_cast(y) + pos.y * 16; + float fz0 = static_cast(z) + pos.z * 16; + float fx1 = fx0 + 1.0f; + float fy1 = fy0 + 1.0f; + float fz1 = fz0 + 1.0f; + + auto def = engine->world->nodeRegistry.getDefinition(id); + World::NodeTextureCoords uv; + if(def.visual == World::NodeVisual::NONE) { + continue; + } else { + uv = engine->world->nodeRegistry.getTextureCoords(id); + } + + // +X + if (chunk->getNodeRaw(x+1, y, z) == 0) { + emitQuad(fx1,fy0,fz0, fx1,fy1,fz0, fx1,fy1,fz1, fx1,fy0,fz1, uv.uvMin.x, uv.uvMin.y, uv.uvMax.x, uv.uvMax.y); + } + // -X + if (chunk->getNodeRaw(x-1, y, z) == 0) { + emitQuad(fx0,fy0,fz1, fx0,fy1,fz1, fx0,fy1,fz0, fx0,fy0,fz0, uv.uvMin.x, uv.uvMin.y, uv.uvMax.x, uv.uvMax.y); + } + // +Y (top) + if (chunk->getNodeRaw(x, y+1, z) == 0) { + emitQuad(fx0,fy1,fz0, fx1,fy1,fz0, fx1,fy1,fz1, fx0,fy1,fz1, uv.uvMin.x, uv.uvMin.y, uv.uvMax.x, uv.uvMax.y); + } + // -Y (bottom) + if (chunk->getNodeRaw(x, y-1, z) == 0) { + emitQuad(fx0,fy0,fz1, fx1,fy0,fz1, fx1,fy0,fz0, fx0,fy0,fz0, uv.uvMin.x, uv.uvMin.y, uv.uvMax.x, uv.uvMax.y); + } + // +Z + if (chunk->getNodeRaw(x, y, z+1) == 0) { + emitQuad(fx0,fy0,fz1, fx0,fy1,fz1, fx1,fy1,fz1, fx1,fy0,fz1, uv.uvMin.x, uv.uvMin.y, uv.uvMax.x, uv.uvMax.y); + } + // -Z + if (chunk->getNodeRaw(x, y, z-1) == 0) { + emitQuad(fx1,fy0,fz0, fx1,fy1,fz0, fx0,fy1,fz0, fx0,fy0,fz0, uv.uvMin.x, uv.uvMin.y, uv.uvMax.x, uv.uvMax.y); + } + } + } + } + + if(chunkBuffers.count(pos)) { + removeChunk(pos); + } + + auto isEmpty = inds.empty(); + if (isEmpty) { + return; + } + + vertexCount = verts.size(); + indexCount = inds.size(); + + std::cout << "Vertices: " << vertexCount << "; Indices: " << indexCount << std::endl; + + WGPUBufferDescriptor vertexBufferDesc = {}; + vertexBufferDesc.size = vertexCount * sizeof(Vertex); + vertexBufferDesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; + vertexBufferDesc.label = WGPUStringView{"Chunk vertex buffer", WGPU_STRLEN}; + auto vertices = wgpuDeviceCreateBuffer(engine->device, &vertexBufferDesc); + wgpuQueueWriteBuffer(engine->queue, vertices, 0, verts.data(), vertexBufferDesc.size); + + WGPUBufferDescriptor indexBufferDesc = {}; + indexBufferDesc.size = indexCount * sizeof(uint32_t); + indexBufferDesc.usage = WGPUBufferUsage_Index | WGPUBufferUsage_CopyDst; + indexBufferDesc.label = WGPUStringView{"Chunk index buffer", WGPU_STRLEN}; + auto indices = wgpuDeviceCreateBuffer(engine->device, &indexBufferDesc); + wgpuQueueWriteBuffer(engine->queue, indices, 0, inds.data(), indexBufferDesc.size); + + chunkBuffers.emplace(pos, ChunkRenderData { + .vertices = vertices, + .indices = indices, + .vertexCount = vertexCount, + .indexCount = indexCount + }); +} + } diff --git a/src/client/ChunkRenderer.h b/src/client/ChunkRenderer.h index a432b78..fe025d1 100644 --- a/src/client/ChunkRenderer.h +++ b/src/client/ChunkRenderer.h @@ -5,25 +5,13 @@ #include namespace Artifact { -namespace World { - -class ClientChunk: public Chunk { - WGPUBuffer vertices; - WGPUBuffer indices; - - void generateMesh() { - - } -}; - -} class Engine; class ChunkRenderer { struct ChunkRenderData { - WGPUBuffer vertices; - WGPUBuffer indices; + WGPUBuffer vertices = nullptr; + WGPUBuffer indices = nullptr; int vertexCount; int indexCount; }; @@ -31,7 +19,10 @@ class ChunkRenderer { std::unordered_map chunkBuffers; public: ChunkRenderer(Engine* engine) : engine(engine) {} - void render(glm::ivec3 pos, World::Chunk* chunk, WGPUCommandEncoder encoder, WGPUTextureView texture); + void render(WGPUCommandEncoder encoder, WGPUTextureView texture); + void addChunk(glm::ivec3 pos, World::Chunk* chunk); + void removeChunk(glm::ivec3 pos); + void generateChunkMesh(glm::ivec3 pos, World::Chunk* chunk); }; } diff --git a/src/core/Window.cpp b/src/client/Window.cpp similarity index 71% rename from src/core/Window.cpp rename to src/client/Window.cpp index d154332..779701e 100644 --- a/src/core/Window.cpp +++ b/src/client/Window.cpp @@ -12,7 +12,7 @@ Window::Window() { glfwSetWindowUserPointer(window, this); glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int scancode, int action, int mods){ - auto me = reinterpret_cast(glfwGetWindowUserPointer(window)); + auto me = static_cast(glfwGetWindowUserPointer(window)); me->dispatch(Events::Key{ .key = key, .scancode = scancode, @@ -22,7 +22,7 @@ Window::Window() { }); glfwSetCursorPosCallback(window, [](GLFWwindow* window, double x, double y) { - auto me = reinterpret_cast(glfwGetWindowUserPointer(window)); + auto me = static_cast(glfwGetWindowUserPointer(window)); me->dispatch(Events::CursorPos{ .x = x, .y = y @@ -30,7 +30,7 @@ Window::Window() { }); glfwSetMouseButtonCallback(window, [](GLFWwindow* window, int button, int action, int mods) { - auto me = reinterpret_cast(glfwGetWindowUserPointer(window)); + auto me = static_cast(glfwGetWindowUserPointer(window)); Events::ActionType type; switch(button) { case GLFW_MOUSE_BUTTON_LEFT: @@ -55,6 +55,14 @@ Window::Window() { .y = y }); }); + + glfwSetScrollCallback(window, [](GLFWwindow* window, double dx, double dy) { + auto me = static_cast(glfwGetWindowUserPointer(window)); + me->dispatch(Events::Scroll{ + .dx = dx, + .dy = dy + }); + }); } Window::~Window() { @@ -73,4 +81,14 @@ void Window::close() const { glfwSetWindowShouldClose(window, true); } +void Window::setPointerLock() { + pointerLocked = true; + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +} + +void Window::releasePointerLock() { + pointerLocked = false; + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +} + } diff --git a/src/core/Window.h b/src/client/Window.h similarity index 78% rename from src/core/Window.h rename to src/client/Window.h index b312576..8f1d891 100644 --- a/src/core/Window.h +++ b/src/client/Window.h @@ -34,20 +34,30 @@ struct Action { double y; }; +struct Scroll { + double dx; + double dy; +}; + } -class Window: public EventTarget { +class Window: public EventTarget {public: GLFWwindow* window; -public: + int width = 1200; int height = 800; + bool pointerLocked = false; + Window(); ~Window(); + WGPUSurface createWGPUSurface(WGPUInstance &instance); bool shouldClose(); void close() const; // TODO: Allow changing the window cursor. void setCursor(); + void setPointerLock(); + void releasePointerLock(); }; } diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index bb7f4d5..5117235 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -50,7 +51,7 @@ Engine::~Engine() { /*/ - +// Hack to ensure GLFW is initialized before the user does anything that might break it. static struct _glfwInit { _glfwInit() { if(!glfwInit()) printf("Failed to initialize GLFW."); @@ -134,7 +135,7 @@ void Engine::init() { depthDesc.size.width = viewportWidth; depthDesc.size.height = viewportHeight; depthDesc.size.depthOrArrayLayers = 1; - depthDesc.format = WGPUTextureFormat_Depth24Plus; + depthDesc.format = WGPUTextureFormat_Depth32Float; depthDesc.usage = WGPUTextureUsage_RenderAttachment; depthDesc.mipLevelCount = 1; depthDesc.sampleCount = 1; @@ -255,7 +256,15 @@ void Engine::init() { }); window.listen([this](auto ev) { + static double lastX = viewportWidth / 2.0; + static double lastY = viewportHeight / 2.0; nk_input_motion(ui.get(), ev.x, ev.y); + + server->players[0]->yaw += (ev.x - lastX) * 0.2f; + server->players[0]->pitch -= (ev.y - lastY) * 0.2f; + server->players[0]->pitch = std::max(-89.0f, std::min(89.0f, (float)server->players[0]->pitch)); + lastX = ev.x; + lastY = ev.y; }); window.listen([this](auto ev) { @@ -274,6 +283,11 @@ void Engine::init() { return; } nk_input_button(ui.get(), type, (int)ev.x, (int)ev.y, ev.state); + window.setPointerLock(); + }); + + window.listen([this](auto ev) { + nk_input_scroll(ui.get(), nk_vec2(ev.dx, ev.dy)); }); int atlasWidth, atlasHeight; @@ -447,7 +461,7 @@ void Engine::makeWorldPipelines() { cubeFragmentState.targets = &cubeColorTarget; WGPUDepthStencilState depthStencilState = {}; - depthStencilState.format = WGPUTextureFormat_Depth24Plus; + depthStencilState.format = WGPUTextureFormat_Depth32Float; depthStencilState.depthWriteEnabled = WGPUOptionalBool_True; depthStencilState.depthCompare = WGPUCompareFunction_Less; @@ -629,20 +643,22 @@ void Engine::makeUIPipeline() { void Engine::run() { init(); - time_t last_time; - time(&last_time); + sendMessage(Events::PlayerJoin { + .name = "singleplayer", + .pos = {0, 0, 0} + }); + auto lastTime = std::chrono::steady_clock::now().time_since_epoch().count(); while(!window.shouldClose()) { nk_input_begin(ui.get()); glfwPollEvents(); nk_input_end(ui.get()); render(); - time_t now; - time(&now); - if(now - last_time > STEP_SIZE) { + auto now = std::chrono::steady_clock::now().time_since_epoch().count(); + if(now - lastTime > STEP_SIZE) { tick(); - dispatch(Events::Tick{now - last_time}); - last_time = now; + dispatch(Events::Tick{now - lastTime}); + lastTime = now; } } } @@ -691,13 +707,11 @@ void Engine::render() { WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr); - for(const auto &pair : world->chunks) { - chunkRenderer->render(pair.first, pair.second.get(), encoder, nextTexture); - } + chunkRenderer->render(encoder, nextTexture); WGPURenderPassColorAttachment colorAttachment = {}; colorAttachment.view = nextTexture; - colorAttachment.loadOp = WGPULoadOp_Clear; + colorAttachment.loadOp = WGPULoadOp_Load; colorAttachment.storeOp = WGPUStoreOp_Store; colorAttachment.clearValue = {0, 0.2, 0.4, 1}; colorAttachment.depthSlice = -1; @@ -799,6 +813,45 @@ void Engine::render() { void Engine::tick() { if(server) server->tick(); + glm::vec3 direction; + direction.x = cos(glm::radians(server->players[0]->yaw)) * cos(glm::radians(server->players[0]->pitch)); + direction.y = sin(glm::radians(server->players[0]->pitch)); + direction.z = sin(glm::radians(server->players[0]->yaw)) * cos(glm::radians(server->players[0]->pitch)); + direction = glm::normalize(direction); + + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 right = glm::normalize(glm::cross(direction, up)); + glm::vec3 forward = glm::normalize(glm::vec3(direction.x, 0.0f, direction.z)); + + float speed = 0.1f; + glm::vec3 moveDir(0.0f); + + if (glfwGetKey(window.window, GLFW_KEY_W) == GLFW_PRESS) + moveDir += forward * speed; + if (glfwGetKey(window.window, GLFW_KEY_S) == GLFW_PRESS) + moveDir -= forward * speed; + if (glfwGetKey(window.window, GLFW_KEY_A) == GLFW_PRESS) + moveDir -= right * speed; + if (glfwGetKey(window.window, GLFW_KEY_D) == GLFW_PRESS) + moveDir += right * speed; + if (glfwGetKey(window.window, GLFW_KEY_SPACE) == GLFW_PRESS) + moveDir += up * speed; + if (glfwGetKey(window.window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) + moveDir -= up * speed; + + glm::vec3 newPos = server->players[0]->pos + moveDir; + server->players[0]->pos = newPos; + glm::vec3 cameraPos = newPos; + auto view = glm::lookAt(cameraPos, cameraPos + direction, glm::vec3(0, 1, 0)); + + glm::mat4 model = glm::mat4(1.0f); + +// model = glm::translate(model, glm::vec3(nkx, nky, nkz)); +// app->view = glm::rotate(app->view, (float)property / 10, newPos + glm::vec3(0, 1, 0)); +// model = glm::rotate(model, (float)rot2 / 10, glm::vec3(1, 0, 0)); +// model = glm::scale(model, glm::vec3(scale)); + glm::mat4 mvp = glm::perspective(glm::radians(45.0f), (float)viewportWidth / viewportHeight, 0.1f, 100.0f) * view * model; + wgpuQueueWriteBuffer(queue, worldUniformBuffer, 0, &mvp, sizeof(mvp)); } } diff --git a/src/core/Engine.h b/src/core/Engine.h index b4b2bbf..f69770c 100644 --- a/src/core/Engine.h +++ b/src/core/Engine.h @@ -119,7 +119,7 @@ public: std::string assetPath = std::string(getenv("HOME")) + "/eclipse-workspace/ArtifactEngine/assets"; glm::mat4 ortho; - Server* server; + Server* server = nullptr; Engine(); ~Engine(); @@ -150,8 +150,8 @@ public: std::cout << "Message received" << std::endl; } else if constexpr (std::is_same_v) { auto ev = (Events::ChunkChanged) msg; - world->chunks.at(ev.pos)->data = ev.data; -// chunkRenderer->updateChunk(ev.pos, ev.data); + world->chunks.emplace(ev.pos, std::make_unique(ev.data)); + chunkRenderer->addChunk(ev.pos, world->chunks.at(ev.pos).get()); } } }; @@ -166,7 +166,7 @@ void Artifact::Engine::sendMessage(T msg){ if(server) { server->receiveMessage(msg); } else { - auto buffer = cista::serialize(msg); + //auto buffer = cista::serialize(msg); } } diff --git a/src/core/Events.h b/src/core/Events.h index 9730276..b85e039 100644 --- a/src/core/Events.h +++ b/src/core/Events.h @@ -7,15 +7,28 @@ #include #include +#include + namespace Artifact { namespace Events { - // Fired when the engine finishes initializing. - struct Initialized {}; - // Fired every time the engine updates game logic. - struct Tick { - time_t dtime; - }; +// Common events. + +// Fired when the engine finishes initializing. +struct Initialized {}; +// Fired every time the engine updates game logic. +struct Tick { + time_t dtime; +}; + +// Server-only events. + +// Fired when a player joins the server. +struct PlayerJoin { + std::string name; + glm::vec3 pos; +}; + } class EventTarget { diff --git a/src/main.cpp b/src/main.cpp index 873516c..d496af7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,8 +42,13 @@ int main() { }); Artifact::World::NodeDef def_Stone; - def_Stone.texture = "test.png"; + def_Stone.visual = Artifact::World::NodeVisual::CUBE; + def_Stone.texture = "rgt_stone_brick.png"; engine.world->nodeRegistry.registerNode("test", def_Stone); + def_Stone.texture = "rgt_stone.png"; + engine.world->nodeRegistry.registerNode("test2", def_Stone); + def_Stone.texture = "rgt_grass_top.png"; + engine.world->nodeRegistry.registerNode("test3", def_Stone); engine.run(); diff --git a/src/server/Server.cpp b/src/server/Server.cpp index 2414a20..0bb1a4d 100644 --- a/src/server/Server.cpp +++ b/src/server/Server.cpp @@ -7,7 +7,7 @@ namespace Artifact { Server::Server(Engine* localClient) : localClient(localClient) { - + players.push_back(std::make_unique()); } void Server::run() { @@ -60,7 +60,7 @@ void Server::start() { } void Server::tick() { - + world->tick(); } } diff --git a/src/server/Server.h b/src/server/Server.h index cad7476..b2bb675 100644 --- a/src/server/Server.h +++ b/src/server/Server.h @@ -5,6 +5,8 @@ #include #include +#include +#include #define STEP_SIZE 200 @@ -15,6 +17,9 @@ class Engine; class Server: public EventTarget { Artifact::Engine* localClient; public: + std::vector> players; + std::unique_ptr world = std::make_unique(this); + Server(Engine* localClient); void run(); @@ -27,6 +32,21 @@ public: void receiveMessage(T msg) { if constexpr (std::is_same_v) { std::cout << "Message received" << std::endl; + } else if constexpr (std::is_same_v) { + std::cout << "<" << msg.name << "> joined." << std::endl; + for(int x = -5, y = -5, z = -5; x <= 5 && y <= 5 && z <= 5;) { + auto chunk = world->loadChunk(msg.pos + glm::vec3{x, y, z}); + sendMessage(Events::ChunkChanged{.pos = msg.pos + glm::vec3{x, y, z}, .data = chunk->data}); + ++x; + if(x > 5) { + x = -5; + ++y; + if(y > 5) { + y = -5; + ++z; + } + } + } } } }; @@ -40,6 +60,6 @@ void Artifact::Server::sendMessage(T msg) { if(localClient) { localClient->receiveMessage(msg); } else { - auto buffer = cista::serialize(msg); + //auto buffer = cista::serialize(msg); } } diff --git a/src/server/ServerPlayer.cpp b/src/server/ServerPlayer.cpp new file mode 100644 index 0000000..cae0725 --- /dev/null +++ b/src/server/ServerPlayer.cpp @@ -0,0 +1,9 @@ +#include + +namespace Artifact { + +ServerPlayer::ServerPlayer() { + +} + +} diff --git a/src/server/ServerPlayer.h b/src/server/ServerPlayer.h new file mode 100644 index 0000000..7091f55 --- /dev/null +++ b/src/server/ServerPlayer.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Artifact { + +class ServerPlayer { +public: + ServerPlayer(); + + glm::ivec3 lastChunkPos; + glm::vec3 pos; + double pitch; + double yaw; + int viewRange = 4; +}; + +} diff --git a/src/server/ServerWorld.cpp b/src/server/ServerWorld.cpp index 78ffb6c..4d5ffc0 100644 --- a/src/server/ServerWorld.cpp +++ b/src/server/ServerWorld.cpp @@ -1,4 +1,6 @@ +#include +#include #include namespace Artifact { @@ -8,36 +10,105 @@ ServerWorld::ServerWorld(Server* server) : server(server) { } -void ServerWorld::tick() { - +glm::ivec3 getChunkPos(glm::vec3 p) { + return {static_cast(std::floor(p.x / 16.0)), static_cast(std::floor(p.y / 16.0)), static_cast(std::floor(p.z / 16.0))}; } -void ServerWorld::updateLoadedChunks() { +std::unordered_set generateViewChunks(const glm::ivec3& center, int range) { + std::unordered_set view; + for (int dx = -range; dx <= range; ++dx) + for (int dy = -range; dy <= range; ++dy) + for (int dz = -range; dz <= range; ++dz) { + if (std::max(std::abs(dx), std::abs(dz)) <= range) { + view.emplace(glm::ivec3{center.x + dx, center.y +dy, center.z + dz}); + } + } + return view; +} +void ServerWorld::tick() { + std::vector toLoad; + std::vector toUnload; + + // First pass: Load or generate chunks and update meshes + for(const auto &player : server->players) { + auto currentPos = getChunkPos(player->pos); + if(currentPos == player->lastChunkPos) continue; + + auto oldView = generateViewChunks(player->lastChunkPos, player->viewRange); + auto newView = generateViewChunks(currentPos, player->viewRange); + + // Compute removed chunks (in old but not new). + std::unordered_set removed; + for (const auto& c : oldView) { + if (newView.find(c) == newView.end()) { + removed.insert(c); + } + } + + // Compute added chunks (in new but not old). + std::unordered_set added; + for (const auto& c : newView) { + if (oldView.find(c) == oldView.end()) { + added.insert(c); + } + } + + // Process removed first (order doesn't matter, but unload before load for safety). + for (const auto& c : removed) { + auto it = refcounts.find(c); + if (it != refcounts.end() && --(it->second) == 0) { + toUnload.push_back(c); + refcounts.erase(it); + chunks.erase(c); + } + } + + // Process added. + for (const auto& c : added) { + int& ref = refcounts[c]; // Default constructs to 0 if missing. + if (ref == 0) { + toLoad.push_back(c); + loadChunk(c); + server->sendMessage(Events::ChunkChanged{.pos = c, .data = chunks.at(c)->data}); + } + ++ref; + } + + player->lastChunkPos = currentPos; + } } Chunk* ServerWorld::loadChunk(glm::ivec3 pos) { - auto chunk = chunks.at(pos).get(); - if(chunk) { - return chunk; + if(chunks.count(pos)) { + return chunks.at(pos).get(); } // TODO: Load from disk std::array data; - for(int x = 0; x < CHUNK_SIZE; ++x) - for(int z = 0; z < CHUNK_SIZE; ++z) { - int height = 8 + ((pos.x * 7 + pos.z * 13 + x * 3 + z * 5) % 8); // Heights 8 to 15 - if (height < 0) height = 0; - if (height >= Chunk::SIZE) height = Chunk::SIZE - 1; - for (int y = 0; y <= 7; ++y) { - data[x + y * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = std::rand() % 3 + 1; // Stone - } - if (x > 3 && x < Chunk::SIZE - 3 && z > 3 && z < Chunk::SIZE - 3) { - data[x + 8 * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = 2; // Grass on top + data.fill(0); + if (pos.y > 0) { + data[1] = 2; // Stone + data[2] = 3; // Stone + data[3] = 4; // Stone + } else { +// chunk.voxels.assign(Chunk::SIZE * Chunk::SIZE * Chunk::SIZE, 0); // Reset voxels + for (int x = 0; x < Chunk::SIZE; ++x) { + for (int z = 0; z < Chunk::SIZE; ++z) { + int height = 8 + ((pos.x * 7 + pos.z * 13 + x * 3 + z * 5) % 8); // Heights 8 to 15 + if (height < 0) height = 0; + if (height >= Chunk::SIZE) height = Chunk::SIZE - 1; + for (int y = 0; y <= 7; ++y) { + data[x + y * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = std::rand() % 3 + 2; // Stone + } + if (x > 3 && x < Chunk::SIZE - 3 && z > 3 && z < Chunk::SIZE - 3) { + data[x + 8 * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = 4; // Grass on top + } + } } } - chunks.at(pos) = std::make_unique(data); + chunks.emplace(pos, std::make_unique(data)); return chunks.at(pos).get(); } diff --git a/src/server/ServerWorld.h b/src/server/ServerWorld.h index 501418c..cc63e20 100644 --- a/src/server/ServerWorld.h +++ b/src/server/ServerWorld.h @@ -1,18 +1,20 @@ #pragma once #include -#include namespace Artifact { + +class Server; + namespace World { class ServerWorld: public World { Server* server = nullptr; + std::unordered_map refcounts; public: ServerWorld(Server* server); void tick(); - void updateLoadedChunks(); Chunk* loadChunk(glm::ivec3 pos); }; diff --git a/src/world/Chunk.cpp b/src/world/Chunk.cpp index d94f5fb..2899df5 100644 --- a/src/world/Chunk.cpp +++ b/src/world/Chunk.cpp @@ -16,6 +16,9 @@ Chunk::~Chunk() { } uint16_t Chunk::getNodeRaw(float x, float y, float z) { + if(x < 0 || y < 0 || z < 0 || x >= Chunk::SIZE || y >= Chunk::SIZE || z >= Chunk::SIZE) { + return 0; + } return data.at(posToIndex(x, y, z)); } diff --git a/src/world/World.cpp b/src/world/World.cpp index eca8888..40b084c 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -12,7 +12,10 @@ int posToIndex(float x, float y, float z) { } NodeRegistry::NodeRegistry() { - registerNode("air", NodeDef{}); + registerNode("air", NodeDef{.visual = NodeVisual::NONE}); + registerNode("unknown", NodeDef{ + .texture = "rgt_oak_log_top.png" + }); } bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std::vector& atlasData) { @@ -27,8 +30,10 @@ bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std:: std::vector> textureData; std::vector widths, heights; int maxWidth = 0, maxHeight = 0; - for (size_t i = 1; i < registeredNodes.size(); ++i) { // Skip ID 0 (air) - if(registeredNodes.at(i).visual == NodeVisual::NONE) continue; + for (size_t i = 0; i < registeredNodes.size(); ++i) { // Skip ID 0 (air) + if(registeredNodes.at(i).visual == NodeVisual::NONE) { + continue; + } drawables.push_back(i); int width, height, channels; unsigned char* data = stbi_load(registeredNodes.at(i).texture.c_str(), &width, &height, &channels, 4); @@ -37,8 +42,8 @@ bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std:: return false; } textureData.push_back({}); - textureData.at(i).resize(width * height * 4); - std::memcpy(textureData[i].data(), data, width * height * 4); + textureData.at(drawables.size() - 1).resize(width * height * 4); + std::memcpy(textureData[drawables.size() - 1].data(), data, width * height * 4); widths.push_back(width); heights.push_back(height); maxWidth = std::max(maxWidth, width); @@ -48,12 +53,12 @@ bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std:: // Simple atlas layout: stack textures horizontally // (You can improve this with a more efficient packing algorithm if needed) - atlasWidth = maxWidth * (drawables.size()) + 1; // -1 because ID 0 (air) has no texture - atlasHeight = maxHeight + 1; + atlasWidth = maxWidth * (drawables.size()); // -1 because ID 0 (air) has no texture + atlasHeight = maxHeight; atlasData.resize(atlasWidth * atlasHeight * 4, 0); // RGBA - for (size_t i = 1; i < drawables.size(); ++i) { - int xOffset = (i - 1) * maxWidth; + for (uint16_t i = 0; i < drawables.size(); ++i) { + int xOffset = i * maxWidth; for (int y = 0; y < heights[i]; ++y) { for (int x = 0; x < widths[i]; ++x) { int srcIdx = (y * widths[i] + x) * 4; @@ -68,7 +73,7 @@ bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std:: float uMax = static_cast(xOffset + widths[i]) / atlasWidth; float vMin = 0.0f; float vMax = static_cast(heights[i]) / atlasHeight; - textureCoords.at(drawables.at(i)) = {{uMin, vMin}, {uMax, vMax}}; + textureCoords.emplace(drawables.at(i), NodeTextureCoords{{uMin, vMin}, {uMax, vMax}}); } return true; diff --git a/src/world/World.h b/src/world/World.h index 07f2075..c8d3a18 100644 --- a/src/world/World.h +++ b/src/world/World.h @@ -28,7 +28,7 @@ struct NodeDef { std::string name; std::string texture; AlphaBlendMode alphaBlendMode = AlphaBlendMode::NONE; - NodeVisual visual = NodeVisual::NONE; + NodeVisual visual = NodeVisual::CUBE; }; struct NodeTextureCoords { @@ -41,7 +41,7 @@ class NodeRegistry { uint16_t next = 0; public: std::unordered_map registeredNodes; - std::vector textureCoords; + std::unordered_map textureCoords; NodeRegistry(); @@ -51,6 +51,16 @@ public: registeredNodes.emplace(next++, def); } + const NodeDef& getDefinition(uint16_t id) { + if(!registeredNodes.count(id)) return registeredNodes.at(1); + return registeredNodes.at(id); + } + + const NodeTextureCoords& getTextureCoords(uint16_t id) { + if(!textureCoords.count(id)) return textureCoords.at(1); + return textureCoords.at(id); + } + bool generateTextureAtlas(int& atlasWidth, int& atlasHeight, std::vector& atlasData); };