diff --git a/res/content/base/preload.json b/res/content/base/preload.json index 56b9896e..760ad2c9 100644 --- a/res/content/base/preload.json +++ b/res/content/base/preload.json @@ -5,7 +5,9 @@ "sounds": [ "blocks/door_open", "blocks/door_close", - "events/pickup" + "events/pickup", + "ambient/rain", + "ambient/thunder" ], "models": [ "drop-item" diff --git a/res/content/base/sounds/ambient/rain_0.ogg b/res/content/base/sounds/ambient/rain_0.ogg new file mode 100644 index 00000000..5a870d16 Binary files /dev/null and b/res/content/base/sounds/ambient/rain_0.ogg differ diff --git a/res/content/base/sounds/ambient/rain_1.ogg b/res/content/base/sounds/ambient/rain_1.ogg new file mode 100644 index 00000000..56ee339c Binary files /dev/null and b/res/content/base/sounds/ambient/rain_1.ogg differ diff --git a/res/content/base/sounds/ambient/rain_2.ogg b/res/content/base/sounds/ambient/rain_2.ogg new file mode 100644 index 00000000..deb63a93 Binary files /dev/null and b/res/content/base/sounds/ambient/rain_2.ogg differ diff --git a/res/content/base/sounds/ambient/rain_3.ogg b/res/content/base/sounds/ambient/rain_3.ogg new file mode 100644 index 00000000..9e354751 Binary files /dev/null and b/res/content/base/sounds/ambient/rain_3.ogg differ diff --git a/res/content/base/sounds/ambient/rain_4.ogg b/res/content/base/sounds/ambient/rain_4.ogg new file mode 100644 index 00000000..8c28dafb Binary files /dev/null and b/res/content/base/sounds/ambient/rain_4.ogg differ diff --git a/res/content/base/sounds/ambient/thunder_0.ogg b/res/content/base/sounds/ambient/thunder_0.ogg new file mode 100644 index 00000000..b36f7174 Binary files /dev/null and b/res/content/base/sounds/ambient/thunder_0.ogg differ diff --git a/res/content/base/sounds/ambient/thunder_1.ogg b/res/content/base/sounds/ambient/thunder_1.ogg new file mode 100644 index 00000000..f2e52bf9 Binary files /dev/null and b/res/content/base/sounds/ambient/thunder_1.ogg differ diff --git a/res/content/base/sounds/ambient/thunder_2.ogg b/res/content/base/sounds/ambient/thunder_2.ogg new file mode 100644 index 00000000..d10b777d Binary files /dev/null and b/res/content/base/sounds/ambient/thunder_2.ogg differ diff --git a/res/content/base/textures/particles/rain_splash_0.png b/res/content/base/textures/particles/rain_splash_0.png new file mode 100644 index 00000000..9aacb072 Binary files /dev/null and b/res/content/base/textures/particles/rain_splash_0.png differ diff --git a/res/content/base/textures/particles/rain_splash_1.png b/res/content/base/textures/particles/rain_splash_1.png new file mode 100644 index 00000000..a15f7abb Binary files /dev/null and b/res/content/base/textures/particles/rain_splash_1.png differ diff --git a/res/content/base/textures/particles/rain_splash_2.png b/res/content/base/textures/particles/rain_splash_2.png new file mode 100644 index 00000000..86c2b4a0 Binary files /dev/null and b/res/content/base/textures/particles/rain_splash_2.png differ diff --git a/res/preload.json b/res/preload.json index e2369e0c..c4e60cad 100644 --- a/res/preload.json +++ b/res/preload.json @@ -20,6 +20,8 @@ "gui/refresh", "gui/folder_icon", "gui/settings_icon", + "misc/rain", + "misc/snow", "gui/check_mark", "gui/left_arrow", "gui/right_arrow" diff --git a/res/presets/weather/clear.json b/res/presets/weather/clear.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/res/presets/weather/clear.json @@ -0,0 +1 @@ +{} diff --git a/res/presets/weather/cloudy.json b/res/presets/weather/cloudy.json new file mode 100644 index 00000000..645b0a28 --- /dev/null +++ b/res/presets/weather/cloudy.json @@ -0,0 +1,4 @@ +{ + "clouds": 0.8 +} + diff --git a/res/presets/weather/fog.json b/res/presets/weather/fog.json new file mode 100644 index 00000000..e9c3a2b3 --- /dev/null +++ b/res/presets/weather/fog.json @@ -0,0 +1,6 @@ +{ + "fog_opacity": 0.98, + "fog_dencity": 3.6, + "fog_curve": 0.25, + "clouds": 0.8 +} diff --git a/res/presets/weather/rain.json b/res/presets/weather/rain.json new file mode 100644 index 00000000..184a80f0 --- /dev/null +++ b/res/presets/weather/rain.json @@ -0,0 +1,23 @@ +{ + "fall": { + "vspeed": 2.0, + "texture": "misc/rain", + "noise": "ambient/rain", + "splash": { + "lifetime": 0.2, + "spawn_interval": 0.0, + "size": [0.2, 0.2, 0.2], + "frames": [ + "particles:rain_splash_0", + "particles:rain_splash_1", + "particles:rain_splash_2" + ] + }, + "min_opacity": 0.8, + "max_intensity": 0.5 + }, + "fog_opacity": 0.5, + "fog_dencity": 1.7, + "fog_curve": 0.5, + "clouds": 0.9 +} diff --git a/res/presets/weather/snow.json b/res/presets/weather/snow.json new file mode 100644 index 00000000..da80a856 --- /dev/null +++ b/res/presets/weather/snow.json @@ -0,0 +1,15 @@ +{ + "fall": { + "vspeed": 0.75, + "hspeed": 0.5, + "texture": "misc/snow", + "scale": 0.35, + "opaque": true, + "min_opacity": 0.8, + "max_opacity": 2.0 + }, + "fog_opacity": 0.8, + "fog_dencity": 2.5, + "fog_curve": 0.5, + "clouds": 0.5 +} diff --git a/res/presets/weather/thunder.json b/res/presets/weather/thunder.json new file mode 100644 index 00000000..d2a107f9 --- /dev/null +++ b/res/presets/weather/thunder.json @@ -0,0 +1,23 @@ +{ + "fall": { + "vspeed": 2.0, + "texture": "misc/rain", + "noise": "ambient/rain", + "splash": { + "lifetime": 0.2, + "spawn_interval": 0.0, + "size": [0.2, 0.2, 0.2], + "frames": [ + "particles:rain_splash_0", + "particles:rain_splash_1", + "particles:rain_splash_2" + ] + }, + "min_opacity": 0.8 + }, + "fog_opacity": 0.8, + "fog_dencity": 2.0, + "fog_curve": 0.5, + "clouds": 0.7, + "thunder_rate": 0.1 +} diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index 95b37007..c06f059a 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -264,6 +264,33 @@ console.add_command( end ) +console.add_command( + "weather.set name:str time:num=1", + "Change weather", + function (args, kwargs) + local filename = file.find("presets/weather/"..args[1]..".json") + if not filename then + return "weather preset not found" + end + local preset = json.parse(file.read(filename)) + gfx.weather.change(preset, args[2], args[1]) + return "weather set to "..filename.." preset ("..tostring(args[2]).." s)" + end +) + +console.add_command( + "weather", + "Display current weather preset name", + function (args, kwargs) + local name = gfx.weather.get_current() + if name == "" then + return "unnamed " .. json.tostring(gfx.weather.get_current_data(), true) + else + return name + end + end +) + console.cheats = { "blocks.fill", "tp", @@ -271,5 +298,6 @@ console.cheats = { "time.set", "time.daycycle", "entity.despawn", - "player.respawn" + "player.respawn", + "weather.set", } diff --git a/res/shaders/entity.glslf b/res/shaders/entity.glslf index 143b3eaa..e7a8a754 100644 --- a/res/shaders/entity.glslf +++ b/res/shaders/entity.glslf @@ -1,7 +1,7 @@ in vec4 a_color; in vec2 a_texCoord; -in float a_distance; in vec3 a_dir; +in float a_fog; out vec4 f_color; uniform sampler2D u_texture0; @@ -14,12 +14,10 @@ uniform bool u_alphaClip; void main() { vec3 fogColor = texture(u_cubemap, a_dir).rgb; vec4 tex_color = texture(u_texture0, a_texCoord); - float depth = (a_distance/256.0); float alpha = a_color.a * tex_color.a; // anyway it's any alpha-test alternative required - if (alpha < (u_alphaClip ? 0.5f : 0.2f)) + if (alpha < (u_alphaClip ? 0.5f : 0.15f)) discard; - f_color = mix(a_color * tex_color, vec4(fogColor,1.0), - min(1.0, pow(depth*u_fogFactor, u_fogCurve))); + f_color = mix(a_color * tex_color, vec4(fogColor,1.0), a_fog); f_color.a = alpha; } diff --git a/res/shaders/entity.glslv b/res/shaders/entity.glslv index 42c72c04..e318db38 100644 --- a/res/shaders/entity.glslv +++ b/res/shaders/entity.glslv @@ -7,7 +7,7 @@ layout (location = 3) in float v_light; out vec4 a_color; out vec2 a_texCoord; -out float a_distance; +out float a_fog; out vec3 a_dir; uniform mat4 u_model; @@ -15,6 +15,12 @@ uniform mat4 u_proj; uniform mat4 u_view; uniform vec3 u_cameraPos; uniform float u_gamma; +uniform float u_opacity; +uniform float u_fogFactor; +uniform float u_fogCurve; +uniform float u_weatherFogOpacity; +uniform float u_weatherFogDencity; +uniform float u_weatherFogCurve; uniform samplerCube u_cubemap; uniform vec3 u_torchlightColor; @@ -36,6 +42,11 @@ void main() { a_dir = modelpos.xyz - u_cameraPos; vec3 skyLightColor = pick_sky_color(u_cubemap); a_color.rgb = max(a_color.rgb, skyLightColor.rgb*decomp_light.a) * v_color; - a_distance = length(u_view * u_model * vec4(pos3d * FOG_POS_SCALE, 0.0)); + a_color.a = u_opacity; + + float dist = length(u_view * u_model * vec4(pos3d * FOG_POS_SCALE, 0.0)); + float depth = (dist / 256.0); + a_fog = min(1.0, max(pow(depth * u_fogFactor, u_fogCurve), + min(pow(depth * u_weatherFogDencity, u_weatherFogCurve), u_weatherFogOpacity))); gl_Position = u_proj * u_view * modelpos; } diff --git a/res/shaders/main.glslf b/res/shaders/main.glslf index 12e96eab..bccbd53a 100644 --- a/res/shaders/main.glslf +++ b/res/shaders/main.glslf @@ -1,20 +1,16 @@ in vec4 a_color; in vec2 a_texCoord; -in float a_distance; +in float a_fog; in vec3 a_dir; out vec4 f_color; uniform sampler2D u_texture0; uniform samplerCube u_cubemap; -uniform vec3 u_fogColor; -uniform float u_fogFactor; -uniform float u_fogCurve; uniform bool u_alphaClip; void main() { vec3 fogColor = texture(u_cubemap, a_dir).rgb; vec4 tex_color = texture(u_texture0, a_texCoord); - float depth = (a_distance/256.0); float alpha = a_color.a * tex_color.a; if (u_alphaClip) { if (alpha < 0.2f) @@ -24,7 +20,6 @@ void main() { if (alpha < 0.002f) discard; } - f_color = mix(a_color * tex_color, vec4(fogColor,1.0), - min(1.0, pow(depth*u_fogFactor, u_fogCurve))); + f_color = mix(a_color * tex_color, vec4(fogColor,1.0), a_fog); f_color.a = alpha; } diff --git a/res/shaders/main.glslv b/res/shaders/main.glslv index ba3687e8..c8d05d5d 100644 --- a/res/shaders/main.glslv +++ b/res/shaders/main.glslv @@ -7,6 +7,7 @@ layout (location = 2) in float v_light; out vec4 a_color; out vec2 a_texCoord; out float a_distance; +out float a_fog; out vec3 a_dir; uniform mat4 u_model; @@ -14,6 +15,12 @@ uniform mat4 u_proj; uniform mat4 u_view; uniform vec3 u_cameraPos; uniform float u_gamma; +uniform float u_fogFactor; +uniform float u_fogCurve; +uniform float u_weatherFogOpacity; +uniform float u_weatherFogDencity; +uniform float u_weatherFogCurve; +uniform float u_timer; uniform samplerCube u_cubemap; uniform vec3 u_torchlightColor; @@ -35,6 +42,10 @@ void main() { a_dir = modelpos.xyz - u_cameraPos; vec3 skyLightColor = pick_sky_color(u_cubemap); a_color.rgb = max(a_color.rgb, skyLightColor.rgb*decomp_light.a); + a_distance = length(u_view * u_model * vec4(pos3d * FOG_POS_SCALE, 0.0)); + float depth = (a_distance / 256.0); + a_fog = min(1.0, max(pow(depth * u_fogFactor, u_fogCurve), + min(pow(depth * u_weatherFogDencity, u_weatherFogCurve), u_weatherFogOpacity))); gl_Position = u_proj * u_view * modelpos; } diff --git a/res/textures/misc/rain.png b/res/textures/misc/rain.png new file mode 100644 index 00000000..c94bdb0f Binary files /dev/null and b/res/textures/misc/rain.png differ diff --git a/res/textures/misc/snow.png b/res/textures/misc/snow.png new file mode 100644 index 00000000..107eacb4 Binary files /dev/null and b/res/textures/misc/snow.png differ diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 08e1b41e..9a471bff 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -36,10 +36,14 @@ static debug::Logger logger("level-screen"); +inline const io::path CLIENT_FILE = "world:client/environment.json"; + LevelScreen::LevelScreen( Engine& engine, std::unique_ptr levelPtr, int64_t localPlayer ) - : Screen(engine), postProcessing(std::make_unique()) { + : Screen(engine), + world(*levelPtr->getWorld()), + postProcessing(std::make_unique()) { Level* level = levelPtr.get(); auto& settings = engine.getSettings(); @@ -62,22 +66,22 @@ LevelScreen::LevelScreen( frontend = std::make_unique( player, controller.get(), assets, settings ); - worldRenderer = std::make_unique( + renderer = std::make_unique( engine, *frontend, *player ); hud = std::make_unique(engine, *frontend, *player); decorator = std::make_unique( - engine, *controller, *worldRenderer, assets, *player + engine, *controller, *renderer, assets, *player ); keepAlive(settings.graphics.backlight.observe([=](bool) { player->chunks->saveAndClear(); - worldRenderer->clear(); + renderer->clear(); })); keepAlive(settings.graphics.denseRender.observe([=](bool) { player->chunks->saveAndClear(); - worldRenderer->clear(); + renderer->clear(); frontend->getContentGfxCache().refresh(); })); keepAlive(settings.camera.fov.observe([=](double value) { @@ -85,22 +89,35 @@ LevelScreen::LevelScreen( })); keepAlive(Events::getBinding(BIND_CHUNKS_RELOAD).onactived.add([=](){ player->chunks->saveAndClear(); - worldRenderer->clear(); + renderer->clear(); return false; })); animator = std::make_unique(); animator->addAnimations(assets.getAnimations()); + loadDecorations(); initializeContent(); } +LevelScreen::~LevelScreen() { + if (!controller->getLevel()->getWorld()->isNameless()) { + saveDecorations(); + saveWorldPreview(); + } + scripting::on_frontend_close(); + // unblock all bindings + Events::enableBindings(); + controller->onWorldQuit(); + engine.getPaths().setCurrentWorldFolder(""); +} + void LevelScreen::initializeContent() { auto& content = controller->getLevel()->content; for (auto& entry : content.getPacks()) { initializePack(entry.second.get()); } - scripting::on_frontend_init(hud.get(), worldRenderer.get()); + scripting::on_frontend_init(hud.get(), renderer.get()); } void LevelScreen::initializePack(ContentPackRuntime* pack) { @@ -116,15 +133,22 @@ void LevelScreen::initializePack(ContentPackRuntime* pack) { } } -LevelScreen::~LevelScreen() { - if (!controller->getLevel()->getWorld()->isNameless()) { - saveWorldPreview(); +void LevelScreen::loadDecorations() { + if (!io::exists(CLIENT_FILE)) { + return; } - scripting::on_frontend_close(); - // unblock all bindings - Events::enableBindings(); - controller->onWorldQuit(); - engine.getPaths().setCurrentWorldFolder(""); + auto data = io::read_object(CLIENT_FILE); + if (data.has("weather")) { + renderer->getWeather().deserialize(data["weather"]); + } +} + +void LevelScreen::saveDecorations() { + io::create_directory("world:client"); + + auto data = dv::object(); + data["weather"] = renderer->getWeather().serialize(); + io::write_json(CLIENT_FILE, data, true); } void LevelScreen::saveWorldPreview() { @@ -144,7 +168,7 @@ void LevelScreen::saveWorldPreview() { Viewport viewport(previewSize * 1.5, previewSize); DrawContext ctx(&pctx, viewport, batch.get()); - worldRenderer->draw(ctx, camera, false, true, 0.0f, postProcessing.get()); + renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing); auto image = postProcessing->toImage(); image->flipY(); imageio::write("world:preview.png", image.get()); @@ -164,26 +188,15 @@ void LevelScreen::updateHotkeys() { if (Events::jpressed(keycode::F3)) { debug = !debug; hud->setDebug(debug); - worldRenderer->setDebug(debug); + renderer->setDebug(debug); } } -void LevelScreen::update(float delta) { - gui::GUI* gui = engine.getGUI(); - auto menu = gui->getMenu(); - - bool inputLocked = menu->hasOpenPage() || - hud->isInventoryOpen() || - gui->isFocusCaught(); - if (!gui->isFocusCaught()) { - updateHotkeys(); - } - - auto level = controller->getLevel(); +void LevelScreen::updateAudio() { auto player = playerController->getPlayer(); auto camera = player->currentCamera; - bool paused = hud->isPause(); + audio::get_channel("regular")->setPaused(paused); audio::get_channel("ambient")->setPaused(paused); glm::vec3 velocity {}; @@ -196,20 +209,34 @@ void LevelScreen::update(float delta) { camera->dir, glm::vec3(0, 1, 0) ); - const auto& settings = engine.getSettings(); +} - if (!hud->isPause()) { - level->getWorld()->updateTimers(delta); - animator->update(delta); +void LevelScreen::update(float delta) { + auto& gui = *engine.getGUI(); + + if (!gui.isFocusCaught()) { + updateHotkeys(); } - if (!hud->isPause()) { + updateAudio(); + + auto menu = gui.getMenu(); + bool inputLocked = + menu->hasOpenPage() || hud->isInventoryOpen() || gui.isFocusCaught(); + bool paused = hud->isPause(); + if (!paused) { + world.updateTimers(delta); + animator->update(delta); playerController->update(delta, !inputLocked); } - controller->update(glm::min(delta, 0.2f), hud->isPause()); - playerController->postUpdate(delta, !inputLocked, hud->isPause()); + controller->update(glm::min(delta, 0.2f), paused); + playerController->postUpdate(delta, !inputLocked, paused); hud->update(hudVisible); - decorator->update(delta, *camera); + + const auto& weather = renderer->getWeather(); + const auto& player = *playerController->getPlayer(); + const auto& camera = *player.currentCamera; + decorator->update(paused ? 0.0f : delta, camera, weather); } void LevelScreen::draw(float delta) { @@ -221,8 +248,8 @@ void LevelScreen::draw(float delta) { if (!hud->isPause()) { scripting::on_entities_render(engine.getTime().getDelta()); } - worldRenderer->draw( - ctx, *camera, hudVisible, hud->isPause(), delta, postProcessing.get() + renderer->draw( + ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing ); if (hudVisible) { @@ -236,8 +263,3 @@ void LevelScreen::onEngineShutdown() { } controller->saveWorld(); } - -LevelController* LevelScreen::getLevelController() const { - return controller.get(); -} - diff --git a/src/frontend/screens/LevelScreen.hpp b/src/frontend/screens/LevelScreen.hpp index 13ad5bf7..8b1401f9 100644 --- a/src/frontend/screens/LevelScreen.hpp +++ b/src/frontend/screens/LevelScreen.hpp @@ -15,12 +15,14 @@ class PostProcessing; class ContentPackRuntime; class Decorator; class Level; +class World; class LevelScreen : public Screen { + World& world; std::unique_ptr frontend; std::unique_ptr controller; std::unique_ptr playerController; - std::unique_ptr worldRenderer; + std::unique_ptr renderer; std::unique_ptr animator; std::unique_ptr postProcessing; std::unique_ptr decorator; @@ -33,6 +35,10 @@ class LevelScreen : public Screen { void updateHotkeys(); void initializeContent(); void initializePack(ContentPackRuntime* pack); + + void loadDecorations(); + void saveDecorations(); + void updateAudio(); public: LevelScreen( Engine& engine, std::unique_ptr level, int64_t localPlayer @@ -43,6 +49,4 @@ public: void draw(float delta) override; void onEngineShutdown() override; - - LevelController* getLevelController() const; }; diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 605e9ec6..c0c88ebb 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -7,6 +7,7 @@ #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" @@ -17,6 +18,8 @@ #include "util/stringutil.hpp" #include "engine/Engine.hpp" #include "io/io.hpp" +#include "audio/audio.hpp" +#include "maths/util.hpp" namespace fs = std::filesystem; @@ -80,8 +83,75 @@ void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { } } +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 + float delta, + const glm::ivec3& areaStart, + const glm::ivec3& areaCenter ) { int index = currentIndex; currentIndex = (currentIndex + BIG_PRIME) % UPDATE_BLOCKS; @@ -93,7 +163,8 @@ void Decorator::update( int lz = (index / UPDATE_AREA_DIAMETER) % UPDATE_AREA_DIAMETER; int ly = (index / UPDATE_AREA_DIAMETER / UPDATE_AREA_DIAMETER); - auto pos = areaStart + glm::ivec3(lx, ly, lz); + glm::ivec3 offset {lx, ly, lz}; + auto pos = areaStart + offset; if (auto vox = chunks.get(pos)) { const auto& def = indices.blocks.require(vox->id); @@ -103,11 +174,7 @@ void Decorator::update( } } -void Decorator::update(float delta, const Camera& camera) { - glm::ivec3 pos = camera.position; - for (int i = 0; i < ITERATIONS; i++) { - update(delta, pos - glm::ivec3(UPDATE_AREA_DIAMETER / 2), pos); - } +void Decorator::updateBlockEmitters(const Camera& camera) { const auto& chunks = *player.chunks; const auto& indices = *level.content.getIndices(); auto iter = blockEmitters.begin(); @@ -139,7 +206,9 @@ void Decorator::update(float delta, const Camera& camera) { } iter++; } +} +void Decorator::updateTextNotes() { for (const auto& [id, player] : *level.players) { if (id == this->player.getId() || playerTexts.find(id) != playerTexts.end()) { @@ -169,3 +238,47 @@ void Decorator::update(float delta, const Camera& camera) { } } } + +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(); +} diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp index e1b7eb17..31b0d506 100644 --- a/src/graphics/render/Decorator.hpp +++ b/src/graphics/render/Decorator.hpp @@ -18,6 +18,8 @@ class Block; class Engine; class LevelController; class WorldRenderer; +class Weather; +struct WeatherPreset; class Decorator { Engine& engine; @@ -29,10 +31,23 @@ class Decorator { std::unordered_map playerTexts; int currentIndex = 0; NotePreset playerNamePreset {}; + float thunderTimer = 0.0f; void update( - float delta, const glm::ivec3& areaStart, const glm::ivec3& areaCenter + float delta, + const glm::ivec3& areaStart, + const glm::ivec3& areaCenter ); + + /// @brief Updates weather effects, blocks ambient sounds, etc.. + void updateRandom( + float delta, + const glm::ivec3& areaCenter, + const WeatherPreset& weather + ); + void updateRandomSounds(float delta, const Weather& weather); + void updateBlockEmitters(const Camera& camera); + void updateTextNotes(); void addParticles(const Block& def, const glm::ivec3& pos); public: Decorator( @@ -43,5 +58,9 @@ public: Player& player ); - void update(float delta, const Camera& camera); + void update( + float delta, + const Camera& camera, + const Weather& weather + ); }; diff --git a/src/graphics/render/ParticlesRenderer.cpp b/src/graphics/render/ParticlesRenderer.cpp index f61b67fd..1be7272a 100644 --- a/src/graphics/render/ParticlesRenderer.cpp +++ b/src/graphics/render/ParticlesRenderer.cpp @@ -6,10 +6,10 @@ #include "assets/assets_util.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" -#include "graphics/render/MainBatch.hpp" #include "window/Camera.hpp" #include "world/Level.hpp" #include "voxels/Chunks.hpp" +#include "MainBatch.hpp" #include "settings.hpp" size_t ParticlesRenderer::visibleParticles = 0; @@ -179,24 +179,6 @@ void ParticlesRenderer::render(const Camera& camera, float delta) { } } -void ParticlesRenderer::gc() { - std::set usedEmitters; - for (const auto& [_, vec] : particles) { - for (const auto& particle : vec) { - usedEmitters.insert(particle.emitter); - } - } - auto iter = emitters.begin(); - while (iter != emitters.end()) { - auto emitter = iter->second.get(); - if (usedEmitters.find(emitter) == usedEmitters.end()) { - iter = emitters.erase(iter); - } else { - iter++; - } - } -} - Emitter* ParticlesRenderer::getEmitter(u64id_t id) const { const auto& found = emitters.find(id); if (found == emitters.end()) { diff --git a/src/graphics/render/ParticlesRenderer.hpp b/src/graphics/render/ParticlesRenderer.hpp index 3fe692e5..a01f1018 100644 --- a/src/graphics/render/ParticlesRenderer.hpp +++ b/src/graphics/render/ParticlesRenderer.hpp @@ -40,12 +40,6 @@ public: u64id_t add(std::unique_ptr emitter); - /// @brief Perform garbage collection (remove extra dead emitters). - /// @note Emitters are deleting without GC when there's no particles with same - /// texture left. - /// @note Currently unused - void gc(); - /// @brief Get emitter by UID /// @return Emitter or nullptr Emitter* getEmitter(u64id_t id) const; diff --git a/src/graphics/render/PrecipitationRenderer.cpp b/src/graphics/render/PrecipitationRenderer.cpp new file mode 100644 index 00000000..2b626921 --- /dev/null +++ b/src/graphics/render/PrecipitationRenderer.cpp @@ -0,0 +1,160 @@ +#include "PrecipitationRenderer.hpp" + +#include "MainBatch.hpp" +#include "assets/Assets.hpp" +#include "assets/assets_util.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Texture.hpp" +#include "lighting/Lightmap.hpp" +#include "maths/util.hpp" +#include "maths/voxmaths.hpp" +#include "util/CentredMatrix.hpp" +#include "settings.hpp" +#include "presets/WeatherPreset.hpp" +#include "voxels/Chunk.hpp" +#include "voxels/Chunks.hpp" +#include "window/Camera.hpp" +#include "world/Level.hpp" + +PrecipitationRenderer::PrecipitationRenderer( + const Assets& assets, + const Level& level, + const Chunks& chunks, + const GraphicsSettings* settings +) + : batch(std::make_unique(4096)), + level(level), + chunks(chunks), + assets(assets), + settings(settings) { +} + +PrecipitationRenderer::~PrecipitationRenderer() = default; + +int PrecipitationRenderer::getHeightAt(int x, int z) { + int y = CHUNK_H - 1; + int cx = floordiv(x); + int cz = floordiv(z); + auto chunk = chunks.getChunk(cx, cz); + if (chunk == nullptr) { + return y; + } + y = chunk->top; + x -= cx * CHUNK_W; + z -= cz * CHUNK_D; + while (y > 0) { + const auto& vox = chunk->voxels[vox_index(x, y, z)]; + if (vox.id == 0) { + y--; + continue; + } + break; + } + return y; +} + +static inline glm::vec4 light_at(const Chunks& chunks, int x, int y, int z) { + light_t lightval = chunks.getLight(x, y, z); + return glm::vec4( + Lightmap::extract(lightval, 0) / 15.f, + Lightmap::extract(lightval, 1) / 15.f, + Lightmap::extract(lightval, 2) / 15.f, + 1 + ); +} + +/// @brief 'Random' vertical texture coord offset to randomize rain layers +static constexpr float K = 21.41149; +/// @brief Precipitation face size +static constexpr glm::ivec2 FACE_SIZE {1, 16}; + +static UVRegion calc_uv( + const glm::vec3& pos, + const glm::vec3& right, + float timer, + const WeatherPreset& weather +) { + static util::PseudoRandom random(0); + + float scale = weather.fall.scale; + + float m = glm::sign(right.x + right.z); + int ux = pos.x; + int uz = pos.z; + if (glm::abs(right.x) < glm::abs(right.z)) { + std::swap(ux, uz); + } + random.setSeed(uz); + float hspeed = (random.randFloat() * 2.0f - 1.0f) * weather.fall.hspeed; + float u1 = ux * scale + timer * hspeed * -m; + float v1 = timer * weather.fall.vspeed + pos.y * scale + uz * K; + + return {u1, v1, u1 + m * scale, v1 + FACE_SIZE.y * scale}; +} + +void PrecipitationRenderer::render( + const Camera& camera, float delta, const WeatherPreset& weather +) { + timer += delta; + + const int radius = 6; + const int depth = 12; + + int x = glm::floor(camera.position.x); + int y = glm::floor(camera.position.y); + int z = glm::floor(camera.position.z); + + util::CentredMatrix heights; + heights.setCenter(x, z); + for (int z = heights.beginY(); z < heights.endY(); z++) { + for (int x = heights.beginX(); x < heights.endX(); x++) { + heights.at(x, z) = getHeightAt(x, z); + } + } + + batch->begin(); + auto& texture = assets.require(weather.fall.texture); + texture.setMipMapping(false); + batch->setTexture(&texture, {}); + + const struct { + glm::vec3 right; + glm::vec3 front; + } faces[] { + {{-1, 0, 0}, {0, 0, 1}}, + {{1, 0, 0}, {0, 0, -1}}, + {{0, 0, -1}, {-1, 0, 0}}, + {{0, 0, 1}, {1, 0, 0}}, + }; + + bool cutBack = glm::dot(camera.up, glm::vec3(0, 1, 0)) > 0.35f * camera.getFov(); + for (const auto& face : faces) { + if (glm::dot(camera.right, face.right) < 0.0f && cutBack) { + continue; + } + for (int lx = -radius; lx <= radius; lx++) { + for (int lz = depth; lz > 0; lz--) { + // Position calculations + glm::vec3 pos = face.right * static_cast(lx) + + face.front * static_cast(lz); + pos += glm::vec3(x, 0, z); + pos.y = + glm::max(y - FACE_SIZE.y / 2, heights.at(pos.x, pos.z)) + + FACE_SIZE.y / 2 + 1; + pos += glm::vec3(0.5f, 0.0f, 0.5f); + + // Draw + batch->quad( + pos, + face.right, + {0, 1, 0}, + FACE_SIZE, + light_at(chunks, pos.x, y, pos.z), + glm::vec3(1.0f), + calc_uv(pos, face.right, timer, weather) + ); + } + } + } + batch->flush(); +} diff --git a/src/graphics/render/PrecipitationRenderer.hpp b/src/graphics/render/PrecipitationRenderer.hpp new file mode 100644 index 00000000..9a691aca --- /dev/null +++ b/src/graphics/render/PrecipitationRenderer.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "typedefs.hpp" + +class Level; +class Assets; +class Chunks; +class Camera; +class MainBatch; +struct GraphicsSettings; +struct WeatherPreset; + +class PrecipitationRenderer { + std::unique_ptr batch; + const Level& level; + const Chunks& chunks; + const Assets& assets; + const GraphicsSettings* settings; + float timer = 0.0f; + + int getHeightAt(int x, int z); +public: + PrecipitationRenderer( + const Assets& assets, + const Level& level, + const Chunks& chunks, + const GraphicsSettings* settings + ); + + ~PrecipitationRenderer(); + + void render(const Camera& camera, float delta, const WeatherPreset& weather); +}; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 4d36c286..2903028c 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -44,6 +44,7 @@ #include "graphics/core/Font.hpp" #include "BlockWrapsRenderer.hpp" #include "ParticlesRenderer.hpp" +#include "PrecipitationRenderer.hpp" #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" #include "GuidesRenderer.hpp" @@ -86,7 +87,10 @@ WorldRenderer::WorldRenderer( )), blockWraps( std::make_unique(assets, level, *player.chunks) - ) { + ), + precipitation(std::make_unique( + assets, level, *player.chunks, &engine.getSettings().graphics + )) { auto& settings = engine.getSettings(); level.events->listen( LevelEventType::CHUNK_HIDDEN, @@ -115,6 +119,9 @@ void WorldRenderer::setupWorldShader( shader.uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); + shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); + shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); + shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); shader.uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform3f("u_cameraPos", camera.position); @@ -160,6 +167,7 @@ void WorldRenderer::renderLevel( } entityShader.uniform1i("u_alphaClip", true); + entityShader.uniform1f("u_opacity", 1.0f); level.entities->render( assets, *modelBatch, @@ -188,6 +196,21 @@ void WorldRenderer::renderLevel( scripting::on_frontend_render(); } + setupWorldShader(entityShader, camera, settings, fogFactor); + + std::array weatherInstances {&weather.a, &weather.b}; + for (const auto& weather : weatherInstances) { + float maxIntensity = weather->fall.maxIntensity; + float zero = weather->fall.minOpacity; + float one = weather->fall.maxOpacity; + float t = (weather->intensity * (one - zero)) * maxIntensity + zero; + entityShader.uniform1i("u_alphaClip", weather->fall.opaque); + entityShader.uniform1f("u_opacity", weather->fall.opaque ? t * t : t); + if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) { + precipitation->render(camera, pause ? 0.0f : delta, *weather); + } + } + skybox->unbind(); } @@ -306,36 +329,46 @@ void WorldRenderer::draw( Camera& camera, bool hudVisible, bool pause, - float delta, - PostProcessing* postProcessing + float uiDelta, + PostProcessing& postProcessing ) { - timer += delta * !pause; + float delta = uiDelta * !pause; + timer += delta; + weather.update(delta); + auto world = level.getWorld(); + const Viewport& vp = pctx.getViewport(); camera.aspect = vp.getWidth() / static_cast(vp.getHeight()); const auto& settings = engine.getSettings(); const auto& worldInfo = world->getInfo(); + + float sqrtT = glm::sqrt(weather.t); + float clouds = weather.b.clouds * sqrtT + + weather.a.clouds * (1.0f - sqrtT); + clouds = glm::max(worldInfo.fog, clouds); + float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f; - skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4); + skybox->refresh(pctx, worldInfo.daytime, mie, 4); const auto& assets = *engine.getAssets(); auto& linesShader = assets.require("lines"); /* World render scope with diegetic HUD included */ { DrawContext wctx = pctx.sub(); - postProcessing->use(wctx); + postProcessing.use(wctx); Window::clearDepth(); // Drawing background sky plane - skybox->draw(pctx, camera, assets, worldInfo.daytime, worldInfo.fog); - + skybox->draw(pctx, camera, assets, worldInfo.daytime, clouds); + /* Actually world render with depth buffer on */ { DrawContext ctx = wctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); - renderLevel(ctx, camera, settings, delta, pause, hudVisible); + renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible); // Debug lines if (hudVisible) { if (debug) { @@ -344,7 +377,7 @@ void WorldRenderer::draw( ); } if (player.currentCamera == player.fpCamera) { - renderHands(camera, delta * !pause); + renderHands(camera, delta); } } } @@ -355,12 +388,12 @@ void WorldRenderer::draw( renderBlockOverlay(wctx); } - // Rendering fullscreen quad with + // Rendering fullscreen quad auto screenShader = assets.get("screen"); screenShader->use(); screenShader->uniform1f("u_timer", timer); screenShader->uniform1f("u_dayTime", worldInfo.daytime); - postProcessing->render(pctx, screenShader); + postProcessing.render(pctx, screenShader); } void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { @@ -414,3 +447,7 @@ void WorldRenderer::clear() { void WorldRenderer::setDebug(bool flag) { debug = flag; } + +Weather& WorldRenderer::getWeather() { + return weather; +} diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 569858d8..8d3914be 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -10,6 +10,9 @@ #include "typedefs.hpp" +#include "presets/WeatherPreset.hpp" +#include "world/Weather.hpp" + class Level; class Player; class Camera; @@ -18,6 +21,7 @@ class LineBatch; class ChunksRenderer; class ParticlesRenderer; class BlockWrapsRenderer; +class PrecipitationRenderer; class GuidesRenderer; class TextsRenderer; class Shader; @@ -43,6 +47,7 @@ class WorldRenderer { std::unique_ptr guides; std::unique_ptr skybox; std::unique_ptr modelBatch; + Weather weather {}; float timer = 0.0f; bool debug = false; @@ -71,6 +76,7 @@ public: std::unique_ptr texts; std::unique_ptr particles; std::unique_ptr blockWraps; + std::unique_ptr precipitation; static bool showChunkBorders; static bool showEntitiesDebug; @@ -84,7 +90,7 @@ public: bool hudVisible, bool pause, float delta, - PostProcessing* postProcessing + PostProcessing& postProcessing ); /// @brief Render level without diegetic interface @@ -103,4 +109,6 @@ public: void clear(); void setDebug(bool flag); + + Weather& getWeather(); }; diff --git a/src/interfaces/Serializable.hpp b/src/interfaces/Serializable.hpp index 24457d44..46228f4a 100644 --- a/src/interfaces/Serializable.hpp +++ b/src/interfaces/Serializable.hpp @@ -9,4 +9,10 @@ public: virtual ~Serializable() {} virtual dv::value serialize() const = 0; virtual void deserialize(const dv::value& src) = 0; + + void deserializeOpt(const dv::optionalvalue& opt) { + if (opt.ptr) { + deserialize(*opt.ptr); + } + } }; diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index 1b7d42d6..e44c3609 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -46,6 +46,7 @@ extern const luaL_Reg utf8lib[]; extern const luaL_Reg vec2lib[]; // vecn.cpp extern const luaL_Reg vec3lib[]; // vecn.cpp extern const luaL_Reg vec4lib[]; // vecn.cpp +extern const luaL_Reg weatherlib[]; extern const luaL_Reg worldlib[]; // Components diff --git a/src/logic/scripting/lua/libs/libweather.cpp b/src/logic/scripting/lua/libs/libweather.cpp new file mode 100644 index 00000000..0028ae5e --- /dev/null +++ b/src/logic/scripting/lua/libs/libweather.cpp @@ -0,0 +1,68 @@ +#include "libhud.hpp" + +#include "world/Level.hpp" +#include "world/World.hpp" + +using namespace scripting; + +static Weather& require_weather() { + if (level == nullptr) { + throw std::runtime_error("world is not open"); + } + return renderer->getWeather(); +} + +static int l_change(lua::State* L) { + WeatherPreset preset {}; + preset.deserialize(lua::tovalue(L, 1)); + float time = lua::tonumber(L, 2); + std::string name; + if (lua::isstring(L, 3)) { + name = lua::tostring(L, 3); + } + auto& weather = require_weather(); + weather.change(std::move(preset), time, std::move(name)); + return 0; +} + +static int l_get_current(lua::State* L) { + const auto& weather = require_weather(); + if (weather.t > 0.5f) { + return lua::pushstring(L, weather.nameB); + } else { + return lua::pushstring(L, weather.nameA); + } +} + +static int l_get_fall_intensity(lua::State* L) { + auto& weather = require_weather(); + const auto& a = weather.a; + const auto& b = weather.b; + float t = weather.t; + return lua::pushnumber(L, + (a.fall.texture.empty() ? 0.0f : (1.0f - t)) + + (b.fall.texture.empty() ? 0.0f : t) + ); +} + +static int l_get_current_data(lua::State* L) { + auto& weather = require_weather(); + if (weather.t > 0.5f) { + return lua::pushvalue(L, weather.b.serialize()); + } else { + return lua::pushvalue(L, weather.a.serialize()); + } +} + +static int l_is_transition(lua::State* L) { + return lua::pushboolean(L, require_weather().t < 1.0f); +} + +const luaL_Reg weatherlib[] = { + {"change", lua::wrap}, + {"get_current", lua::wrap}, + {"get_current_data", lua::wrap}, + {"get_fall_intensity", lua::wrap}, + {"is_transition", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/scripting_hud.cpp b/src/logic/scripting/scripting_hud.cpp index 478b4f9e..c34c223d 100644 --- a/src/logic/scripting/scripting_hud.cpp +++ b/src/logic/scripting/scripting_hud.cpp @@ -35,6 +35,7 @@ void scripting::on_frontend_init(Hud* hud, WorldRenderer* renderer) { lua::openlib(L, "hud", hudlib); lua::openlib(L, "gfx", "blockwraps", blockwrapslib); lua::openlib(L, "gfx", "particles", particleslib); + lua::openlib(L, "gfx", "weather", weatherlib); lua::openlib(L, "gfx", "text3d", text3dlib); load_script("hud_classes.lua"); diff --git a/src/maths/util.hpp b/src/maths/util.hpp index df39cf41..f417384d 100644 --- a/src/maths/util.hpp +++ b/src/maths/util.hpp @@ -20,6 +20,8 @@ namespace util { class PseudoRandom { unsigned short seed; public: + PseudoRandom(unsigned short seed) : seed(seed) {} + PseudoRandom() { seed = static_cast(time(0)); } diff --git a/src/presets/WeatherPreset.cpp b/src/presets/WeatherPreset.cpp new file mode 100644 index 00000000..6ae0bae7 --- /dev/null +++ b/src/presets/WeatherPreset.cpp @@ -0,0 +1,56 @@ +#include "WeatherPreset.hpp" + +#include "data/dv_util.hpp" + +dv::value WeatherPreset::serialize() const { + auto root = dv::object(); + + auto froot = dv::object(); + froot["texture"] = fall.texture; + froot["vspeed"] = fall.vspeed; + froot["hspeed"] = fall.hspeed; + froot["scale"] = fall.scale; + froot["noise"] = fall.noise; + froot["min_opacity"] = fall.minOpacity; + froot["max_opacity"] = fall.maxOpacity; + froot["max_intensity"] = fall.maxIntensity; + froot["opaque"] = fall.opaque; + if (fall.splash) { + froot["splash"] = fall.splash->serialize(); + } + root["fall"] = froot; + + root["fog_opacity"] = fogOpacity; + root["fog_dencity"] = fogDencity; + root["fog_curve"] = fogCurve; + root["clouds"] = clouds; + root["thunder_rate"] = thunderRate; + + return root; +} + +void WeatherPreset::deserialize(const dv::value& src) { + if (src.has("fall")) { + const auto& froot = src["fall"]; + froot.at("texture").get(fall.texture); + froot.at("vspeed").get(fall.vspeed); + froot.at("hspeed").get(fall.hspeed); + froot.at("scale").get(fall.scale); + froot.at("noise").get(fall.noise); + froot.at("min_opacity").get(fall.minOpacity); + froot.at("max_opacity").get(fall.maxOpacity); + froot.at("max_intensity").get(fall.maxIntensity); + froot.at("opaque").get(fall.opaque); + + if (froot.has("splash")) { + const auto& sroot = froot["splash"]; + fall.splash = ParticlesPreset {}; + fall.splash->deserialize(sroot); + } + } + src.at("fog_opacity").get(fogOpacity); + src.at("fog_dencity").get(fogDencity); + src.at("fog_curve").get(fogCurve); + src.at("clouds").get(clouds); + src.at("thunder_rate").get(thunderRate); +} diff --git a/src/presets/WeatherPreset.hpp b/src/presets/WeatherPreset.hpp new file mode 100644 index 00000000..b3fa5db2 --- /dev/null +++ b/src/presets/WeatherPreset.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include "interfaces/Serializable.hpp" + +#include "ParticlesPreset.hpp" + +struct WeatherPreset : Serializable { + struct { + /// @brief Precipitation texture + std::string texture; + + /// @brief Fall sound + std::string noise; + + /// @brief Vertical speed + float vspeed = 1.0f; + + /// @brief Max horizontal speed + float hspeed = 0.1f; + + /// @brief UV scaling + float scale = 0.1f; + + /// @brief Fall opacity interpreted as zero. + /// @example if 0.8 then opacity range is 0.8-max for 0.0-1.0 intensity + float minOpacity = 0.0f; + + /// @brief Fall opacity interpreted as one. + /// @example if 0.8 then opacity range is min-0.8 for 0.0-1.0 intensity + float maxOpacity = 1.0f; + + /// @brief Max fall intencity + /// (influences opacity, noise volume and splashes frequency) + float maxIntensity = 1.0f; + + /// @brief Clip texture by alpha channel + bool opaque = false; + + /// @brief Fall splash + std::optional splash; + } fall {}; + + /// @brief Max weather fog opacity + float fogOpacity = 0.0f; + + /// @brief Weather fog depth multiplier + float fogDencity = 1.0f; + + /// @brief Weather fog curve + float fogCurve = 1.0f; + + /// @brief Clouds opacity + float clouds = 0.0f; + + /// @brief Thunder rate in range 0.0-1.0 (1.0 is 100% - every second) + float thunderRate = 0.0f; + + /// @brief Weather effects intensity + float intensity = 1.0f; + + dv::value serialize() const override; + void deserialize(const dv::value& src) override; +}; diff --git a/src/util/CentredMatrix.hpp b/src/util/CentredMatrix.hpp new file mode 100644 index 00000000..805a1aa1 --- /dev/null +++ b/src/util/CentredMatrix.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +namespace util { + template + class CentredMatrix { + public: + static constexpr CoordT radius = diameter / 2; + + CentredMatrix() {} + + void setCenter(CoordT x, CoordT y) { + centerX = x; + centerY = y; + } + + T& at(CoordT x, CoordT y) { + x -= centerX - (diameter - radius); + y -= centerY - (diameter - radius); + if (x < 0 || y < 0 || x >= diameter || y >= diameter) { + throw std::invalid_argument("position is out if matrix"); + } + return arr.at(y * diameter + x); + } + + auto begin() { + return arr.begin(); + } + + auto end() { + return arr.end(); + } + + CoordT beginX() const { + return centerX - (diameter - radius); + } + + CoordT beginY() const { + return centerY - (diameter - radius); + } + + CoordT endX() const { + return centerX + radius; + } + + CoordT endY() const { + return centerY + radius; + } + private: + std::array arr; + CoordT centerX = 0, centerY = 0; + }; +} diff --git a/src/world/Weather.hpp b/src/world/Weather.hpp new file mode 100644 index 00000000..98999126 --- /dev/null +++ b/src/world/Weather.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include "presets/WeatherPreset.hpp" + +struct Weather : Serializable { + WeatherPreset a {}; + WeatherPreset b {}; + std::string nameA; + std::string nameB; + float t = 1.0f; + float speed = 0.0f; + + void update(float delta) { + t += delta * speed; + t = std::min(t, 1.0f); + b.intensity = t; + a.intensity = 1.0f - t; + } + + void change(WeatherPreset preset, float time, std::string name="") { + std::swap(a, b); + std::swap(nameA, nameB); + b = std::move(preset); + t = 0.0f; + speed = 1.0f / std::max(time, 1.e-5f); + nameB = std::move(name); + update(0.0f); + } + + float fogOpacity() const { + return b.fogOpacity * t + a.fogOpacity * (1.0f - t); + } + + float fogDencity() const { + return b.fogDencity * t + a.fogDencity * (1.0f - t); + } + + float fogCurve() const { + return b.fogCurve * t + a.fogCurve * (1.0f - t); + } + + float thunderRate() const { + return b.thunderRate * t + a.thunderRate * (1.0f - t); + } + + dv::value serialize() const override { + return dv::object({ + {"a", a.serialize()}, + {"b", b.serialize()}, + {"name-a", nameA}, + {"name-b", nameB}, + {"t", t}, + {"speed", speed}, + }); + } + + void deserialize(const dv::value& src) override { + a.deserializeOpt(src.at("a")); + b.deserializeOpt(src.at("b")); + src.at("name-a").get(nameA); + src.at("name-b").get(nameB); + src.at("t").get(t); + src.at("speed").get(speed); + } +}; diff --git a/src/world/World.cpp b/src/world/World.cpp index 313f75cd..68842ae3 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -231,8 +231,8 @@ dv::value WorldInfo::serialize() const { timeobj["day-time-speed"] = daytimeSpeed; timeobj["total-time"] = totalTime; - auto& weatherobj = root.object("weather"); - weatherobj["fog"] = fog; + root["weather"] = dv::object(); + root["weather"]["fog"] = fog; root["next-inventory-id"] = nextInventoryId; root["next-entity-id"] = nextEntityId; diff --git a/src/world/World.hpp b/src/world/World.hpp index 531e7084..295ddac3 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -4,9 +4,9 @@ #include #include -#include "io/fwd.hpp" #include "content/ContentPack.hpp" #include "interfaces/Serializable.hpp" +#include "io/fwd.hpp" #include "typedefs.hpp" #include "util/timeutil.hpp" @@ -33,7 +33,6 @@ struct WorldInfo : public Serializable { /// 0.5 - is noon float daytime = timeutil::time_value(10, 00, 00); - // looking bad float daytimeSpeed = 1.0f; /// @brief total time passed in the world (not depending on daytimeSpeed)