#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; auto north = get({index.x, index.y - 1}); auto east = get({index.x + 1, index.y}); auto south = get({index.x, index.y + 1}); auto west = get({index.x - 1, index.y}); auto no_terrain = [](const ChunkData& d){ return !d.chunk.has_value(); }; if (no_terrain(north) || no_terrain(east) || no_terrain(south) || no_terrain(west)) { return; } data.mesh_data = Generation::ChunkMeshing::create_mesh_for_chunk( data.chunk.value(), {north.chunk.value(), east.chunk.value(), south.chunk.value(), west.chunk.value()} ); 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; } }