-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworld.cpp
More file actions
138 lines (122 loc) · 5.26 KB
/
world.cpp
File metadata and controls
138 lines (122 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <algorithm>
#include "world.h"
#include <unordered_set>
#include "coords.h"
#include "mesher.h"
constexpr int CHUNK_RADIUS = 1;
// a square disk around a position
std::vector<glm::ivec2> neighbors(const glm::ivec2 position, const int layers) {
std::vector<glm::ivec2> vec{};
for (int x = -layers; x <= layers; x++)
for (int y = -layers; y <= layers; y++)
vec.emplace_back(position + glm::ivec2{x, y});
return vec;
}
// abstract this and `neighbors()` from the returned container?
std::unordered_set<glm::ivec2> disk(const glm::ivec2 position,
const int layers) {
std::unordered_set<glm::ivec2> set{};
for (int x = -layers; x <= layers; x++)
for (int y = -layers; y <= layers; y++)
set.emplace(position + glm::ivec2{x, y});
return set;
}
// TODO optimization: instead of constantly releasing 2*RADIUS+1 chunks on every
// change and reallocating the SAME AMOUNT OF SPACE just try and reuse the
// allocated memory somehow. After all, for a fixed block radius we should
// be able to allocate all necessary memory at start and never change it
// again!
// Chunk Arena, a statically allocated set of pages. The hashmap indexes
// into it converting from world-coordinates into arena-coordinates.
void update_chunks(std::unordered_map<glm::ivec2, Chunk> &chunks,
const std::optional<glm::ivec2> previous_chunk,
const glm::ivec2 current_chunk) {
if (previous_chunk.has_value()) {
auto direction = current_chunk - previous_chunk.value();
// moving in x direction
if (direction.x != 0) {
const int erase_line = current_chunk.x - direction.x * (CHUNK_RADIUS + 1);
const int emplace_line = current_chunk.x + direction.x * CHUNK_RADIUS;
for (int z = -CHUNK_RADIUS; z <= CHUNK_RADIUS; z++) {
auto pos = glm::ivec2{erase_line, current_chunk.y + z};
chunks.erase(pos);
pos.x = emplace_line;
chunks.emplace(pos, Chunk{chunks, pos.x, pos.y});
}
} else { // moving in z direction
const int erase_line = current_chunk.y - direction.y * (CHUNK_RADIUS + 1);
const int emplace_line = current_chunk.y + direction.y * CHUNK_RADIUS;
for (int x = -CHUNK_RADIUS; x <= CHUNK_RADIUS; x++) {
auto pos = glm::ivec2{current_chunk.x + x, erase_line};
chunks.erase(pos);
pos.y = emplace_line;
chunks.emplace(pos, Chunk{chunks, pos.x, pos.y});
}
}
} else {
chunks.clear();
for (const auto &coord : neighbors(current_chunk, CHUNK_RADIUS))
chunks.emplace(coord, Chunk{chunks, coord.x, coord.y});
}
}
bool within_local_bounds(const glm::ivec3 pos) {
return pos.x >= 0 && pos.x < CHUNK_SIZE && pos.y >= 0 &&
pos.y < CHUNK_HEIGHT && pos.z >= 0 && pos.z < CHUNK_SIZE;
}
bool is_solid_at_world(const std::unordered_map<glm::ivec2, Chunk> &chunks,
const glm::ivec3 world_pos) {
const glm::ivec2 chunk_pos = coords::world_to_chunk_pos(world_pos);
auto it = chunks.find(chunk_pos);
if (it == chunks.end()) {
// Treat missing chunks as air so faces render until neighbors load.
// TODO trigger boundary remesh when new chunks arrive.
return false;
}
const glm::ivec3 local_pos = coords::world_to_local_pos(world_pos);
if (!within_local_bounds(local_pos))
return false;
return it->second.chunk_map[local_pos.x][local_pos.y][local_pos.z];
}
void emit_meshes(const std::unordered_map<glm::ivec2, Chunk> &chunks,
Mesh &mesh, std::vector<ChunkDraw> &draws) {
mesh.vertices.clear();
mesh.indices.clear();
draws.clear();
auto query = [&chunks](const glm::ivec3 pos) {
return is_solid_at_world(chunks, pos);
};
for (const auto &[pos, chunk] : chunks) {
Mesh chunk_mesh = build_chunk_mesh(chunk, pos, query);
const uint32_t base_index =
static_cast<uint32_t>(mesh.vertices.size());
const size_t index_offset = mesh.indices.size();
const glm::vec3 min{
static_cast<float>(pos.x * CHUNK_SIZE), 0.0f,
static_cast<float>(pos.y * CHUNK_SIZE)};
const glm::vec3 max{
static_cast<float>((pos.x + 1) * CHUNK_SIZE),
static_cast<float>(CHUNK_HEIGHT),
static_cast<float>((pos.y + 1) * CHUNK_SIZE)};
mesh.vertices.insert(mesh.vertices.end(), chunk_mesh.vertices.begin(),
chunk_mesh.vertices.end());
mesh.indices.reserve(mesh.indices.size() + chunk_mesh.indices.size());
for (const uint32_t idx : chunk_mesh.indices)
mesh.indices.push_back(base_index + idx);
draws.push_back(
ChunkDraw{pos, index_offset, chunk_mesh.indices.size(), AABB{min, max}});
}
}
World::World(const glm::vec3 start_position) { set_position(start_position); }
void World::set_position(const glm::vec3 new_pos) {
player_position = new_pos;
current_chunk = coords::pos_to_chunk(player_position);
if (prev_chunk != current_chunk) {
chunk_changed = true; // signals the renderer to upload the data to the gpu
update_chunks(chunks, prev_chunk, current_chunk);
emit_meshes(chunks, chunk_mesh, chunk_draws);
prev_chunk = current_chunk;
}
}
void World::finished_rendering() { chunk_changed = false; }
const Mesh &World::mesh() const { return chunk_mesh; }
const std::vector<ChunkDraw> &World::draws() const { return chunk_draws; }