#include "World.hpp" #include "Generation/ChunkMeshing.hpp" #include "Generation/Lighting.hpp" #include "../Time.hpp" namespace MC::World { std::vector World::get_visible_chunks(Time const& time, Position::World position) { process_chunk_updates(time); if (should_reassess_priorities(position)) { reassess_priorities(position); } auto visible_chunks = get_visible_chunk_indices(position); std::vector chunks{}; chunks.reserve(visible_chunks.size()); for (auto index : visible_chunks) { auto& data = m_registry.get(index); if (data.get_status() == ChunkRegistry::Status::Empty) { request_generation(index, calculate_priority(index, position, RequestType::Initial)); data.status = ChunkRegistry::Status::WaitingForGeneration; continue; } if (data.get_status() == ChunkRegistry::Status::NeedsReification || data.get_status() == ChunkRegistry::Status::Damaged) { auto do_all_exist = Generation::find_chunk_neighbors(index, m_registry).all_exist(); if (m_reification_queue.size() <= 50 && do_all_exist) { auto request_type = data.get_status() == ChunkRegistry::Status::Damaged ? RequestType::Update : RequestType::Initial; request_reification(index, calculate_priority(index, position, request_type)); data.status = ChunkRegistry::Status::WaitingForReification; } } // TODO: Use a better indicator than `land_mesh.has_value()`. if (data.land_mesh.has_value()) chunks.push_back(&data); } return chunks; } Chunk::BlockData World::block_at(Position::BlockWorld pos) { auto data = m_registry.find(pos); if (data && data->chunk.has_value()) return data->chunk->at(pos.to_local()); return {}; } void World::set_block(Position::BlockWorld pos, BlockType type) { auto chunk_data = m_registry.find(pos); if (!chunk_data || !chunk_data->chunk.has_value()) return; auto& block_data = chunk_data->chunk->at(pos.to_local()); block_data.type = type; chunk_data->damage(); } void World::break_block(Position::BlockWorld pos) { if (!block_at(pos).type.is_solid()) return; set_block(pos, BlockType::Air); } std::vector World::get_visible_chunk_indices(const Position::World position) const { ChunkIndex center = ChunkIndex::from_position(position.round_to_block()); std::vector 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++) { // Needed so that this function and is_chunk_in_radius forcibly agree. if (!is_chunk_in_radius(position, {x + center.x, y + center.y})) continue; indices.emplace_back(x + center.x, y + center.y); } } auto cmp = [=](const ChunkIndex& a, const ChunkIndex& b) -> bool { return position.distance(a.middle()) > position.distance(b.middle()); }; std::sort(indices.begin(), indices.end(), cmp); return indices; } void World::process_chunk_updates(Time const& time) { auto generation_results = m_generation_queue.done(); for (auto& [id, res] : generation_results) { m_registry.get(id) = { id, ChunkRegistry::Status::NeedsReification, time.tick(), {res.chunk} }; log_chunk_time(res.generation_duration); } auto reification_results = m_reification_queue.done(); for (auto& [id, res] : reification_results) { auto& data = m_registry.get(id); data.status = ChunkRegistry::Status::Done; data.land_mesh = res.chunk_mesh.land_mesh; data.water_mesh = res.chunk_mesh.water_mesh; // TODO: Damage surrounding chunks. } } void World::request_generation(ChunkIndex index, Real priority) { m_generation_queue.add(index, priority, [=]() -> GenerationResult { auto start = Time::now(); auto chunk = m_generator.generate(index.x, index.y); return {chunk, Time::now() - start}; }); } void World::request_reification(ChunkIndex index, Real priority) { auto& data = m_registry.get(index); auto& chunk = data.chunk.value(); auto neighbors = Generation::find_chunk_neighbors(index, m_registry); auto meshing_context = Generation::ChunkMeshing::create_meshing_context(data.chunk.value(), neighbors); m_reification_queue.add(index, priority, [=]() mutable -> ReificationResult { Generation::Lighting intitial_lighting{}; intitial_lighting.add_chunk(chunk); intitial_lighting.illuminate(chunk); return {Generation::ChunkMeshing::mesh_chunk(chunk, meshing_context)}; }); } Bool World::should_reassess_priorities(Position::World player_position) const { return player_position.distance(m_last_priority_reassession_at) > m_priority_reassession_distance_threshold; } void World::reassess_priorities(Position::World player_position) { m_last_priority_reassession_at = player_position; // TODO: How do we know a chunk has been requested as an update? m_generation_queue.reassess([&](ChunkIndex id) -> GenerationQueue::Reassession { if (!is_chunk_in_radius(player_position, id)) { m_registry.get(id).status = ChunkRegistry::Status::Empty; return {GenerationQueue::Reassession::Cancel}; } return { GenerationQueue::Reassession::Reassess, calculate_priority(id, player_position, RequestType::Initial) }; }); m_reification_queue.reassess([&](ChunkIndex id) -> ReificationQueue::Reassession { if (!is_chunk_in_radius(player_position, id)) { m_registry.get(id).status = ChunkRegistry::Status::NeedsReification; return {ReificationQueue::Reassession::Cancel}; } return { ReificationQueue::Reassession::Reassess, calculate_priority(id, player_position, RequestType::Initial) }; }); } Real World::calculate_priority(ChunkIndex chunk, Position::World player_position, RequestType request_type) { auto chunk_position = chunk.middle(); auto flat_distance = Position::World{player_position.x(), chunk_position.y(), player_position.z()}.distance(chunk_position); Real request_type_penalty; switch (request_type) { case RequestType::Initial: request_type_penalty = 0; break; case RequestType::Update: request_type_penalty = 500; break; } return flat_distance + request_type_penalty; } 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; } Bool World::is_chunk_in_radius(Position::World position, ChunkIndex chunk) const { Real max_distance = (m_view_distance_radius + 2) * Chunk::Width; // TODO: Tune this so that m_view_distance_radius represents the exact radius. auto chunk_position = chunk.middle(); // TODO: Create a flat() method in Position::World for things like this. See also: priorities. Position::World flat_position{position.x(), chunk_position.y(), position.z()}; return chunk_position.distance(flat_position) < max_distance; } }