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": [
"blocks/door_open",
"blocks/door_close",
"events/pickup"
"events/pickup",
"ambient/rain",
"ambient/thunder"
],
"models": [
"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/folder_icon",
"gui/settings_icon",
"misc/rain",
"misc/snow",
"gui/check_mark",
"gui/left_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
)
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",
}

View File

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

View File

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

View File

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

View File

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

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");
inline const io::path CLIENT_FILE = "world:client/environment.json";
LevelScreen::LevelScreen(
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();
auto& settings = engine.getSettings();
@ -62,22 +66,22 @@ LevelScreen::LevelScreen(
frontend = std::make_unique<LevelFrontend>(
player, controller.get(), assets, settings
);
worldRenderer = std::make_unique<WorldRenderer>(
renderer = std::make_unique<WorldRenderer>(
engine, *frontend, *player
);
hud = std::make_unique<Hud>(engine, *frontend, *player);
decorator = std::make_unique<Decorator>(
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<TextureAnimator>();
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();
}

View File

@ -15,12 +15,14 @@ class PostProcessing;
class ContentPackRuntime;
class Decorator;
class Level;
class World;
class LevelScreen : public Screen {
World& world;
std::unique_ptr<LevelFrontend> frontend;
std::unique_ptr<LevelController> controller;
std::unique_ptr<PlayerController> playerController;
std::unique_ptr<WorldRenderer> worldRenderer;
std::unique_ptr<WorldRenderer> renderer;
std::unique_ptr<TextureAnimator> animator;
std::unique_ptr<PostProcessing> postProcessing;
std::unique_ptr<Decorator> 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> level, int64_t localPlayer
@ -43,6 +49,4 @@ public:
void draw(float delta) override;
void onEngineShutdown() override;
LevelController* getLevelController() const;
};

View File

@ -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<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(
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<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 LevelController;
class WorldRenderer;
class Weather;
struct WeatherPreset;
class Decorator {
Engine& engine;
@ -29,10 +31,23 @@ class Decorator {
std::unordered_map<int64_t, u64id_t> 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
);
};

View File

@ -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<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 {
const auto& found = emitters.find(id);
if (found == emitters.end()) {

View File

@ -40,12 +40,6 @@ public:
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
/// @return Emitter or nullptr
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 "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<BlockWrapsRenderer>(assets, level, *player.chunks)
) {
),
precipitation(std::make_unique<PrecipitationRenderer>(
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<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();
}
@ -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<float>(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<Shader>("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<Shader>("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;
}

View File

@ -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<GuidesRenderer> guides;
std::unique_ptr<Skybox> skybox;
std::unique_ptr<ModelBatch> modelBatch;
Weather weather {};
float timer = 0.0f;
bool debug = false;
@ -71,6 +76,7 @@ public:
std::unique_ptr<TextsRenderer> texts;
std::unique_ptr<ParticlesRenderer> particles;
std::unique_ptr<BlockWrapsRenderer> blockWraps;
std::unique_ptr<PrecipitationRenderer> 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();
};

View File

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

View File

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

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, "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");

View File

@ -20,6 +20,8 @@ namespace util {
class PseudoRandom {
unsigned short seed;
public:
PseudoRandom(unsigned short seed) : seed(seed) {}
PseudoRandom() {
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["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;

View File

@ -4,9 +4,9 @@
#include <string>
#include <vector>
#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)