#include "Decorator.hpp" #include "ParticlesRenderer.hpp" #include "WorldRenderer.hpp" #include "TextsRenderer.hpp" #include "TextNote.hpp" #include "assets/Assets.hpp" #include "assets/assets_util.hpp" #include "content/Content.hpp" #include "voxels/Chunks.hpp" #include "voxels/Chunk.hpp" #include "voxels/Block.hpp" #include "world/Level.hpp" #include "window/Camera.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" #include "objects/Entities.hpp" #include "logic/LevelController.hpp" #include "util/stringutil.hpp" #include "engine/Engine.hpp" #include "io/io.hpp" #include "audio/audio.hpp" #include "maths/util.hpp" namespace fs = std::filesystem; /// @brief Not greather than 64 for this BIG_PRIME value inline constexpr int UPDATE_AREA_DIAMETER = 32; /// @brief Number of blocks in the volume inline constexpr int UPDATE_BLOCKS = UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER; /// @brief Number of update iterations inline constexpr int ITERATIONS = 512; /// @brief Big prime number used for pseudo-random 3d array iteration inline constexpr int BIG_PRIME = 666667; Decorator::Decorator( Engine& engine, LevelController& controller, WorldRenderer& renderer, const Assets& assets, Player& player ) : level(*controller.getLevel()), assets(assets), player(player), renderer(renderer) { controller.getBlocksController()->listenBlockInteraction( [this](auto player, const auto& pos, const auto& def, BlockInteraction type) { if (type == BlockInteraction::placing && def.particles) { addParticles(def, pos); } }); for (const auto& [id, player] : *level.players) { if (id == this->player.getId()) { continue; } playerTexts[id] = renderer.texts->add(std::make_unique( util::str2wstr_utf8(player->getName()), playerNamePreset, player->getPosition() )); } playerNamePreset.deserialize(engine.getResPaths().readCombinedObject( "presets/text3d/player_name.toml" )); } void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { const auto& found = blockEmitters.find(pos); if (found == blockEmitters.end()) { auto treg = util::get_texture_region( assets, def.particles->texture, "" ); blockEmitters[pos] = renderer.particles->add(std::make_unique( level, glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5}, *def.particles, treg.texture, treg.region, -1 )); } } void Decorator::updateRandom( float delta, const glm::ivec3& areaCenter, const WeatherPreset& weather ) { util::PseudoRandom random(rand()); const auto& chunks = *player.chunks; const auto& indices = *level.content.getIndices(); const auto& rainSplash = weather.fall.splash; auto pos = areaCenter + glm::ivec3( random.rand32() % 12, random.rand32() % 12, random.rand32() % 12 ); auto vox = chunks.get(pos); auto chunk = chunks.getChunkByVoxel(pos); if (vox == nullptr || chunk == nullptr) { return; } const auto& def = indices.blocks.require(vox->id); auto dst2 = util::distance2(pos, areaCenter); if (!def.obstacle || dst2 >= 256 || weather.fall.noise.empty()) { return; } for (int y = pos.y + 1; y < chunk->top; y++) { if (indices.blocks.require(chunks.get(pos.x, y, pos.z)->id).obstacle) { return; } } float intensity = weather.intensity * weather.fall.maxIntensity; if (rainSplash.has_value() && dst2 < 128 && random.randFloat() < glm::pow(intensity, 2.0f)) { auto treg = util::get_texture_region( assets, "particles:rain_splash_0", "" ); renderer.particles->add(std::make_unique( level, glm::vec3 { pos.x + random.randFloat(), pos.y + 1.1, pos.z + random.randFloat()}, *rainSplash, treg.texture, treg.region, 2 )); } if (random.rand() % 200 < 3 && pos.y < areaCenter.y + 1) { auto sound = assets.get(weather.fall.noise); audio::play( sound, pos, false, intensity * intensity, 1.0f, false, audio::PRIORITY_LOW, audio::get_channel_index("ambient") ); } } void Decorator::update( float delta, const glm::ivec3& areaStart, const glm::ivec3& areaCenter ) { int index = currentIndex; currentIndex = (currentIndex + BIG_PRIME) % UPDATE_BLOCKS; const auto& chunks = *player.chunks; const auto& indices = *level.content.getIndices(); int lx = index % UPDATE_AREA_DIAMETER; int lz = (index / UPDATE_AREA_DIAMETER) % UPDATE_AREA_DIAMETER; int ly = (index / UPDATE_AREA_DIAMETER / UPDATE_AREA_DIAMETER); glm::ivec3 offset {lx, ly, lz}; auto pos = areaStart + offset; if (auto vox = chunks.get(pos)) { const auto& def = indices.blocks.require(vox->id); if (def.particles) { addParticles(def, pos); } } } void Decorator::updateBlockEmitters(const Camera& camera) { const auto& chunks = *player.chunks; const auto& indices = *level.content.getIndices(); auto iter = blockEmitters.begin(); while (iter != blockEmitters.end()) { auto emitter = renderer.particles->getEmitter(iter->second); if (emitter == nullptr) { iter = blockEmitters.erase(iter); continue; } bool remove = false; if (auto vox = chunks.get(iter->first)) { const auto& def = indices.blocks.require(vox->id); if (def.particles == nullptr) { remove = true; } } else { iter = blockEmitters.erase(iter); continue; } if (util::distance2(iter->first, glm::ivec3(camera.position)) > UPDATE_AREA_DIAMETER * UPDATE_AREA_DIAMETER) { remove = true; } if (remove) { emitter->stop(); iter = blockEmitters.erase(iter); continue; } iter++; } } void Decorator::updateTextNotes() { for (const auto& [id, player] : *level.players) { if (id == this->player.getId() || playerTexts.find(id) != playerTexts.end()) { continue; } playerTexts[id] = renderer.texts->add(std::make_unique( util::str2wstr_utf8(player->getName()), playerNamePreset, player->getPosition() )); } auto textsIter = playerTexts.begin(); while (textsIter != playerTexts.end()) { auto note = renderer.texts->get(textsIter->second); auto player = level.players->get(textsIter->first); if (player == nullptr) { renderer.texts->remove(textsIter->second); textsIter = playerTexts.erase(textsIter); } else { glm::vec3 position = player->getPosition(); if (auto entity = level.entities->get(player->getEntity())) { position = entity->getInterpolatedPosition(); } note->setPosition(position + glm::vec3(0, 1, 0)); ++textsIter; } } } void Decorator::updateRandomSounds(float delta, const Weather& weather) { thunderTimer += delta; util::PseudoRandom random(rand()); if (thunderTimer >= 1.0f) { thunderTimer = 0.0f; if (random.randFloat() < weather.thunderRate()) { audio::play( assets.get("ambient/thunder"), glm::vec3(), false, 1.0f, 1.0f + random.randFloat() - 0.5f, false, audio::PRIORITY_NORMAL, audio::get_channel_index("ambient") ); } } } void Decorator::update( float delta, const Camera& camera, const Weather& weather ) { updateRandomSounds(delta, weather); glm::ivec3 pos = camera.position; for (int i = 0; i < ITERATIONS; i++) { update(delta, pos - glm::ivec3(UPDATE_AREA_DIAMETER / 2), pos); } int randIters = std::min(50'000, static_cast(delta * 24'000)); for (int i = 0; i < randIters; i++) { if (weather.a.intensity > 1.e-3f) { updateRandom(delta, pos, weather.a); } if (weather.b.intensity > 1.e-3f) { updateRandom(delta, pos, weather.b); } } updateBlockEmitters(camera); updateTextNotes(); }