Botcraft 1.21.4
Loading...
Searching...
No Matches
WorldRenderer.cpp
Go to the documentation of this file.
7
12
13#include <glm/gtc/type_ptr.hpp>
14
15#include <unordered_set>
16
17namespace Botcraft
18{
19 namespace Renderer
20 {
21 WorldRenderer::WorldRenderer(const unsigned int section_height_)
22 {
23 section_height = section_height_;
24
25 chunks = std::unordered_map<Position, std::shared_ptr<Chunk> >();
26 transparent_chunks = std::unordered_map<Position, std::shared_ptr<TransparentChunk> >();
27
29
30 entities = std::unordered_map<int, std::shared_ptr<Entity> >();
32
33 camera = std::make_shared<Camera>();
34 }
35
37 {
38 glDeleteTextures(1, &atlas_texture);
39 camera.reset();
40 }
41
42 std::shared_ptr<Camera> WorldRenderer::GetCamera()
43 {
44 return camera;
45 }
46
48 {
49 glGenBuffers(1, &view_uniform_buffer);
50 glBindBuffer(GL_UNIFORM_BUFFER, view_uniform_buffer);
51 glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
52 glBindBuffer(GL_UNIFORM_BUFFER, 0);
53
54 glBindBufferRange(GL_UNIFORM_BUFFER, 0, view_uniform_buffer, 0, sizeof(glm::mat4));
55
56 //Create a texture
57 glGenTextures(1, &atlas_texture);
58 glBindTexture(GL_TEXTURE_2D, atlas_texture);
59
60 //Options
61 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
62 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
63 float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
64 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
65 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
66 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
67
68 const Atlas* atlas = AssetsManager::getInstance().GetAtlas();
69
70 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, atlas->GetWidth(), atlas->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, atlas->Get());
71 glGenerateMipmap(GL_TEXTURE_2D);
72
73 glBindTexture(GL_TEXTURE_2D, 0);
74 }
75
77 {
78 if (camera->GetHasChangedOrientation() || camera->GetHasChangedPosition())
79 {
80 m_mutex_camera.lock();
81 glm::mat4 view_matrix = camera->GetViewMatrix();
82 if (camera->GetHasChangedPosition())
83 {
85 for (auto it = transparent_chunks.begin(); it != transparent_chunks.end(); ++it)
86 {
87 it->second->SetDisplayStatus(BufferStatus::Updated);
88 }
90 }
91 camera->ResetHasChangedPosition();
92 camera->ResetHasChangedOrientation();
93 m_mutex_camera.unlock();
94 glBindBuffer(GL_UNIFORM_BUFFER, view_uniform_buffer);
95 glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(view_matrix));
96 glBindBuffer(GL_UNIFORM_BUFFER, 0);
97 }
98 }
99
100 void WorldRenderer::SetCameraProjection(const glm::mat4& proj)
101 {
102 std::lock_guard<std::mutex> lock(m_mutex_camera);
103 camera->SetProjection(proj);
104 }
105
107 {
109 {
111 {
112 std::lock_guard<std::mutex> lock(chunks_mutex);
113 for (auto it = chunks.begin(); it != chunks.end();)
114 {
115 it->second->Update();
116 if (it->second->GetNumFace() == 0)
117 {
118 it = chunks.erase(it);
119 }
120 else
121 {
122 ++it;
123 }
124 }
125 }
126 {
127 std::lock_guard<std::mutex> lock(transparent_chunks_mutex);
128 for (auto it = transparent_chunks.begin(); it != transparent_chunks.end();)
129 {
130 it->second->Update();
131 if (it->second->GetNumFace() == 0)
132 {
133 it = transparent_chunks.erase(it);
134 }
135 else
136 {
137 ++it;
138 }
139 }
140 }
141 }
143 {
145 std::lock_guard<std::mutex> lock(entities_mutex);
146 for (auto it = entities.begin(); it != entities.end();)
147 {
148 it->second->Update();
149 if (it->second->GetNumFace() == 0)
150 {
151 it = entities.erase(it);
152 }
153 else
154 {
155 ++it;
156 }
157 }
158 }
159 }
160
161 void WorldRenderer::UpdateChunk(const int x_, const int z_, const std::optional<Botcraft::Chunk>& chunk)
162 {
163 // Remove any previous version of this chunk
164 {
165 std::lock_guard<std::mutex> lock(chunks_mutex);
166
167 for (auto it = chunks.begin(); it != chunks.end(); ++it)
168 {
169 if (it->first.x == x_ && it->first.z == z_)
170 {
171 it->second->ClearFaces();
172 }
173 }
174 }
175 {
176 std::lock_guard<std::mutex> lock(transparent_chunks_mutex);
177
178 for (auto it = transparent_chunks.begin(); it != transparent_chunks.end(); ++it)
179 {
180 if (it->first.x == x_ && it->first.z == z_)
181 {
182 it->second->ClearFaces();
183 }
184 }
185 }
186
187 if (!chunk.has_value())
188 {
190 return;
191 }
192
193 // For each block in the chunk, check its neighbours
194 // to see which face to draw
195 const std::vector<Position> neighbour_positions({ Position(0, -1, 0), Position(0, 0, -1),
196 Position(-1, 0, 0), Position(1, 0, 0), Position(0, 0, 1), Position(0, 1, 0) });
197
198 std::vector<const Blockstate*> neighbour_blockstates(6);
199
200 Position pos;
201 for (int y = chunk->GetMinY(); y < chunk->GetHeight() + chunk->GetMinY(); ++y)
202 {
203 pos.y = y;
204 for (int z = 0; z < CHUNK_WIDTH; ++z)
205 {
206 pos.z = z;
207 for (int x = 0; x < CHUNK_WIDTH; ++x)
208 {
209 pos.x = x;
210
211 // If this block is air, just skip it
212 const Blockstate* this_block = chunk->GetBlock(pos);
213 if (this_block == nullptr || this_block->IsAir())
214 {
215 continue;
216 }
217
218 // Else check its neighbours to find which face to draw
219 for (int i = 0; i < 6; ++i)
220 {
221 neighbour_blockstates[i] = chunk->GetBlock(pos + neighbour_positions[i]);
222 }
223
224 bool is_surrounded_by_opaque = false;
225 //Check if this block is sourrounded by non transparent blocks
226 for (int i = 0; i < neighbour_positions.size(); ++i)
227 {
228 if (neighbour_blockstates[i] == nullptr ||
229 neighbour_blockstates[i]->IsTransparent())
230 {
231 break;
232 }
233
234 //If we are here, all the neigbhours are non transparent blocks, so
235 //there is no face to add to the renderer
236 if (i == neighbour_positions.size() - 1)
237 {
238 is_surrounded_by_opaque = true;
239 }
240 }
241
242 if (is_surrounded_by_opaque)
243 {
244 continue;
245 }
246
247 //Add all faces of the current state
248 const Position block_pos(
249 pos.x + CHUNK_WIDTH * x_,
250 pos.y,
251 pos.z + CHUNK_WIDTH * z_
252 );
253 const std::vector<FaceDescriptor>& current_faces = this_block->GetModel(this_block->GetModelId(block_pos)).GetFaces();
254 const Vector3<double> offset = this_block->GetHorizontalOffsetAtPos(block_pos);
255#if PROTOCOL_VERSION < 552 /* < 1.15 */
256 const Biome* current_biome = chunk->GetBiome(x, z);
257#else
258 const Biome* current_biome = chunk->GetBiome(x, y, z);
259#endif
260
261 for (int i = 0; i < current_faces.size(); ++i)
262 {
263 //Check if the neighbour in this direction is hidding this face
264 // We also remove the faces between two transparent blocks with the same names
265 // (example: faces between two water blocks)
266 if (current_faces[i].cullface_direction == Orientation::None ||
267 !neighbour_blockstates[static_cast<int>(current_faces[i].cullface_direction)] ||
268 (neighbour_blockstates[static_cast<int>(current_faces[i].cullface_direction)]->IsTransparent() &&
269 neighbour_blockstates[static_cast<int>(current_faces[i].cullface_direction)]->GetName() != this_block->GetName())
270 )
271 {
272 AddFace(block_pos, offset,
273 current_faces[i].face, current_faces[i].texture_names,
274 GetColorModifier(pos.y, current_biome, this_block, current_faces[i].use_tintindexes));
275 }
276 }
277 }
278 }
279 }
281 }
282
283 void WorldRenderer::UpdateEntity(const int id, const std::vector<Face>& faces)
284 {
285 std::lock_guard<std::mutex> lock(entities_mutex);
286
287 auto it = entities.find(id);
288
289 if (it == entities.end() && faces.size() != 0)
290 {
291 entities[id] = std::make_shared<Entity>(faces);
293 }
294 else if (it != entities.end() && faces.size() == 0)
295 {
296 it->second->ClearFaces();
298 }
299 else if (it != entities.end())
300 {
301 it->second->UpdateFaces(faces);
303 }
304 }
305
307 {
308 glBindTexture(GL_TEXTURE_2D, atlas_texture);
309 }
310
312 {
313 // We simply remove all the faces from all the chunks
314 // The chunks will be deleted on the next frame
315 {
316 std::lock_guard<std::mutex> lock(chunks_mutex);
317 for (auto it = chunks.begin(); it != chunks.end(); it++)
318 {
319 it->second->ClearFaces();
320 }
321 }
322 {
323 std::lock_guard<std::mutex> lock(transparent_chunks_mutex);
324 for (auto it = transparent_chunks.begin(); it != transparent_chunks.end(); it++)
325 {
326 it->second->ClearFaces();
327 }
328 }
329 {
330 std::lock_guard<std::mutex> lock(entities_mutex);
331 for (auto it = entities.begin(); it != entities.end(); it++)
332 {
333 it->second->ClearFaces();
334 }
335 }
338 }
339
340 void WorldRenderer::SetPosOrientation(const double x_, const double y_, const double z_, const float yaw_, const float pitch_)
341 {
342 if (camera)
343 {
344 std::lock_guard<std::mutex> lock(m_mutex_camera);
345 camera->SetPosition(
346 static_cast<float>(x_),
347 static_cast<float>(y_),
348 static_cast<float>(z_)
349 );
350 camera->SetRotation(pitch_, yaw_);
351 }
352 }
353
354 void WorldRenderer::RenderFaces(int* num_chunks_, int* num_rendered_chunks_,
355 int* num_entities_, int* num_rendered_entities_,
356 int* num_faces_, int* num_rendered_faces_)
357 {
358 const std::array<glm::vec4, 6>& frustum_planes = camera->GetFrustumPlanes();
359
360 // Get a list of all loaded chunks
361 std::unordered_set<Position> all_loaded_chunks;
362 all_loaded_chunks.reserve(chunks.size() + transparent_chunks.size());
363 int num_faces = 0;
364
365 chunks_mutex.lock();
366 for (auto it = chunks.begin(); it != chunks.end(); ++it)
367 {
368 all_loaded_chunks.insert(it->first);
369 num_faces += it->second->GetNumFace();
370 }
371 chunks_mutex.unlock();
373 for (auto it = transparent_chunks.begin(); it != transparent_chunks.end(); ++it)
374 {
375 all_loaded_chunks.insert(it->first);
376 num_faces += it->second->GetNumFace();
377 }
379 const int num_chunks = static_cast<int>(all_loaded_chunks.size());
380
381 // Apply frustum culling to render only the visible ones
382 std::vector<Position> chunks_to_render;
383 chunks_to_render.reserve(all_loaded_chunks.size());
384 std::unordered_map<Position, bool> inside_frustum;
385 // Frustum culling algorithm from http://old.cescg.org/CESCG-2002/DSykoraJJelinek/
386 for (auto it = all_loaded_chunks.begin(); it != all_loaded_chunks.end(); ++it)
387 {
389
390 const float min_x = static_cast<float>(CHUNK_WIDTH * (*it).x);
391 const float max_x = static_cast<float>(CHUNK_WIDTH * ((*it).x + 1));
392 const float min_y = static_cast<float>(static_cast<int>(section_height) * (*it).y);
393 const float max_y = static_cast<float>(static_cast<int>(section_height) * ((*it).y + 1));
394 const float min_z = static_cast<float>(CHUNK_WIDTH * (*it).z);
395 const float max_z = static_cast<float>(CHUNK_WIDTH * ((*it).z + 1));
396
397 for (int i = 0; i < 6; ++i)
398 {
399 const bool sign_x = frustum_planes[i].x > 0.0f;
400 const bool sign_y = frustum_planes[i].y > 0.0f;
401 const bool sign_z = frustum_planes[i].z > 0.0f;
402
403 const glm::vec4 p_vertex = glm::vec4(sign_x ? max_x : min_x, sign_y ? max_y : min_y, sign_z ? max_z : min_z, 1.0f);
404
405 if (glm::dot(frustum_planes[i], p_vertex) < 0.0f)
406 {
407 result = FrustumResult::Outside;
408 break;
409 }
410
411 const glm::vec4 n_vertex = glm::vec4(sign_x ? min_x : max_x, sign_y ? min_y : max_y, sign_z ? min_z : max_z, 1.0f);
412
413 if (glm::dot(frustum_planes[i], n_vertex) < 0.0f)
414 {
416 }
417 }
418
419 inside_frustum[*it] = result != FrustumResult::Outside;
420 if (result != FrustumResult::Outside)
421 {
422 chunks_to_render.push_back(*it);
423 }
424 }
425
426 // Sort the chunks from far to near the camera (necessary for transparent chunks to render in the right order)
427 m_mutex_camera.lock();
428 std::sort(chunks_to_render.begin(), chunks_to_render.end(), [this](const Position& p1, const Position& p2) {return this->DistanceToCamera(p1) > this->DistanceToCamera(p2); });
429 m_mutex_camera.unlock();
430
431 // Render all non partially transparent faces
432 const int num_rendered_chunks = static_cast<int>(chunks_to_render.size());
433 int num_rendered_faces = 0;
434 chunks_mutex.lock();
435 for (int i = 0; i < num_rendered_chunks; ++i)
436 {
437 auto found_it = chunks.find(chunks_to_render[i]);
438 if (found_it != chunks.end())
439 {
440 found_it->second->Render();
441 num_rendered_faces += found_it->second->GetNumFace();
442 }
443 }
444 chunks_mutex.unlock();
445
446 // Render entities, with approximate frustum culling
447 const std::vector<Position> neighbouring_positions({ Position(0, 0, 0), Position(0, -1, 0), Position(0, 0, -1),
448 Position(-1, 0, 0), Position(1, 0, 0), Position(0, 0, 1), Position(0, 1, 0) });
449 entities_mutex.lock();
450 const int num_entities = static_cast<int>(entities.size());
451 int num_rendered_entities = 0;
452 for (auto& e : entities)
453 {
454 num_faces += e.second->GetNumFace();
455
456 const Vector3<float> approx_pos = e.second->GetApproxPos();
457 const Position chunk_position(
458 static_cast<int>(floor(approx_pos.x / static_cast<double>(CHUNK_WIDTH))),
459 static_cast<int>(floor(approx_pos.y / static_cast<double>(section_height))),
460 static_cast<int>(floor(approx_pos.z / static_cast<double>(CHUNK_WIDTH)))
461 );
462
463 for (int i = 0; i < neighbouring_positions.size(); ++i)
464 {
465 if (inside_frustum[chunk_position + neighbouring_positions[i]])
466 {
467 e.second->Render();
468 num_rendered_entities += 1;
469 num_rendered_faces += e.second->GetNumFace();
470 break;
471 }
472 }
473 }
474 entities_mutex.unlock();
475
476 m_mutex_camera.lock();
477 const glm::vec3 cam_pos = camera->GetPosition();
478 m_mutex_camera.unlock();
479
480 // Render all partially transparent faces
482 for (int i = 0; i < num_rendered_chunks; ++i)
483 {
484 auto found_it = transparent_chunks.find(chunks_to_render[i]);
485 if (found_it != transparent_chunks.end())
486 {
487 found_it->second->Sort(cam_pos);
488 found_it->second->Render();
489 num_rendered_faces += found_it->second->GetNumFace();
490 }
491 }
493
494 if (num_chunks_)
495 {
496 *num_chunks_ = num_chunks;
497 }
498 if (num_rendered_chunks_)
499 {
500 *num_rendered_chunks_ = num_rendered_chunks;
501 }
502 if (num_faces_)
503 {
504 *num_faces_ = num_faces;
505 }
506 if (num_rendered_faces_)
507 {
508 *num_rendered_faces_ = num_rendered_faces;
509 }
510 if (num_entities_)
511 {
512 *num_entities_ = num_entities;
513 }
514 if (num_rendered_entities_)
515 {
516 *num_rendered_entities_ = num_rendered_entities;
517 }
518 }
519
520 void WorldRenderer::AddFace(const Position& block_pos, const Vector3<double>& offset, const Face& face_, const std::vector<std::string>& texture_identifiers_, const std::vector<unsigned int>& texture_multipliers_)
521 {
522 std::array<unsigned int, 2> texture_multipliers = { 0xFFFFFFFF, 0xFFFFFFFF };
523 for (int i = 0; i < std::min(2, static_cast<int>(texture_multipliers_.size())); ++i)
524 {
525 texture_multipliers[i] = texture_multipliers_[i];
526 }
527
528 const Position chunk_position(
529 static_cast<int>(floor(block_pos.x / static_cast<double>(CHUNK_WIDTH))),
530 static_cast<int>(floor(block_pos.y / static_cast<double>(section_height))),
531 static_cast<int>(floor(block_pos.z / static_cast<double>(CHUNK_WIDTH)))
532 );
533
535 {
536 std::lock_guard<std::mutex> lock(transparent_chunks_mutex);
537 auto chunk_it = transparent_chunks.find(chunk_position);
538 if (chunk_it == transparent_chunks.end())
539 {
540 transparent_chunks[chunk_position] = std::make_shared<TransparentChunk>();
541 }
542 //Add 0.5 because the origin of the block is at the center
543 //but the coordinates start from the block corner
544 transparent_chunks[chunk_position]->AddFace(face_, texture_multipliers, offset.x + 0.5f, offset.y + 0.5f, offset.z + 0.5f);
545 }
546 else
547 {
548 std::lock_guard<std::mutex> lock(chunks_mutex);
549 auto chunk_it = chunks.find(chunk_position);
550 if (chunk_it == chunks.end())
551 {
552 chunks[chunk_position] = std::make_shared<Chunk>();
553 }
554 chunks[chunk_position]->AddFace(face_, texture_multipliers, offset.x + 0.5f, offset.y + 0.5f, offset.z + 0.5f);
555 }
556 }
557
558 const std::vector<unsigned int> WorldRenderer::GetColorModifier(const int y, const Biome* biome, const Blockstate* blockstate, const std::vector<bool>& use_tintindex) const
559 {
560 std::vector<unsigned int> texture_modifier(use_tintindex.size(), 0xFFFFFFFF);
561 for (int i = 0; i < use_tintindex.size(); ++i)
562 {
563 switch (blockstate->GetTintType())
564 {
565 case TintType::None:
566 break;
567 case TintType::Grass:
568 if (use_tintindex[i])
569 {
570 if (biome)
571 {
572 texture_modifier[i] = biome->GetColorMultiplier(y, true);
573 }
574 }
575 break;
576 case TintType::Leaves:
577 if (use_tintindex[i])
578 {
579 if (biome)
580 {
581 texture_modifier[i] = biome->GetColorMultiplier(y, false);
582 }
583 }
584 break;
585 // Black when signal strength is 0 and red when it's 15
587#if PROTOCOL_VERSION == 340 /* 1.12.2 */
588 texture_modifier[i] = 0xFF000000 | (25 + 15 * blockstate->GetId().second);
589#else
590 texture_modifier[i] = 0xFF000000 | (25 + 15 * std::stoi(blockstate->GetVariableValue("power")));
591#endif
592 break;
593 case TintType::Water:
594 if (biome)
595 {
596 texture_modifier[i] = biome->GetWaterColorMultiplier();
597 }
598 break;
599 default:
600 break;
601 }
602 }
603 return texture_modifier;
604 }
605
606 const float WorldRenderer::DistanceToCamera(const Position& chunk) const
607 {
608 return camera->GetDistance(CHUNK_WIDTH * (chunk.x + 0.5f), section_height * (chunk.y + 0.5f), CHUNK_WIDTH * (chunk.z + 0.5f));
609 }
610 } // Renderer
611} // Botcraft
const Renderer::Atlas * GetAtlas() const
static AssetsManager & getInstance()
unsigned int GetWaterColorMultiplier() const
Definition Biome.cpp:91
unsigned int GetColorMultiplier(const int height, const bool is_grass) const
Definition Biome.cpp:40
const std::string & GetName() const
BlockstateId GetId() const
unsigned char GetModelId(const Position &pos) const
TintType GetTintType() const
Vector3< double > GetHorizontalOffsetAtPos(const Position &pos) const
const std::string & GetVariableValue(const std::string &variable) const
const Model & GetModel(const unsigned short index) const
const std::vector< FaceDescriptor > & GetFaces() const
Definition Model.cpp:601
const unsigned char * Get(const int row=0, const int col=0, const int depth=0) const
Definition Atlas.cpp:248
Transparency GetTransparencyData() const
Definition Face.cpp:142
WorldRenderer(const unsigned int section_height_)
std::shared_ptr< Camera > camera
void AddFace(const Position &block_pos, const Vector3< double > &offset, const Face &face_, const std::vector< std::string > &texture_identifiers_, const std::vector< unsigned int > &texture_multipliers_)
void UpdateChunk(const int x_, const int z_, const std::optional< Botcraft::Chunk > &chunk)
const float DistanceToCamera(const Position &chunk) const
void SetPosOrientation(const double x_, const double y_, const double z_, const float yaw_, const float pitch_)
std::shared_ptr< Camera > GetCamera()
std::unordered_map< int, std::shared_ptr< Entity > > entities
std::unordered_map< Position, std::shared_ptr< TransparentChunk > > transparent_chunks
const std::vector< unsigned int > GetColorModifier(const int y, const Biome *biome, const Blockstate *blockstate, const std::vector< bool > &use_tintindex) const
std::unordered_map< Position, std::shared_ptr< Chunk > > chunks
void UpdateEntity(const int id, const std::vector< Face > &faces)
void SetCameraProjection(const glm::mat4 &proj)
void RenderFaces(int *num_chunks_=nullptr, int *num_rendered_chunks_=nullptr, int *num_entities_=nullptr, int *num_rendered_entities_=nullptr, int *num_faces_=nullptr, int *num_rendered_faces_=nullptr)
static constexpr int CHUNK_WIDTH
Definition Chunk.hpp:21
Vector3< int > Position
Definition Vector3.hpp:282