Merge pull request #462 from MihailRis/precipitation

Weather
This commit is contained in:
MihailRis 2025-03-10 15:07:44 +03:00 committed by GitHub
commit 1f9b619392
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 926 additions and 115 deletions

View File

@ -5,7 +5,9 @@
"sounds": [ "sounds": [
"blocks/door_open", "blocks/door_open",
"blocks/door_close", "blocks/door_close",
"events/pickup" "events/pickup",
"ambient/rain",
"ambient/thunder"
], ],
"models": [ "models": [
"drop-item" "drop-item"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -20,6 +20,8 @@
"gui/refresh", "gui/refresh",
"gui/folder_icon", "gui/folder_icon",
"gui/settings_icon", "gui/settings_icon",
"misc/rain",
"misc/snow",
"gui/check_mark", "gui/check_mark",
"gui/left_arrow", "gui/left_arrow",
"gui/right_arrow" "gui/right_arrow"

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,4 @@
{
"clouds": 0.8
}

View File

@ -0,0 +1,6 @@
{
"fog_opacity": 0.98,
"fog_dencity": 3.6,
"fog_curve": 0.25,
"clouds": 0.8
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -264,6 +264,33 @@ console.add_command(
end 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 = { console.cheats = {
"blocks.fill", "blocks.fill",
"tp", "tp",
@ -271,5 +298,6 @@ console.cheats = {
"time.set", "time.set",
"time.daycycle", "time.daycycle",
"entity.despawn", "entity.despawn",
"player.respawn" "player.respawn",
"weather.set",
} }

View File

@ -1,7 +1,7 @@
in vec4 a_color; in vec4 a_color;
in vec2 a_texCoord; in vec2 a_texCoord;
in float a_distance;
in vec3 a_dir; in vec3 a_dir;
in float a_fog;
out vec4 f_color; out vec4 f_color;
uniform sampler2D u_texture0; uniform sampler2D u_texture0;
@ -14,12 +14,10 @@ uniform bool u_alphaClip;
void main() { void main() {
vec3 fogColor = texture(u_cubemap, a_dir).rgb; vec3 fogColor = texture(u_cubemap, a_dir).rgb;
vec4 tex_color = texture(u_texture0, a_texCoord); vec4 tex_color = texture(u_texture0, a_texCoord);
float depth = (a_distance/256.0);
float alpha = a_color.a * tex_color.a; float alpha = a_color.a * tex_color.a;
// anyway it's any alpha-test alternative required // 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; discard;
f_color = mix(a_color * tex_color, vec4(fogColor,1.0), f_color = mix(a_color * tex_color, vec4(fogColor,1.0), a_fog);
min(1.0, pow(depth*u_fogFactor, u_fogCurve)));
f_color.a = alpha; f_color.a = alpha;
} }

View File

@ -7,7 +7,7 @@ layout (location = 3) in float v_light;
out vec4 a_color; out vec4 a_color;
out vec2 a_texCoord; out vec2 a_texCoord;
out float a_distance; out float a_fog;
out vec3 a_dir; out vec3 a_dir;
uniform mat4 u_model; uniform mat4 u_model;
@ -15,6 +15,12 @@ uniform mat4 u_proj;
uniform mat4 u_view; uniform mat4 u_view;
uniform vec3 u_cameraPos; uniform vec3 u_cameraPos;
uniform float u_gamma; 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 samplerCube u_cubemap;
uniform vec3 u_torchlightColor; uniform vec3 u_torchlightColor;
@ -36,6 +42,11 @@ void main() {
a_dir = modelpos.xyz - u_cameraPos; a_dir = modelpos.xyz - u_cameraPos;
vec3 skyLightColor = pick_sky_color(u_cubemap); vec3 skyLightColor = pick_sky_color(u_cubemap);
a_color.rgb = max(a_color.rgb, skyLightColor.rgb*decomp_light.a) * v_color; 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; gl_Position = u_proj * u_view * modelpos;
} }

View File

@ -1,20 +1,16 @@
in vec4 a_color; in vec4 a_color;
in vec2 a_texCoord; in vec2 a_texCoord;
in float a_distance; in float a_fog;
in vec3 a_dir; in vec3 a_dir;
out vec4 f_color; out vec4 f_color;
uniform sampler2D u_texture0; uniform sampler2D u_texture0;
uniform samplerCube u_cubemap; uniform samplerCube u_cubemap;
uniform vec3 u_fogColor;
uniform float u_fogFactor;
uniform float u_fogCurve;
uniform bool u_alphaClip; uniform bool u_alphaClip;
void main() { void main() {
vec3 fogColor = texture(u_cubemap, a_dir).rgb; vec3 fogColor = texture(u_cubemap, a_dir).rgb;
vec4 tex_color = texture(u_texture0, a_texCoord); vec4 tex_color = texture(u_texture0, a_texCoord);
float depth = (a_distance/256.0);
float alpha = a_color.a * tex_color.a; float alpha = a_color.a * tex_color.a;
if (u_alphaClip) { if (u_alphaClip) {
if (alpha < 0.2f) if (alpha < 0.2f)
@ -24,7 +20,6 @@ void main() {
if (alpha < 0.002f) if (alpha < 0.002f)
discard; discard;
} }
f_color = mix(a_color * tex_color, vec4(fogColor,1.0), f_color = mix(a_color * tex_color, vec4(fogColor,1.0), a_fog);
min(1.0, pow(depth*u_fogFactor, u_fogCurve)));
f_color.a = alpha; f_color.a = alpha;
} }

View File

@ -7,6 +7,7 @@ layout (location = 2) in float v_light;
out vec4 a_color; out vec4 a_color;
out vec2 a_texCoord; out vec2 a_texCoord;
out float a_distance; out float a_distance;
out float a_fog;
out vec3 a_dir; out vec3 a_dir;
uniform mat4 u_model; uniform mat4 u_model;
@ -14,6 +15,12 @@ uniform mat4 u_proj;
uniform mat4 u_view; uniform mat4 u_view;
uniform vec3 u_cameraPos; uniform vec3 u_cameraPos;
uniform float u_gamma; 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 samplerCube u_cubemap;
uniform vec3 u_torchlightColor; uniform vec3 u_torchlightColor;
@ -35,6 +42,10 @@ void main() {
a_dir = modelpos.xyz - u_cameraPos; a_dir = modelpos.xyz - u_cameraPos;
vec3 skyLightColor = pick_sky_color(u_cubemap); vec3 skyLightColor = pick_sky_color(u_cubemap);
a_color.rgb = max(a_color.rgb, skyLightColor.rgb*decomp_light.a); 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)); 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; gl_Position = u_proj * u_view * modelpos;
} }

BIN
res/textures/misc/rain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

BIN
res/textures/misc/snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -36,10 +36,14 @@
static debug::Logger logger("level-screen"); static debug::Logger logger("level-screen");
inline const io::path CLIENT_FILE = "world:client/environment.json";
LevelScreen::LevelScreen( LevelScreen::LevelScreen(
Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
) )
: Screen(engine), postProcessing(std::make_unique<PostProcessing>()) { : Screen(engine),
world(*levelPtr->getWorld()),
postProcessing(std::make_unique<PostProcessing>()) {
Level* level = levelPtr.get(); Level* level = levelPtr.get();
auto& settings = engine.getSettings(); auto& settings = engine.getSettings();
@ -62,22 +66,22 @@ LevelScreen::LevelScreen(
frontend = std::make_unique<LevelFrontend>( frontend = std::make_unique<LevelFrontend>(
player, controller.get(), assets, settings player, controller.get(), assets, settings
); );
worldRenderer = std::make_unique<WorldRenderer>( renderer = std::make_unique<WorldRenderer>(
engine, *frontend, *player engine, *frontend, *player
); );
hud = std::make_unique<Hud>(engine, *frontend, *player); hud = std::make_unique<Hud>(engine, *frontend, *player);
decorator = std::make_unique<Decorator>( decorator = std::make_unique<Decorator>(
engine, *controller, *worldRenderer, assets, *player engine, *controller, *renderer, assets, *player
); );
keepAlive(settings.graphics.backlight.observe([=](bool) { keepAlive(settings.graphics.backlight.observe([=](bool) {
player->chunks->saveAndClear(); player->chunks->saveAndClear();
worldRenderer->clear(); renderer->clear();
})); }));
keepAlive(settings.graphics.denseRender.observe([=](bool) { keepAlive(settings.graphics.denseRender.observe([=](bool) {
player->chunks->saveAndClear(); player->chunks->saveAndClear();
worldRenderer->clear(); renderer->clear();
frontend->getContentGfxCache().refresh(); frontend->getContentGfxCache().refresh();
})); }));
keepAlive(settings.camera.fov.observe([=](double value) { keepAlive(settings.camera.fov.observe([=](double value) {
@ -85,22 +89,35 @@ LevelScreen::LevelScreen(
})); }));
keepAlive(Events::getBinding(BIND_CHUNKS_RELOAD).onactived.add([=](){ keepAlive(Events::getBinding(BIND_CHUNKS_RELOAD).onactived.add([=](){
player->chunks->saveAndClear(); player->chunks->saveAndClear();
worldRenderer->clear(); renderer->clear();
return false; return false;
})); }));
animator = std::make_unique<TextureAnimator>(); animator = std::make_unique<TextureAnimator>();
animator->addAnimations(assets.getAnimations()); animator->addAnimations(assets.getAnimations());
loadDecorations();
initializeContent(); 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() { void LevelScreen::initializeContent() {
auto& content = controller->getLevel()->content; auto& content = controller->getLevel()->content;
for (auto& entry : content.getPacks()) { for (auto& entry : content.getPacks()) {
initializePack(entry.second.get()); 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) { void LevelScreen::initializePack(ContentPackRuntime* pack) {
@ -116,15 +133,22 @@ void LevelScreen::initializePack(ContentPackRuntime* pack) {
} }
} }
LevelScreen::~LevelScreen() { void LevelScreen::loadDecorations() {
if (!controller->getLevel()->getWorld()->isNameless()) { if (!io::exists(CLIENT_FILE)) {
saveWorldPreview(); return;
} }
scripting::on_frontend_close(); auto data = io::read_object(CLIENT_FILE);
// unblock all bindings if (data.has("weather")) {
Events::enableBindings(); renderer->getWeather().deserialize(data["weather"]);
controller->onWorldQuit(); }
engine.getPaths().setCurrentWorldFolder(""); }
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() { void LevelScreen::saveWorldPreview() {
@ -144,7 +168,7 @@ void LevelScreen::saveWorldPreview() {
Viewport viewport(previewSize * 1.5, previewSize); Viewport viewport(previewSize * 1.5, previewSize);
DrawContext ctx(&pctx, viewport, batch.get()); 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(); auto image = postProcessing->toImage();
image->flipY(); image->flipY();
imageio::write("world:preview.png", image.get()); imageio::write("world:preview.png", image.get());
@ -164,26 +188,15 @@ void LevelScreen::updateHotkeys() {
if (Events::jpressed(keycode::F3)) { if (Events::jpressed(keycode::F3)) {
debug = !debug; debug = !debug;
hud->setDebug(debug); hud->setDebug(debug);
worldRenderer->setDebug(debug); renderer->setDebug(debug);
} }
} }
void LevelScreen::update(float delta) { void LevelScreen::updateAudio() {
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();
auto player = playerController->getPlayer(); auto player = playerController->getPlayer();
auto camera = player->currentCamera; auto camera = player->currentCamera;
bool paused = hud->isPause(); bool paused = hud->isPause();
audio::get_channel("regular")->setPaused(paused); audio::get_channel("regular")->setPaused(paused);
audio::get_channel("ambient")->setPaused(paused); audio::get_channel("ambient")->setPaused(paused);
glm::vec3 velocity {}; glm::vec3 velocity {};
@ -196,20 +209,34 @@ void LevelScreen::update(float delta) {
camera->dir, camera->dir,
glm::vec3(0, 1, 0) glm::vec3(0, 1, 0)
); );
const auto& settings = engine.getSettings();
if (!hud->isPause()) {
level->getWorld()->updateTimers(delta);
animator->update(delta);
} }
if (!hud->isPause()) {
void LevelScreen::update(float delta) {
auto& gui = *engine.getGUI();
if (!gui.isFocusCaught()) {
updateHotkeys();
}
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); playerController->update(delta, !inputLocked);
} }
controller->update(glm::min(delta, 0.2f), hud->isPause()); controller->update(glm::min(delta, 0.2f), paused);
playerController->postUpdate(delta, !inputLocked, hud->isPause()); playerController->postUpdate(delta, !inputLocked, paused);
hud->update(hudVisible); 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) { void LevelScreen::draw(float delta) {
@ -221,8 +248,8 @@ void LevelScreen::draw(float delta) {
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta()); scripting::on_entities_render(engine.getTime().getDelta());
} }
worldRenderer->draw( renderer->draw(
ctx, *camera, hudVisible, hud->isPause(), delta, postProcessing.get() ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
); );
if (hudVisible) { if (hudVisible) {
@ -236,8 +263,3 @@ void LevelScreen::onEngineShutdown() {
} }
controller->saveWorld(); controller->saveWorld();
} }
LevelController* LevelScreen::getLevelController() const {
return controller.get();
}

View File

@ -15,12 +15,14 @@ class PostProcessing;
class ContentPackRuntime; class ContentPackRuntime;
class Decorator; class Decorator;
class Level; class Level;
class World;
class LevelScreen : public Screen { class LevelScreen : public Screen {
World& world;
std::unique_ptr<LevelFrontend> frontend; std::unique_ptr<LevelFrontend> frontend;
std::unique_ptr<LevelController> controller; std::unique_ptr<LevelController> controller;
std::unique_ptr<PlayerController> playerController; std::unique_ptr<PlayerController> playerController;
std::unique_ptr<WorldRenderer> worldRenderer; std::unique_ptr<WorldRenderer> renderer;
std::unique_ptr<TextureAnimator> animator; std::unique_ptr<TextureAnimator> animator;
std::unique_ptr<PostProcessing> postProcessing; std::unique_ptr<PostProcessing> postProcessing;
std::unique_ptr<Decorator> decorator; std::unique_ptr<Decorator> decorator;
@ -33,6 +35,10 @@ class LevelScreen : public Screen {
void updateHotkeys(); void updateHotkeys();
void initializeContent(); void initializeContent();
void initializePack(ContentPackRuntime* pack); void initializePack(ContentPackRuntime* pack);
void loadDecorations();
void saveDecorations();
void updateAudio();
public: public:
LevelScreen( LevelScreen(
Engine& engine, std::unique_ptr<Level> level, int64_t localPlayer Engine& engine, std::unique_ptr<Level> level, int64_t localPlayer
@ -43,6 +49,4 @@ public:
void draw(float delta) override; void draw(float delta) override;
void onEngineShutdown() override; void onEngineShutdown() override;
LevelController* getLevelController() const;
}; };

View File

@ -7,6 +7,7 @@
#include "assets/assets_util.hpp" #include "assets/assets_util.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/Chunk.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
@ -17,6 +18,8 @@
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "io/io.hpp" #include "io/io.hpp"
#include "audio/audio.hpp"
#include "maths/util.hpp"
namespace fs = std::filesystem; 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<Emitter>(
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<audio::Sound>(weather.fall.noise);
audio::play(
sound,
pos,
false,
intensity * intensity,
1.0f,
false,
audio::PRIORITY_LOW,
audio::get_channel_index("ambient")
);
}
}
void Decorator::update( 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; int index = currentIndex;
currentIndex = (currentIndex + BIG_PRIME) % UPDATE_BLOCKS; currentIndex = (currentIndex + BIG_PRIME) % UPDATE_BLOCKS;
@ -93,7 +163,8 @@ void Decorator::update(
int lz = (index / UPDATE_AREA_DIAMETER) % UPDATE_AREA_DIAMETER; int lz = (index / UPDATE_AREA_DIAMETER) % UPDATE_AREA_DIAMETER;
int ly = (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)) { if (auto vox = chunks.get(pos)) {
const auto& def = indices.blocks.require(vox->id); const auto& def = indices.blocks.require(vox->id);
@ -103,11 +174,7 @@ void Decorator::update(
} }
} }
void Decorator::update(float delta, const Camera& camera) { void Decorator::updateBlockEmitters(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);
}
const auto& chunks = *player.chunks; const auto& chunks = *player.chunks;
const auto& indices = *level.content.getIndices(); const auto& indices = *level.content.getIndices();
auto iter = blockEmitters.begin(); auto iter = blockEmitters.begin();
@ -139,7 +206,9 @@ void Decorator::update(float delta, const Camera& camera) {
} }
iter++; iter++;
} }
}
void Decorator::updateTextNotes() {
for (const auto& [id, player] : *level.players) { for (const auto& [id, player] : *level.players) {
if (id == this->player.getId() || if (id == this->player.getId() ||
playerTexts.find(id) != playerTexts.end()) { 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<audio::Sound>("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<int>(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();
}

View File

@ -18,6 +18,8 @@ class Block;
class Engine; class Engine;
class LevelController; class LevelController;
class WorldRenderer; class WorldRenderer;
class Weather;
struct WeatherPreset;
class Decorator { class Decorator {
Engine& engine; Engine& engine;
@ -29,10 +31,23 @@ class Decorator {
std::unordered_map<int64_t, u64id_t> playerTexts; std::unordered_map<int64_t, u64id_t> playerTexts;
int currentIndex = 0; int currentIndex = 0;
NotePreset playerNamePreset {}; NotePreset playerNamePreset {};
float thunderTimer = 0.0f;
void update( 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); void addParticles(const Block& def, const glm::ivec3& pos);
public: public:
Decorator( Decorator(
@ -43,5 +58,9 @@ public:
Player& player Player& player
); );
void update(float delta, const Camera& camera); void update(
float delta,
const Camera& camera,
const Weather& weather
);
}; };

View File

@ -6,10 +6,10 @@
#include "assets/assets_util.hpp" #include "assets/assets_util.hpp"
#include "graphics/core/Shader.hpp" #include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp" #include "graphics/core/Texture.hpp"
#include "graphics/render/MainBatch.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "MainBatch.hpp"
#include "settings.hpp" #include "settings.hpp"
size_t ParticlesRenderer::visibleParticles = 0; size_t ParticlesRenderer::visibleParticles = 0;
@ -179,24 +179,6 @@ void ParticlesRenderer::render(const Camera& camera, float delta) {
} }
} }
void ParticlesRenderer::gc() {
std::set<Emitter*> 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 { Emitter* ParticlesRenderer::getEmitter(u64id_t id) const {
const auto& found = emitters.find(id); const auto& found = emitters.find(id);
if (found == emitters.end()) { if (found == emitters.end()) {

View File

@ -40,12 +40,6 @@ public:
u64id_t add(std::unique_ptr<Emitter> emitter); u64id_t add(std::unique_ptr<Emitter> 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 /// @brief Get emitter by UID
/// @return Emitter or nullptr /// @return Emitter or nullptr
Emitter* getEmitter(u64id_t id) const; Emitter* getEmitter(u64id_t id) const;

View File

@ -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<MainBatch>(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<CHUNK_W>(x);
int cz = floordiv<CHUNK_D>(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<int, (depth + 1) * 2> 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<Texture>(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<float>(lx) +
face.front * static_cast<float>(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();
}

View File

@ -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<MainBatch> 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);
};

View File

@ -44,6 +44,7 @@
#include "graphics/core/Font.hpp" #include "graphics/core/Font.hpp"
#include "BlockWrapsRenderer.hpp" #include "BlockWrapsRenderer.hpp"
#include "ParticlesRenderer.hpp" #include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "TextsRenderer.hpp" #include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp" #include "ChunksRenderer.hpp"
#include "GuidesRenderer.hpp" #include "GuidesRenderer.hpp"
@ -86,7 +87,10 @@ WorldRenderer::WorldRenderer(
)), )),
blockWraps( blockWraps(
std::make_unique<BlockWrapsRenderer>(assets, level, *player.chunks) std::make_unique<BlockWrapsRenderer>(assets, level, *player.chunks)
) { ),
precipitation(std::make_unique<PrecipitationRenderer>(
assets, level, *player.chunks, &engine.getSettings().graphics
)) {
auto& settings = engine.getSettings(); auto& settings = engine.getSettings();
level.events->listen( level.events->listen(
LevelEventType::CHUNK_HIDDEN, LevelEventType::CHUNK_HIDDEN,
@ -115,6 +119,9 @@ void WorldRenderer::setupWorldShader(
shader.uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_gamma", settings.graphics.gamma.get());
shader.uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogFactor", fogFactor);
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); 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.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform3f("u_cameraPos", camera.position); shader.uniform3f("u_cameraPos", camera.position);
@ -160,6 +167,7 @@ void WorldRenderer::renderLevel(
} }
entityShader.uniform1i("u_alphaClip", true); entityShader.uniform1i("u_alphaClip", true);
entityShader.uniform1f("u_opacity", 1.0f);
level.entities->render( level.entities->render(
assets, assets,
*modelBatch, *modelBatch,
@ -188,6 +196,21 @@ void WorldRenderer::renderLevel(
scripting::on_frontend_render(); scripting::on_frontend_render();
} }
setupWorldShader(entityShader, camera, settings, fogFactor);
std::array<const WeatherPreset*, 2> 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(); skybox->unbind();
} }
@ -306,36 +329,46 @@ void WorldRenderer::draw(
Camera& camera, Camera& camera,
bool hudVisible, bool hudVisible,
bool pause, bool pause,
float delta, float uiDelta,
PostProcessing* postProcessing PostProcessing& postProcessing
) { ) {
timer += delta * !pause; float delta = uiDelta * !pause;
timer += delta;
weather.update(delta);
auto world = level.getWorld(); auto world = level.getWorld();
const Viewport& vp = pctx.getViewport(); const Viewport& vp = pctx.getViewport();
camera.aspect = vp.getWidth() / static_cast<float>(vp.getHeight()); camera.aspect = vp.getWidth() / static_cast<float>(vp.getHeight());
const auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
const auto& worldInfo = world->getInfo(); const auto& worldInfo = world->getInfo();
skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4); 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, mie, 4);
const auto& assets = *engine.getAssets(); const auto& assets = *engine.getAssets();
auto& linesShader = assets.require<Shader>("lines"); auto& linesShader = assets.require<Shader>("lines");
/* World render scope with diegetic HUD included */ { /* World render scope with diegetic HUD included */ {
DrawContext wctx = pctx.sub(); DrawContext wctx = pctx.sub();
postProcessing->use(wctx); postProcessing.use(wctx);
Window::clearDepth(); Window::clearDepth();
// Drawing background sky plane // 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 */ { /* Actually world render with depth buffer on */ {
DrawContext ctx = wctx.sub(); DrawContext ctx = wctx.sub();
ctx.setDepthTest(true); ctx.setDepthTest(true);
ctx.setCullFace(true); ctx.setCullFace(true);
renderLevel(ctx, camera, settings, delta, pause, hudVisible); renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible);
// Debug lines // Debug lines
if (hudVisible) { if (hudVisible) {
if (debug) { if (debug) {
@ -344,7 +377,7 @@ void WorldRenderer::draw(
); );
} }
if (player.currentCamera == player.fpCamera) { if (player.currentCamera == player.fpCamera) {
renderHands(camera, delta * !pause); renderHands(camera, delta);
} }
} }
} }
@ -355,12 +388,12 @@ void WorldRenderer::draw(
renderBlockOverlay(wctx); renderBlockOverlay(wctx);
} }
// Rendering fullscreen quad with // Rendering fullscreen quad
auto screenShader = assets.get<Shader>("screen"); auto screenShader = assets.get<Shader>("screen");
screenShader->use(); screenShader->use();
screenShader->uniform1f("u_timer", timer); screenShader->uniform1f("u_timer", timer);
screenShader->uniform1f("u_dayTime", worldInfo.daytime); screenShader->uniform1f("u_dayTime", worldInfo.daytime);
postProcessing->render(pctx, screenShader); postProcessing.render(pctx, screenShader);
} }
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
@ -414,3 +447,7 @@ void WorldRenderer::clear() {
void WorldRenderer::setDebug(bool flag) { void WorldRenderer::setDebug(bool flag) {
debug = flag; debug = flag;
} }
Weather& WorldRenderer::getWeather() {
return weather;
}

View File

@ -10,6 +10,9 @@
#include "typedefs.hpp" #include "typedefs.hpp"
#include "presets/WeatherPreset.hpp"
#include "world/Weather.hpp"
class Level; class Level;
class Player; class Player;
class Camera; class Camera;
@ -18,6 +21,7 @@ class LineBatch;
class ChunksRenderer; class ChunksRenderer;
class ParticlesRenderer; class ParticlesRenderer;
class BlockWrapsRenderer; class BlockWrapsRenderer;
class PrecipitationRenderer;
class GuidesRenderer; class GuidesRenderer;
class TextsRenderer; class TextsRenderer;
class Shader; class Shader;
@ -43,6 +47,7 @@ class WorldRenderer {
std::unique_ptr<GuidesRenderer> guides; std::unique_ptr<GuidesRenderer> guides;
std::unique_ptr<Skybox> skybox; std::unique_ptr<Skybox> skybox;
std::unique_ptr<ModelBatch> modelBatch; std::unique_ptr<ModelBatch> modelBatch;
Weather weather {};
float timer = 0.0f; float timer = 0.0f;
bool debug = false; bool debug = false;
@ -71,6 +76,7 @@ public:
std::unique_ptr<TextsRenderer> texts; std::unique_ptr<TextsRenderer> texts;
std::unique_ptr<ParticlesRenderer> particles; std::unique_ptr<ParticlesRenderer> particles;
std::unique_ptr<BlockWrapsRenderer> blockWraps; std::unique_ptr<BlockWrapsRenderer> blockWraps;
std::unique_ptr<PrecipitationRenderer> precipitation;
static bool showChunkBorders; static bool showChunkBorders;
static bool showEntitiesDebug; static bool showEntitiesDebug;
@ -84,7 +90,7 @@ public:
bool hudVisible, bool hudVisible,
bool pause, bool pause,
float delta, float delta,
PostProcessing* postProcessing PostProcessing& postProcessing
); );
/// @brief Render level without diegetic interface /// @brief Render level without diegetic interface
@ -103,4 +109,6 @@ public:
void clear(); void clear();
void setDebug(bool flag); void setDebug(bool flag);
Weather& getWeather();
}; };

View File

@ -9,4 +9,10 @@ public:
virtual ~Serializable() {} virtual ~Serializable() {}
virtual dv::value serialize() const = 0; virtual dv::value serialize() const = 0;
virtual void deserialize(const dv::value& src) = 0; virtual void deserialize(const dv::value& src) = 0;
void deserializeOpt(const dv::optionalvalue& opt) {
if (opt.ptr) {
deserialize(*opt.ptr);
}
}
}; };

View File

@ -46,6 +46,7 @@ extern const luaL_Reg utf8lib[];
extern const luaL_Reg vec2lib[]; // vecn.cpp extern const luaL_Reg vec2lib[]; // vecn.cpp
extern const luaL_Reg vec3lib[]; // vecn.cpp extern const luaL_Reg vec3lib[]; // vecn.cpp
extern const luaL_Reg vec4lib[]; // vecn.cpp extern const luaL_Reg vec4lib[]; // vecn.cpp
extern const luaL_Reg weatherlib[];
extern const luaL_Reg worldlib[]; extern const luaL_Reg worldlib[];
// Components // Components

View File

@ -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<l_change>},
{"get_current", lua::wrap<l_get_current>},
{"get_current_data", lua::wrap<l_get_current_data>},
{"get_fall_intensity", lua::wrap<l_get_fall_intensity>},
{"is_transition", lua::wrap<l_is_transition>},
{NULL, NULL}
};

View File

@ -35,6 +35,7 @@ void scripting::on_frontend_init(Hud* hud, WorldRenderer* renderer) {
lua::openlib(L, "hud", hudlib); lua::openlib(L, "hud", hudlib);
lua::openlib(L, "gfx", "blockwraps", blockwrapslib); lua::openlib(L, "gfx", "blockwraps", blockwrapslib);
lua::openlib(L, "gfx", "particles", particleslib); lua::openlib(L, "gfx", "particles", particleslib);
lua::openlib(L, "gfx", "weather", weatherlib);
lua::openlib(L, "gfx", "text3d", text3dlib); lua::openlib(L, "gfx", "text3d", text3dlib);
load_script("hud_classes.lua"); load_script("hud_classes.lua");

View File

@ -20,6 +20,8 @@ namespace util {
class PseudoRandom { class PseudoRandom {
unsigned short seed; unsigned short seed;
public: public:
PseudoRandom(unsigned short seed) : seed(seed) {}
PseudoRandom() { PseudoRandom() {
seed = static_cast<unsigned short>(time(0)); seed = static_cast<unsigned short>(time(0));
} }

View File

@ -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);
}

View File

@ -0,0 +1,65 @@
#pragma once
#include <optional>
#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<ParticlesPreset> 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;
};

View File

@ -0,0 +1,55 @@
#pragma once
#include <array>
#include <stdexcept>
namespace util {
template<typename T, int diameter, typename CoordT = int>
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<T, diameter * diameter> arr;
CoordT centerX = 0, centerY = 0;
};
}

67
src/world/Weather.hpp Normal file
View File

@ -0,0 +1,67 @@
#pragma once
#include <string>
#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);
}
};

View File

@ -231,8 +231,8 @@ dv::value WorldInfo::serialize() const {
timeobj["day-time-speed"] = daytimeSpeed; timeobj["day-time-speed"] = daytimeSpeed;
timeobj["total-time"] = totalTime; timeobj["total-time"] = totalTime;
auto& weatherobj = root.object("weather"); root["weather"] = dv::object();
weatherobj["fog"] = fog; root["weather"]["fog"] = fog;
root["next-inventory-id"] = nextInventoryId; root["next-inventory-id"] = nextInventoryId;
root["next-entity-id"] = nextEntityId; root["next-entity-id"] = nextEntityId;

View File

@ -4,9 +4,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "io/fwd.hpp"
#include "content/ContentPack.hpp" #include "content/ContentPack.hpp"
#include "interfaces/Serializable.hpp" #include "interfaces/Serializable.hpp"
#include "io/fwd.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/timeutil.hpp" #include "util/timeutil.hpp"
@ -33,7 +33,6 @@ struct WorldInfo : public Serializable {
/// 0.5 - is noon /// 0.5 - is noon
float daytime = timeutil::time_value(10, 00, 00); float daytime = timeutil::time_value(10, 00, 00);
// looking bad
float daytimeSpeed = 1.0f; float daytimeSpeed = 1.0f;
/// @brief total time passed in the world (not depending on daytimeSpeed) /// @brief total time passed in the world (not depending on daytimeSpeed)