Make world rendering work.
This commit is contained in:
parent
d99b70eaa1
commit
48d055e889
22 changed files with 438 additions and 82 deletions
BIN
assets/rgt_grass_top.png
Normal file
BIN
assets/rgt_grass_top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 354 B |
BIN
assets/rgt_oak_log_top.png
Normal file
BIN
assets/rgt_oak_log_top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 440 B |
BIN
assets/rgt_stone.png
Normal file
BIN
assets/rgt_stone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 B |
BIN
assets/rgt_stone_brick.png
Normal file
BIN
assets/rgt_stone_brick.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 341 B |
Binary file not shown.
|
Before Width: | Height: | Size: 221 B |
|
|
@ -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<Vertex> verts;
|
||||
std::vector<uint32_t> 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<uint32_t>(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<float>(x) + pos.x * 16;
|
||||
float fy0 = static_cast<float>(y) + pos.y * 16;
|
||||
float fz0 = static_cast<float>(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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,25 +5,13 @@
|
|||
#include <Mesh.h>
|
||||
|
||||
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<glm::vec3, ChunkRenderData, Util::Vec3Hash, Util::Vec3Equal> 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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Window*>(glfwGetWindowUserPointer(window));
|
||||
auto me = static_cast<Window*>(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<Window*>(glfwGetWindowUserPointer(window));
|
||||
auto me = static_cast<Window*>(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<Window*>(glfwGetWindowUserPointer(window));
|
||||
auto me = static_cast<Window*>(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<Window*>(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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#include <glfw3webgpu.h>
|
||||
#include <webgpu/webgpu.h>
|
||||
|
|
@ -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<Events::CursorPos>([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<Events::Action>([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<Events::Scroll>([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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T, Events::ChunkChanged>) {
|
||||
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<World::Chunk>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,28 @@
|
|||
#include <any>
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm.hpp>
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
namespace Events {
|
||||
// Fired when the engine finishes initializing.
|
||||
struct Initialized {};
|
||||
// Fired every time the engine updates game logic.
|
||||
struct Tick {
|
||||
// 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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
namespace Artifact {
|
||||
|
||||
Server::Server(Engine* localClient) : localClient(localClient) {
|
||||
|
||||
players.push_back(std::make_unique<ServerPlayer>());
|
||||
}
|
||||
|
||||
void Server::run() {
|
||||
|
|
@ -60,7 +60,7 @@ void Server::start() {
|
|||
}
|
||||
|
||||
void Server::tick() {
|
||||
|
||||
world->tick();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include <cista.h>
|
||||
|
||||
#include <Events.h>
|
||||
#include <ServerPlayer.h>
|
||||
#include <ServerWorld.h>
|
||||
|
||||
#define STEP_SIZE 200
|
||||
|
||||
|
|
@ -15,6 +17,9 @@ class Engine;
|
|||
class Server: public EventTarget {
|
||||
Artifact::Engine* localClient;
|
||||
public:
|
||||
std::vector<std::unique_ptr<ServerPlayer>> players;
|
||||
std::unique_ptr<World::ServerWorld> world = std::make_unique<World::ServerWorld>(this);
|
||||
|
||||
Server(Engine* localClient);
|
||||
|
||||
void run();
|
||||
|
|
@ -27,6 +32,21 @@ public:
|
|||
void receiveMessage(T msg) {
|
||||
if constexpr (std::is_same_v<T, Events::Initialized>) {
|
||||
std::cout << "Message received" << std::endl;
|
||||
} else if constexpr (std::is_same_v<T, Events::PlayerJoin>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
src/server/ServerPlayer.cpp
Normal file
9
src/server/ServerPlayer.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#include <ServerPlayer.h>
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
ServerPlayer::ServerPlayer() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
src/server/ServerPlayer.h
Normal file
18
src/server/ServerPlayer.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm.hpp>
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
class ServerPlayer {
|
||||
public:
|
||||
ServerPlayer();
|
||||
|
||||
glm::ivec3 lastChunkPos;
|
||||
glm::vec3 pos;
|
||||
double pitch;
|
||||
double yaw;
|
||||
int viewRange = 4;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
#include <unordered_set>
|
||||
|
||||
#include <Server.h>
|
||||
#include <ServerWorld.h>
|
||||
|
||||
namespace Artifact {
|
||||
|
|
@ -8,36 +10,105 @@ ServerWorld::ServerWorld(Server* server) : server(server) {
|
|||
|
||||
}
|
||||
|
||||
void ServerWorld::tick() {
|
||||
|
||||
glm::ivec3 getChunkPos(glm::vec3 p) {
|
||||
return {static_cast<int>(std::floor(p.x / 16.0)), static_cast<int>(std::floor(p.y / 16.0)), static_cast<int>(std::floor(p.z / 16.0))};
|
||||
}
|
||||
|
||||
void ServerWorld::updateLoadedChunks() {
|
||||
std::unordered_set<glm::ivec3, Util::Vec3Hash, Util::Vec3Equal> generateViewChunks(const glm::ivec3& center, int range) {
|
||||
std::unordered_set<glm::ivec3, Util::Vec3Hash, Util::Vec3Equal> 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<glm::ivec3> toLoad;
|
||||
std::vector<glm::ivec3> 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<glm::ivec3, Util::Vec3Hash, Util::Vec3Equal> 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<glm::ivec3, Util::Vec3Hash, Util::Vec3Equal> 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<uint16_t, Chunk::SIZE_CUBED> data;
|
||||
for(int x = 0; x < CHUNK_SIZE; ++x)
|
||||
for(int z = 0; z < CHUNK_SIZE; ++z) {
|
||||
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 + 1; // Stone
|
||||
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] = 2; // Grass on top
|
||||
data[x + 8 * Chunk::SIZE + z * Chunk::SIZE * Chunk::SIZE] = 4; // Grass on top
|
||||
}
|
||||
}
|
||||
chunks.at(pos) = std::make_unique<Chunk>(data);
|
||||
}
|
||||
}
|
||||
chunks.emplace(pos, std::make_unique<Chunk>(data));
|
||||
return chunks.at(pos).get();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <World.h>
|
||||
#include <Server.h>
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
class Server;
|
||||
|
||||
namespace World {
|
||||
|
||||
class ServerWorld: public World {
|
||||
Server* server = nullptr;
|
||||
std::unordered_map<glm::vec3, int, Util::Vec3Hash, Util::Vec3Equal> refcounts;
|
||||
public:
|
||||
ServerWorld(Server* server);
|
||||
|
||||
void tick();
|
||||
void updateLoadedChunks();
|
||||
Chunk* loadChunk(glm::ivec3 pos);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<unsigned char>& atlasData) {
|
||||
|
|
@ -27,8 +30,10 @@ bool NodeRegistry::generateTextureAtlas(int& atlasWidth, int& atlasHeight, std::
|
|||
std::vector<std::vector<unsigned char>> textureData;
|
||||
std::vector<int> 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<float>(xOffset + widths[i]) / atlasWidth;
|
||||
float vMin = 0.0f;
|
||||
float vMax = static_cast<float>(heights[i]) / atlasHeight;
|
||||
textureCoords.at(drawables.at(i)) = {{uMin, vMin}, {uMax, vMax}};
|
||||
textureCoords.emplace(drawables.at(i), NodeTextureCoords{{uMin, vMin}, {uMax, vMax}});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -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<uint16_t, NodeDef> registeredNodes;
|
||||
std::vector<NodeTextureCoords> textureCoords;
|
||||
std::unordered_map<uint16_t, NodeTextureCoords> 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<unsigned char>& atlasData);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue