#include "World.hpp" #include "Generation/ChunkMeshing.hpp" namespace MC::World { std::vector World::get_visible_chunks(Vector<3> position) { auto finished_chunks = load_finished_chunks_from_queue(); auto visible_chunks = get_visible_chunk_indices(position); auto updates = visible_chunks; for (auto index : m_visible_chunks) { updates.erase(index); } for (auto index : finished_chunks) { updates.insert(index); } if (!updates.empty()) { process_chunk_visibility_updates(updates, position); m_visible_chunks = visible_chunks; } std::vector chunks{}; chunks.reserve(visible_chunks.size()); for (auto index : visible_chunks) { auto& data = get(index); if (data.status == ChunkStatus::NeedsMesh) { try_to_create_mesh_for_chunk(data); } if (data.status == ChunkStatus::Done) { chunks.push_back(&data); } } return chunks; } Chunk* World::get_chunk_for_position(Vector<3> position) { I32 x = std::round(position.x() / Chunk::Width); I32 y = std::round(position.z() / Chunk::Width); auto& data = get({x, y}); if (data.chunk.has_value()) { return &data.chunk.value(); } return nullptr; } void World::process_chunk_visibility_updates(const std::unordered_set& new_chunks, const Vector<3> player) { for (auto new_index: new_chunks) { auto& data = get(new_index); if (data.status == ChunkStatus::Empty) { request_generation(new_index, player.distance(new_index.middle())); data.status = ChunkStatus::WaitingForGeneration; } } } std::unordered_set World::get_visible_chunk_indices(const Vector<3> position) const { I32 center_x = std::round(position.x() / Chunk::Width); I32 center_y = std::round(position.z() / Chunk::Width); std::unordered_set indices{}; indices.reserve(m_view_distance_radius * m_view_distance_radius * 4); auto radius = m_view_distance_radius; for (I32 x = -radius; x <= radius; x++) { I32 height = std::round(std::sqrt(radius * radius - x * x) + 0.5); for (I32 y = -height; y <= height; y++) { indices.emplace(x + center_x, y + center_y); } } return indices; } std::unordered_set World::load_finished_chunks_from_queue() { std::unordered_set indices; auto results = m_queue.done(); for (auto& [id, res] : results) { get(id) = {id, ChunkStatus::NeedsMesh, {res.chunk}}; indices.insert(id); log_chunk_time(res.generation_duration); } return indices; } void World::request_generation(ChunkIndex index, Real priority) { m_queue.add(index, priority, [=]() -> GenerationResult { auto start = timestamp(); auto chunk = m_generator.generate(index.x, index.y); return {chunk, timestamp() - start}; }); } World::ChunkData& World::get(ChunkIndex index) { auto entry = m_chunks.find(index); if (entry == m_chunks.end()) { ChunkData data{index, ChunkStatus::Empty}; m_chunks.insert({index, std::move(data)}); return m_chunks.at(index); } return entry->second; } U64 World::timestamp() { auto time = std::chrono::system_clock::now().time_since_epoch(); auto ms = std::chrono::duration_cast(time); return ms.count(); } void World::try_to_create_mesh_for_chunk(ChunkData& data) { auto index = data.index; UInt neighbor_index = 0; std::array neighbors; for (I32 x = -1; x <= 1; x++) { for (I32 y = -1; y <= 1; y++) { if (x == 0 && y == 0) continue; auto& neighbor_data = get({index.x + x, index.y + y}); if (!neighbor_data.chunk.has_value()) return; // All neighbors need to be generated first. neighbors[neighbor_index++] = &neighbor_data.chunk.value(); } } // Layout of neighboring chunks in `neighbors` array: // (-1; -1) > (-1; 0) > (-1; 1) > (0; -1) // ( 0; 1) > ( 1; -1) > ( 1; 0) > (1; 1) Generation::ChunkMeshing::ChunkNeighbors chunk_neighbors { neighbors[3], neighbors[6], neighbors[4], neighbors[1], neighbors[5], neighbors[7], neighbors[2], neighbors[0], }; data.mesh_data = create_mesh_for_chunk(data.chunk.value(), chunk_neighbors); data.mesh = GFX::Binder::load(data.mesh_data.value()); data.status = ChunkStatus::Done; } void World::log_chunk_time(U64 chunk_time_ms) { m_statistics.chunk_time_sample_count++; m_statistics.average_chunk_time_ms += ((Real)chunk_time_ms - m_statistics.average_chunk_time_ms) / m_statistics.chunk_time_sample_count; } Real World::get_average_chunk_time() const { return m_statistics.average_chunk_time_ms; } }