diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 874d3e885..6e15a2b5f 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -39,6 +39,7 @@ core.features = { dynamic_add_media_filepath = true, lsystem_decoration_type = true, item_meta_range = true, + particle_blend_clip = true, } function core.has_feature(arg) diff --git a/doc/lua_api.md b/doc/lua_api.md index 329f17b01..4e26e9841 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5431,6 +5431,8 @@ Utilities lsystem_decoration_type = true, -- Overrideable pointing range using the itemstack meta key `"range"` (5.9.0) item_meta_range = true, + -- Particles can specify a "clip" blend mode + particle_blend_clip = true, } ``` @@ -10562,7 +10564,9 @@ Used by `minetest.add_particle`. texture = "image.png", -- The texture of the particle -- v5.6.0 and later: also supports the table format described in the - -- following section + -- following section, but due to a bug this did not take effect + -- (beyond the texture name). + -- v5.9.0 and later: fixes the bug. playername = "singleplayer", -- Optional, if specified spawns particle only on the player's client @@ -10909,6 +10913,14 @@ texture = { -- (default) blends transparent pixels with those they are drawn atop -- according to the alpha channel of the source texture. useful for -- e.g. material objects like rocks, dirt, smoke, or node chunks + blend = "clip", + -- pixels are either fully opaque or fully transparent, + -- depending on whether alpha is greater than or less than 50% + -- (similar to `use_texture_alpha = "clip"` for nodes). + -- this fixes rendering bugs (invisibility) that occur when particles + -- interact with translucent nodes + -- (see https://github.com/minetest/minetest/issues/3761). + -- in the future, it may be useful for better performance. blend = "add", -- adds the value of pixels to those underneath them, modulo the sources -- alpha channel. useful for e.g. bright light effects like sparks or fire diff --git a/games/devtest/mods/testtools/particles.lua b/games/devtest/mods/testtools/particles.lua index 18efe2572..eea9faac1 100644 --- a/games/devtest/mods/testtools/particles.lua +++ b/games/devtest/mods/testtools/particles.lua @@ -1,14 +1,27 @@ +local function spawn_clip_test_particle(pos) + minetest.add_particle({ + pos = pos, + size = 5, + expirationtime = 10, + texture = { + name = "testtools_particle_clip.png", + blend = "clip", + }, + }) +end + minetest.register_tool("testtools:particle_spawner", { - description = "Particle Spawner".."\n".. + description = table.concat({ + "Particle Spawner", "Punch: Spawn random test particle", + "Place: Spawn clip test particle", + }, "\n"), inventory_image = "testtools_particle_spawner.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing, true) if pos == nil then - if user then - pos = user:get_pos() - end + pos = assert(user):get_pos() end pos = vector.add(pos, {x=0, y=0.5, z=0}) local tex, anim @@ -32,5 +45,12 @@ minetest.register_tool("testtools:particle_spawner", { glow = math.random(0, 5), }) end, + on_place = function(itemstack, user, pointed_thing) + local pos = assert(minetest.get_pointed_thing_position(pointed_thing, true)) + spawn_clip_test_particle(pos) + end, + on_secondary_use = function(_, user) + spawn_clip_test_particle(assert(user):get_pos()) + end, }) diff --git a/games/devtest/mods/testtools/textures/testtools_particle_clip.png b/games/devtest/mods/testtools/textures/testtools_particle_clip.png new file mode 100644 index 000000000..5fb9ad09a Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_particle_clip.png differ diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 14384f3b8..f3793b008 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -18,7 +18,10 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "particles.h" +#include +#include #include +#include #include "client.h" #include "collision.h" #include "client/content_cao.h" @@ -75,36 +78,30 @@ Particle::Particle( // Set material { // translate blend modes to GL blend functions - video::E_BLEND_FACTOR bfsrc, bfdst; - video::E_BLEND_OPERATION blendop; + bool blend = true; + video::E_BLEND_FACTOR bfsrc = video::EBF_SRC_ALPHA, bfdst = video::EBF_DST_ALPHA; + video::E_BLEND_OPERATION blendop = video::EBO_ADD; const auto blendmode = texture.tex != nullptr ? texture.tex->blendmode : ParticleParamTypes::BlendMode::alpha; switch (blendmode) { - case ParticleParamTypes::BlendMode::add: - bfsrc = video::EBF_SRC_ALPHA; - bfdst = video::EBF_DST_ALPHA; - blendop = video::EBO_ADD; - break; - + case ParticleParamTypes::BlendMode::clip: + blend = false; + break; + case ParticleParamTypes::BlendMode::alpha: + bfdst = video::EBF_ONE_MINUS_SRC_ALPHA; + break; + case ParticleParamTypes::BlendMode::add: break; case ParticleParamTypes::BlendMode::sub: - bfsrc = video::EBF_SRC_ALPHA; - bfdst = video::EBF_DST_ALPHA; blendop = video::EBO_REVSUBTRACT; - break; - + break; case ParticleParamTypes::BlendMode::screen: bfsrc = video::EBF_ONE; bfdst = video::EBF_ONE_MINUS_SRC_COLOR; - blendop = video::EBO_ADD; - break; - - default: // includes ParticleParamTypes::BlendMode::alpha - bfsrc = video::EBF_SRC_ALPHA; - bfdst = video::EBF_ONE_MINUS_SRC_ALPHA; - blendop = video::EBO_ADD; - break; + break; + case ParticleParamTypes::BlendMode::BlendMode_END: + throw std::logic_error("invalid blend mode"); } // Texture @@ -120,12 +117,16 @@ Particle::Particle( m_material.ZWriteEnable = video::EZW_AUTO; // enable alpha blending and set blend mode - m_material.MaterialType = video::EMT_ONETEXTURE_BLEND; - m_material.MaterialTypeParam = video::pack_textureBlendFunc( - bfsrc, bfdst, - video::EMFN_MODULATE_1X, - video::EAS_TEXTURE | video::EAS_VERTEX_COLOR); - m_material.BlendOperation = blendop; + if (blend) { + m_material.MaterialType = video::EMT_ONETEXTURE_BLEND; + m_material.MaterialTypeParam = video::pack_textureBlendFunc( + bfsrc, bfdst, + video::EMFN_MODULATE_1X, + video::EAS_TEXTURE | video::EAS_VERTEX_COLOR); + m_material.BlendOperation = blendop; + } else { + m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + } m_material.setTexture(0, m_texture.ref); } @@ -141,8 +142,11 @@ Particle::Particle( void Particle::OnRegisterSceneNode() { - if (IsVisible) - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT); + if (IsVisible) { + bool solid = m_material.MaterialType == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + SceneManager->registerNodeForRendering(this, + solid ? scene::ESNRP_SOLID : scene::ESNRP_TRANSPARENT_EFFECT); + } ISceneNode::OnRegisterSceneNode(); } diff --git a/src/particles.cpp b/src/particles.cpp index c67d72711..977fd4e10 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "particles.h" +#include "exceptions.h" #include using namespace ParticleParamTypes; @@ -204,8 +205,9 @@ void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, bool n FlagT flags = 0; if (animated) flags |= FlagT(ParticleTextureFlags::animated); - if (blendmode != BlendMode::alpha) - flags |= FlagT(blendmode) << 1; + // Default to `blend = "alpha"` for older clients that don't support `blend = "clip"` + flags |= FlagT(protocol_ver < 44 && blendmode == BlendMode::clip + ? BlendMode::alpha : blendmode) << 1; serializeParameterValue(os, flags); alpha.serialize(os); @@ -221,9 +223,14 @@ void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, bool { FlagT flags = 0; deSerializeParameterValue(is, flags); + // Backwards compatibility: Older clients don't send these, leave them at the defaults + if (is.eof()) + return; animated = !!(flags & FlagT(ParticleTextureFlags::animated)); blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1); + if (blendmode >= BlendMode::BlendMode_END) + throw SerializationError("invalid blend mode"); alpha.deSerialize(is); scale.deSerialize(is); @@ -254,6 +261,7 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const writeV3F32(os, drag); jitter.serialize(os); bounce.serialize(os); + texture.serialize(os, protocol_ver, true); } template @@ -291,4 +299,5 @@ void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver) return; jitter.deSerialize(is); bounce.deSerialize(is); + texture.deSerialize(is, protocol_ver, true); } diff --git a/src/particles.h b/src/particles.h index b9fbe38c8..31852fc02 100644 --- a/src/particles.h +++ b/src/particles.h @@ -249,7 +249,8 @@ namespace ParticleParamTypes } enum class AttractorKind : u8 { none, point, line, plane }; - enum class BlendMode : u8 { alpha, add, sub, screen }; + // Note: Allows at most 8 enum members (due to how this is serialized) + enum class BlendMode : u8 { alpha, add, sub, screen, clip, BlendMode_END }; // these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations using v3fRange = RangedParameter; diff --git a/src/script/lua_api/l_particleparams.h b/src/script/lua_api/l_particleparams.h index 0ad1541b4..01b0c150c 100644 --- a/src/script/lua_api/l_particleparams.h +++ b/src/script/lua_api/l_particleparams.h @@ -133,13 +133,14 @@ namespace LuaParticleParams {(int)BlendMode::add, "add"}, {(int)BlendMode::sub, "sub"}, {(int)BlendMode::screen, "screen"}, + {(int)BlendMode::clip, "clip"}, {0, nullptr}, }; luaL_checktype(L, -1, LUA_TSTRING); int v = (int)BlendMode::alpha; if (!string_to_enum(opts, v, lua_tostring(L, -1))) { - throw LuaError("blend mode must be one of ('alpha', 'add', 'sub', 'screen')"); + throw LuaError("blend mode must be one of ('alpha', 'clip', 'add', 'sub', 'screen')"); } ret = (BlendMode)v; }