mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
feat(client-chunk): add greedy meshing, receive_chunk, and biome field
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Cubed/tools/cubed_assert.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Cubed {
|
namespace Cubed {
|
||||||
@@ -77,4 +80,32 @@ DesertParams& desert_params();
|
|||||||
MountainParams& mountain_params();
|
MountainParams& mountain_params();
|
||||||
RiverParams& river_params();
|
RiverParams& river_params();
|
||||||
|
|
||||||
|
inline BiomeType get_biome(int id) {
|
||||||
|
using enum BiomeType;
|
||||||
|
auto to = std::to_underlying<BiomeType>;
|
||||||
|
if (id == to(PLAIN)) {
|
||||||
|
return PLAIN;
|
||||||
|
}
|
||||||
|
if (id == to(FOREST)) {
|
||||||
|
return FOREST;
|
||||||
|
}
|
||||||
|
if (id == to(DESERT)) {
|
||||||
|
return DESERT;
|
||||||
|
}
|
||||||
|
if (id == to(MOUNTAIN)) {
|
||||||
|
return MOUNTAIN;
|
||||||
|
}
|
||||||
|
if (id == to(RIVER)) {
|
||||||
|
return RIVER;
|
||||||
|
}
|
||||||
|
if (id == to(SNOWY_PLAIN)) {
|
||||||
|
return SNOWY_PLAIN;
|
||||||
|
}
|
||||||
|
if (id == to(OCEAN)) {
|
||||||
|
return OCEAN;
|
||||||
|
}
|
||||||
|
ASSERT_MSG(false, "Unknown Biome Id");
|
||||||
|
throw std::invalid_argument("Unknown Biome Id");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "Cubed/gameplay/block.hpp"
|
#include "Cubed/gameplay/block.hpp"
|
||||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||||
#include "Cubed/gameplay/vertex_data.hpp"
|
#include "Cubed/gameplay/vertex_data.hpp"
|
||||||
|
#include "world/chunk_data.pb.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
@@ -34,9 +35,13 @@ public:
|
|||||||
ClientChunk(ClientChunk&&) noexcept;
|
ClientChunk(ClientChunk&&) noexcept;
|
||||||
ClientChunk& operator=(ClientChunk&&) noexcept;
|
ClientChunk& operator=(ClientChunk&&) noexcept;
|
||||||
|
|
||||||
|
static int index(int x, int y, int z);
|
||||||
|
static int index(const glm::vec3& pos);
|
||||||
|
|
||||||
BiomeType get_biome() const;
|
BiomeType get_biome() const;
|
||||||
ChunkPos get_chunk_pos() const;
|
ChunkPos get_chunk_pos() const;
|
||||||
const std::vector<BlockType>& get_chunk_blocks() const;
|
const std::vector<BlockType>& get_chunk_blocks() const;
|
||||||
|
void receive_chunk(const ChunkDataRsp& data);
|
||||||
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
|
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
|
||||||
void upload_to_gpu();
|
void upload_to_gpu();
|
||||||
|
|
||||||
@@ -85,6 +90,7 @@ private:
|
|||||||
static constexpr int SIZE_X = CHUNK_SIZE;
|
static constexpr int SIZE_X = CHUNK_SIZE;
|
||||||
static constexpr int SIZE_Y = WORLD_SIZE_Y;
|
static constexpr int SIZE_Y = WORLD_SIZE_Y;
|
||||||
static constexpr int SIZE_Z = CHUNK_SIZE;
|
static constexpr int SIZE_Z = CHUNK_SIZE;
|
||||||
|
static constexpr int BLOCK_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
|
||||||
static constexpr int VERTEX_DATA_SUM = 5;
|
static constexpr int VERTEX_DATA_SUM = 5;
|
||||||
std::atomic<bool> m_dirty{false};
|
std::atomic<bool> m_dirty{false};
|
||||||
std::atomic<bool> m_need_upload{true};
|
std::atomic<bool> m_need_upload{true};
|
||||||
|
|||||||
@@ -0,0 +1,521 @@
|
|||||||
|
#include "Cubed/gameplay/client_chunk.hpp"
|
||||||
|
|
||||||
|
#include "Cubed/tools/cubed_assert.hpp"
|
||||||
|
|
||||||
|
namespace Cubed {
|
||||||
|
using OptionalBlockVectorArray =
|
||||||
|
std::array<std::optional<std::vector<BlockType>>, 4>;
|
||||||
|
namespace {
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Face direction mapping
|
||||||
|
// Original DIR[6]: {+Z,+X,-Z,-X,+Y,-Y} => face index 0-5
|
||||||
|
// Axis × direction => face:
|
||||||
|
// axis=2(Z) dir=+1 => face 0 (+Z)
|
||||||
|
// axis=0(X) dir=+1 => face 1 (+X)
|
||||||
|
// axis=2(Z) dir=-1 => face 2 (-Z)
|
||||||
|
// axis=0(X) dir=-1 => face 3 (-X)
|
||||||
|
// axis=1(Y) dir=+1 => face 4 (+Y)
|
||||||
|
// axis=1(Y) dir=-1 => face 5 (-Y)
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
inline int axis_dir_to_face(int axis, int dir) {
|
||||||
|
// axis: 0=X 1=Y 2=Z
|
||||||
|
// dir: +1 or -1
|
||||||
|
static const int TABLE[3][2] = {
|
||||||
|
{3, 1}, // X: dir=-1->face3(-X), dir=+1->face1(+X)
|
||||||
|
{5, 4}, // Y: dir=-1->face5(-Y), dir=+1->face4(+Y)
|
||||||
|
{2, 0}, // Z: dir=-1->face2(-Z), dir=+1->face0(+Z)
|
||||||
|
};
|
||||||
|
return TABLE[axis][dir > 0 ? 1 : 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline BlockType
|
||||||
|
get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos,
|
||||||
|
const std::vector<BlockType>& blocks,
|
||||||
|
const OptionalBlockVectorArray& neighbor_block) {
|
||||||
|
if (lx >= 0 && lx < CHUNK_SIZE && ly >= 0 && ly < WORLD_SIZE_Y && lz >= 0 &&
|
||||||
|
lz < CHUNK_SIZE) {
|
||||||
|
return blocks[ClientChunk::index(lx, ly, lz)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out of bounds: check neighbors
|
||||||
|
int world_x = lx + chunk_pos.x * CHUNK_SIZE;
|
||||||
|
int world_z = lz + chunk_pos.z * CHUNK_SIZE;
|
||||||
|
|
||||||
|
auto [nb_cx, nb_cz] = get_chunk_pos(world_x, world_z);
|
||||||
|
|
||||||
|
const std::optional<std::vector<BlockType>>* nb = nullptr;
|
||||||
|
if (nb_cx == chunk_pos.x + 1)
|
||||||
|
nb = &neighbor_block[0];
|
||||||
|
else if (nb_cx == chunk_pos.x - 1)
|
||||||
|
nb = &neighbor_block[1];
|
||||||
|
else if (nb_cz == chunk_pos.z + 1)
|
||||||
|
nb = &neighbor_block[2];
|
||||||
|
else if (nb_cz == chunk_pos.z - 1)
|
||||||
|
nb = &neighbor_block[3];
|
||||||
|
|
||||||
|
if (!nb || !nb->has_value())
|
||||||
|
return 0; // Neighbor does not exist, treat as opaque
|
||||||
|
|
||||||
|
int nbx = world_x - nb_cx * CHUNK_SIZE;
|
||||||
|
int nby = ly;
|
||||||
|
int nbz = world_z - nb_cz * CHUNK_SIZE;
|
||||||
|
|
||||||
|
if (nbx < 0 || nby < 0 || nbz < 0 || nbx >= CHUNK_SIZE ||
|
||||||
|
nby >= WORLD_SIZE_Y || nbz >= CHUNK_SIZE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int idx = ClientChunk::index(nbx, nby, nbz);
|
||||||
|
if (static_cast<size_t>(idx) >= (*nb)->size()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (**nb)[idx];
|
||||||
|
}
|
||||||
|
// Determine whether the face from cur_id looking towards neighbor_id should be
|
||||||
|
// culled (does not need to be rendered)
|
||||||
|
inline bool is_face_culled(BlockType cur_id, BlockType neighbor_id) {
|
||||||
|
if (!BlockManager::is_transparent(neighbor_id))
|
||||||
|
return true; // Neighbor is opaque, blocking
|
||||||
|
// Neighbor transparency: same block type culls each other (e.g., water
|
||||||
|
// adjacent to water does not render internal faces)
|
||||||
|
if (neighbor_id == cur_id)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int choose_buf(BlockType id) {
|
||||||
|
if (!BlockManager::is_transparent(id))
|
||||||
|
return 0;
|
||||||
|
if (BlockManager::is_discard(id))
|
||||||
|
return 2;
|
||||||
|
if (BlockManager::is_blend(id)) {
|
||||||
|
return (id == 7) ? 4 : 3; // water=4, other blend=3
|
||||||
|
}
|
||||||
|
return 3; // fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
ClientChunk::ClientChunk(ClientWorld& world) : m_world(world) {}
|
||||||
|
ClientChunk::~ClientChunk() {}
|
||||||
|
|
||||||
|
ClientChunk::ClientChunk(ClientChunk&& other) noexcept
|
||||||
|
: m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()),
|
||||||
|
m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()),
|
||||||
|
m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)),
|
||||||
|
m_world(other.m_world), m_blocks(std::move(other.m_blocks)),
|
||||||
|
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed) {}
|
||||||
|
|
||||||
|
ClientChunk& ClientChunk::operator=(ClientChunk&& other) noexcept {
|
||||||
|
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
|
||||||
|
// other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z,
|
||||||
|
// static_cast<const void*>(&other));
|
||||||
|
|
||||||
|
m_chunk_pos = std::move(other.m_chunk_pos);
|
||||||
|
m_blocks = std::move(other.m_blocks);
|
||||||
|
m_dirty = other.is_dirty();
|
||||||
|
m_vertex_data = std::move(other.m_vertex_data);
|
||||||
|
m_biome = other.m_biome.load();
|
||||||
|
m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load();
|
||||||
|
m_need_upload = other.m_need_upload.load();
|
||||||
|
m_seed = other.m_seed;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientChunk::index(int x, int y, int z) {
|
||||||
|
ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||||
|
z >= CHUNK_SIZE));
|
||||||
|
if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 ||
|
||||||
|
(x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >=
|
||||||
|
CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) {
|
||||||
|
Logger::error("block pos x {} y {} z {} range error", x, y, z);
|
||||||
|
ASSERT(0);
|
||||||
|
}
|
||||||
|
return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientChunk::index(const glm::vec3& pos) {
|
||||||
|
return ClientChunk::index(pos.x, pos.y, pos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
BiomeType ClientChunk::get_biome() const { return m_biome.load(); }
|
||||||
|
|
||||||
|
ChunkPos ClientChunk::get_chunk_pos() const { return m_chunk_pos; }
|
||||||
|
|
||||||
|
const std::vector<BlockType>& ClientChunk::get_chunk_blocks() const {
|
||||||
|
return m_blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientChunk::gen_vertex_data(
|
||||||
|
const OptionalBlockVectorArray& neighbor_block) {
|
||||||
|
if (m_is_on_gen_vertex_data.exchange(true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard lk(m_vertexs_data_mutex);
|
||||||
|
|
||||||
|
for (auto& data : m_vertex_data) {
|
||||||
|
data.m_vertices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_vertices(neighbor_block);
|
||||||
|
for (auto& data : m_vertex_data) {
|
||||||
|
data.update_sum();
|
||||||
|
}
|
||||||
|
m_need_upload = true;
|
||||||
|
m_is_on_gen_vertex_data = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ClientChunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
|
||||||
|
|
||||||
|
size_t ClientChunk::get_normal_vertices_sum() const {
|
||||||
|
if (m_vertex_data[0].m_sum == 0) {
|
||||||
|
Logger::warn("m_normal_vertices_sum is 0");
|
||||||
|
}
|
||||||
|
return m_vertex_data[0].m_sum.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ClientChunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
|
||||||
|
size_t ClientChunk::get_cross_vertices_sum() const {
|
||||||
|
return m_vertex_data[1].m_sum.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ClientChunk::get_normal_discard_vao() const {
|
||||||
|
return m_vertex_data[2].m_vao;
|
||||||
|
}
|
||||||
|
size_t ClientChunk::get_normal_discard_vertices_sum() const {
|
||||||
|
return m_vertex_data[2].m_sum.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ClientChunk::get_normal_blend_vao() const {
|
||||||
|
return m_vertex_data[3].m_vao;
|
||||||
|
}
|
||||||
|
size_t ClientChunk::get_normal_blend_vertices_sum() const {
|
||||||
|
return m_vertex_data[3].m_sum.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint ClientChunk::get_water_vao() const { return m_vertex_data[4].m_vao; }
|
||||||
|
size_t ClientChunk::get_water_vertices_sum() const {
|
||||||
|
return m_vertex_data[4].m_sum.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientChunk::upload_to_gpu() {
|
||||||
|
|
||||||
|
ASSERT(is_need_upload());
|
||||||
|
|
||||||
|
std::lock_guard lk(m_vertexs_data_mutex);
|
||||||
|
|
||||||
|
for (auto& data : m_vertex_data) {
|
||||||
|
data.upload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// after fininshed it, can use
|
||||||
|
clear_dirty();
|
||||||
|
m_need_upload = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClientChunk::is_dirty() const { return m_dirty.load(); }
|
||||||
|
|
||||||
|
void ClientChunk::mark_dirty() { m_dirty = true; }
|
||||||
|
|
||||||
|
void ClientChunk::clear_dirty() { m_dirty = false; }
|
||||||
|
|
||||||
|
bool ClientChunk::is_need_upload() const { return m_need_upload.load(); }
|
||||||
|
|
||||||
|
void ClientChunk::need_upload() { m_need_upload = true; }
|
||||||
|
|
||||||
|
void ClientChunk::set_chunk_block(int index, unsigned id) {
|
||||||
|
m_blocks[index] = id;
|
||||||
|
mark_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; }
|
||||||
|
|
||||||
|
BiomeType ClientChunk::biome() const { return m_biome; }
|
||||||
|
|
||||||
|
void ClientChunk::biome(BiomeType b) { m_biome = b; }
|
||||||
|
|
||||||
|
std::vector<BlockType>& ClientChunk::blocks() { return m_blocks; }
|
||||||
|
ClientWorld& ClientChunk::world() { return m_world; }
|
||||||
|
unsigned ClientChunk::seed() const {
|
||||||
|
if (m_seed == 0) {
|
||||||
|
Logger::warn("Seed Not Generator");
|
||||||
|
}
|
||||||
|
return m_seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
|
||||||
|
|
||||||
|
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256
|
||||||
|
// Axis order: axis 0=X, 1=Y, 2=Z
|
||||||
|
// Two slice dimensions of each axis
|
||||||
|
const int DIMS[3] = {CHUNK_SIZE, WORLD_SIZE_Y, CHUNK_SIZE};
|
||||||
|
|
||||||
|
// Maximum mask size: max(16*256, 16*16) = 4096
|
||||||
|
static thread_local FaceKey mask[CHUNK_SIZE * WORLD_SIZE_Y];
|
||||||
|
static thread_local bool visited[CHUNK_SIZE * WORLD_SIZE_Y];
|
||||||
|
|
||||||
|
for (int axis = 0; axis < 3; axis++) {
|
||||||
|
int u_axis = (axis + 1) % 3; // horizontal
|
||||||
|
int v_axis = (axis + 2) % 3; // vertical
|
||||||
|
|
||||||
|
int u = DIMS[u_axis];
|
||||||
|
int v = DIMS[v_axis];
|
||||||
|
int d = DIMS[axis]; // Depth along the normal axis
|
||||||
|
|
||||||
|
for (int face_dir : {1, -1}) {
|
||||||
|
int face_idx = axis_dir_to_face(axis, face_dir);
|
||||||
|
|
||||||
|
for (int layer = 0; layer < d; layer++) {
|
||||||
|
|
||||||
|
// ── 1. Build mask ──────────────────────────────────────────
|
||||||
|
for (int vi = 0; vi < v; vi++) {
|
||||||
|
for (int ui = 0; ui < u; ui++) {
|
||||||
|
// Current cell local coordinates
|
||||||
|
int lpos[3];
|
||||||
|
lpos[axis] = layer;
|
||||||
|
lpos[u_axis] = ui;
|
||||||
|
lpos[v_axis] = vi;
|
||||||
|
|
||||||
|
// Neighbor (offset one cell along the normal direction)
|
||||||
|
int npos[3];
|
||||||
|
npos[axis] = layer + face_dir;
|
||||||
|
npos[u_axis] = ui;
|
||||||
|
npos[v_axis] = vi;
|
||||||
|
|
||||||
|
BlockType cur_id = get_block_safe(
|
||||||
|
lpos[0], lpos[1], lpos[2], m_chunk_pos, m_blocks,
|
||||||
|
neighbor_block);
|
||||||
|
|
||||||
|
// Air / cross plane are not involved in greedy meshing
|
||||||
|
if (cur_id == 0 ||
|
||||||
|
BlockManager::is_cross_plane(cur_id)) {
|
||||||
|
mask[vi * u + ui] = {};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockType nb_id = get_block_safe(
|
||||||
|
npos[0], npos[1], npos[2], m_chunk_pos, m_blocks,
|
||||||
|
neighbor_block);
|
||||||
|
|
||||||
|
if (is_face_culled(cur_id, nb_id)) {
|
||||||
|
mask[vi * u + ui] = {};
|
||||||
|
} else {
|
||||||
|
mask[vi * u + ui] = {cur_id, face_idx};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. Greedy Merge ──────────────────────────────────────
|
||||||
|
std::fill(visited, visited + u * v, false);
|
||||||
|
|
||||||
|
for (int vi = 0; vi < v; vi++) {
|
||||||
|
for (int ui = 0; ui < u; ui++) {
|
||||||
|
if (visited[vi * u + ui])
|
||||||
|
continue;
|
||||||
|
FaceKey cur = mask[vi * u + ui];
|
||||||
|
if (!cur.valid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Extend width in the u direction
|
||||||
|
int w = 1;
|
||||||
|
while (ui + w < u && !visited[vi * u + (ui + w)] &&
|
||||||
|
mask[vi * u + (ui + w)] == cur) {
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend height in the v direction
|
||||||
|
int h = 1;
|
||||||
|
bool can_expand = true;
|
||||||
|
while (vi + h < v && can_expand) {
|
||||||
|
for (int k = 0; k < w; k++) {
|
||||||
|
int idx = (vi + h) * u + (ui + k);
|
||||||
|
if (visited[idx] || mask[idx] != cur) {
|
||||||
|
can_expand = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (can_expand)
|
||||||
|
h++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark visited
|
||||||
|
for (int dv = 0; dv < h; dv++)
|
||||||
|
for (int du = 0; du < w; du++)
|
||||||
|
visited[(vi + dv) * u + (ui + du)] = true;
|
||||||
|
|
||||||
|
// output quad
|
||||||
|
emit_quad(axis, face_dir, layer, ui, vi, w, h, u_axis,
|
||||||
|
v_axis, cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < CHUNK_SIZE; x++) {
|
||||||
|
for (int y = 0; y < WORLD_SIZE_Y; y++) {
|
||||||
|
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||||
|
BlockType id = m_blocks[index(x, y, z)];
|
||||||
|
if (id != 0 && BlockManager::is_cross_plane(id)) {
|
||||||
|
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
|
||||||
|
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
|
||||||
|
gen_cross_plane_vertices(world_x, y, world_z, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ClientChunk::emit_quad(int axis, int face_dir, int layer, int i, int j,
|
||||||
|
int w, int h, int u_axis, int v_axis, FaceKey key) {
|
||||||
|
float axis_val = (float)(layer + (face_dir > 0 ? 1 : 0));
|
||||||
|
float wx_base = (float)(m_chunk_pos.x * CHUNK_SIZE);
|
||||||
|
float wz_base = (float)(m_chunk_pos.z * CHUNK_SIZE);
|
||||||
|
|
||||||
|
// Offsets of the four corners along the u_axis/v_axis
|
||||||
|
int su[4] = {0, w, w, 0};
|
||||||
|
int sv[4] = {0, 0, h, h};
|
||||||
|
|
||||||
|
// Each face's UV: directly read from the four corners of TEX_COORDS, then
|
||||||
|
// scaled by w/h TEX_COORDS vertex order: 0=BL, 1=TL, 2=TR, 3=TR, 4=BR, 5=BL
|
||||||
|
// (two triangles) Four unique corners correspond to indices: BL=0, TL=1,
|
||||||
|
// TR=2, BR=4 Extract the UVs of the four corners from TEX_COORDS (unique
|
||||||
|
// corners after removing duplicate vertices) Vertices 0,1,2,4 correspond to
|
||||||
|
// BL, TL, TR, BR
|
||||||
|
float u0 = TEX_COORDS[key.face][0][0]; // BL.u
|
||||||
|
float v0 = TEX_COORDS[key.face][0][1]; // BL.v
|
||||||
|
float u1 = TEX_COORDS[key.face][4][0]; // BR.u
|
||||||
|
float v1 = TEX_COORDS[key.face][4][1]; // BR.v
|
||||||
|
float u3 = TEX_COORDS[key.face][1][0]; // TL.u
|
||||||
|
float v3 = TEX_COORDS[key.face][1][1]; // TL.v
|
||||||
|
|
||||||
|
float du_u = u1 - u0; // Change in u when su increases (per block)
|
||||||
|
float dv_u = v1 - v0;
|
||||||
|
float du_v = u3 - u0; // Change in u when sv increases
|
||||||
|
float dv_v = v3 - v0;
|
||||||
|
|
||||||
|
float uvs[4][2] = {
|
||||||
|
{u0, v0}, // (0, 0 )
|
||||||
|
{u0 + du_u * (float)w, v0 + dv_u * (float)w}, // (w, 0 )
|
||||||
|
{u0 + du_u * (float)w + du_v * (float)h,
|
||||||
|
v0 + dv_u * (float)w + dv_v * (float)h}, // (w, h )
|
||||||
|
{u0 + du_v * (float)h, v0 + dv_v * (float)h}, // (0, h )
|
||||||
|
};
|
||||||
|
|
||||||
|
int tri[6] = {0, 1, 2, 0, 2, 3};
|
||||||
|
|
||||||
|
float pos[4][3];
|
||||||
|
for (int c = 0; c < 4; c++) {
|
||||||
|
pos[c][axis] = axis_val;
|
||||||
|
pos[c][u_axis] = (float)(i + su[c]);
|
||||||
|
pos[c][v_axis] = (float)(j + sv[c]);
|
||||||
|
pos[c][0] += wx_base;
|
||||||
|
pos[c][2] += wz_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
float layer_id = (float)(key.block_id * 6 + key.face);
|
||||||
|
float roughness = BlockManager::roughness(key.block_id);
|
||||||
|
int buf = choose_buf(key.block_id);
|
||||||
|
|
||||||
|
for (int vi = 0; vi < 6; vi++) {
|
||||||
|
int c = tri[vi];
|
||||||
|
Vertex3D vex = {
|
||||||
|
pos[c][0],
|
||||||
|
pos[c][1],
|
||||||
|
pos[c][2],
|
||||||
|
uvs[c][0],
|
||||||
|
uvs[c][1],
|
||||||
|
layer_id,
|
||||||
|
NORMALS[key.face][0][0],
|
||||||
|
NORMALS[key.face][0][1],
|
||||||
|
NORMALS[key.face][0][2],
|
||||||
|
roughness,
|
||||||
|
TANGENTS[key.face][0][0],
|
||||||
|
TANGENTS[key.face][0][1],
|
||||||
|
TANGENTS[key.face][0][2],
|
||||||
|
};
|
||||||
|
m_vertex_data[buf].m_vertices.emplace_back(vex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientChunk::gen_cross_plane_vertices(int world_x, int world_y,
|
||||||
|
int world_z, BlockType id) {
|
||||||
|
|
||||||
|
if (!BlockManager::is_cross_plane(id)) {
|
||||||
|
Logger::warn("Block {} {} {} id {} is not cross plane", world_x,
|
||||||
|
world_y, world_z, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int face = 0; face < 2; face++) {
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
Vertex3D vex = {
|
||||||
|
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
|
||||||
|
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
|
||||||
|
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
|
||||||
|
CROSS_TEX_COORDS[face][i][0],
|
||||||
|
CROSS_TEX_COORDS[face][i][1],
|
||||||
|
static_cast<float>(BlockManager::cross_plane_index(id)),
|
||||||
|
CROSS_NORMALS[face][i][0],
|
||||||
|
CROSS_NORMALS[face][i][1],
|
||||||
|
CROSS_NORMALS[face][i][2],
|
||||||
|
BlockManager::roughness(id),
|
||||||
|
CROSS_TANGENTS[face][i][0],
|
||||||
|
CROSS_TANGENTS[face][i][1],
|
||||||
|
CROSS_TANGENTS[face][i][2]
|
||||||
|
|
||||||
|
};
|
||||||
|
m_vertex_data[1].m_vertices.emplace_back(vex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientChunk::receive_chunk(const ChunkDataRsp& data) {
|
||||||
|
OptionalBlockVectorArray neighbor;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
neighbor[i] = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.chunk_blocks_size() != BLOCK_SIZE) {
|
||||||
|
Logger::error("Bad Chunk, size {}", data.chunk_blocks_size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_blocks.reserve(BLOCK_SIZE);
|
||||||
|
|
||||||
|
for (const auto& b : data.chunk_blocks()) {
|
||||||
|
m_blocks.push_back(static_cast<BlockType>(b));
|
||||||
|
}
|
||||||
|
// temp neighbor block data
|
||||||
|
if (data.neighbor_blocks_1_size() == BLOCK_SIZE) {
|
||||||
|
neighbor[0] = std::vector<BlockType>();
|
||||||
|
neighbor[0]->reserve(BLOCK_SIZE);
|
||||||
|
for (const auto& b : data.chunk_blocks()) {
|
||||||
|
neighbor[0]->push_back(static_cast<BlockType>(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.neighbor_blocks_2_size() == BLOCK_SIZE) {
|
||||||
|
neighbor[1] = std::vector<BlockType>();
|
||||||
|
neighbor[1]->reserve(BLOCK_SIZE);
|
||||||
|
for (const auto& b : data.chunk_blocks()) {
|
||||||
|
neighbor[1]->push_back(static_cast<BlockType>(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.neighbor_blocks_3_size() == BLOCK_SIZE) {
|
||||||
|
neighbor[2] = std::vector<BlockType>();
|
||||||
|
neighbor[2]->reserve(BLOCK_SIZE);
|
||||||
|
for (const auto& b : data.chunk_blocks()) {
|
||||||
|
neighbor[2]->push_back(static_cast<BlockType>(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.neighbor_blocks_3_size() == BLOCK_SIZE) {
|
||||||
|
neighbor[3] = std::vector<BlockType>();
|
||||||
|
neighbor[3]->reserve(BLOCK_SIZE);
|
||||||
|
for (const auto& b : data.chunk_blocks()) {
|
||||||
|
neighbor[3]->push_back(static_cast<BlockType>(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_vertex_data(neighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Cubed
|
||||||
@@ -437,6 +437,7 @@ void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rsq.set_chunk_seed(it->second.seed());
|
rsq.set_chunk_seed(it->second.seed());
|
||||||
|
rsq.set_biome_type(std::to_underlying(it->second.biome()));
|
||||||
auto* blocks = rsq.mutable_chunk_blocks();
|
auto* blocks = rsq.mutable_chunk_blocks();
|
||||||
auto& chunk_blocks = it->second.get_chunk_blocks();
|
auto& chunk_blocks = it->second.get_chunk_blocks();
|
||||||
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
|
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ message ChunkDataReq {
|
|||||||
message ChunkDataRsp {
|
message ChunkDataRsp {
|
||||||
ChunkPos pos = 1;
|
ChunkPos pos = 1;
|
||||||
uint32 chunk_seed = 2;
|
uint32 chunk_seed = 2;
|
||||||
repeated uint32 chunk_blocks = 3 [packed=true];
|
int32 biome_type = 3;
|
||||||
repeated uint32 neighbor_blocks_1 = 4 [packed=true];
|
repeated uint32 chunk_blocks = 4 [packed=true];
|
||||||
repeated uint32 neighbor_blocks_2 = 5 [packed=true];
|
repeated uint32 neighbor_blocks_1 = 5 [packed=true];
|
||||||
repeated uint32 neighbor_blocks_3 = 6 [packed=true];
|
repeated uint32 neighbor_blocks_2 = 6 [packed=true];
|
||||||
repeated uint32 neighbor_blocks_4 = 7 [packed=true];
|
repeated uint32 neighbor_blocks_3 = 7 [packed=true];
|
||||||
|
repeated uint32 neighbor_blocks_4 = 8 [packed=true];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user