From c2e2b9794423a5f05db5d461def9fbaef56a6953 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 11 Oct 2025 19:37:30 +0200 Subject: [PATCH] Respect node alpha node for inventory drawing (#16556) --- .../mods/testnodes/performance_test_nodes.lua | 37 +++--- src/client/wieldmesh.cpp | 117 +++++++++++++----- src/client/wieldmesh.h | 17 ++- src/gui/drawItemStack.cpp | 7 +- 4 files changed, 114 insertions(+), 64 deletions(-) diff --git a/games/devtest/mods/testnodes/performance_test_nodes.lua b/games/devtest/mods/testnodes/performance_test_nodes.lua index fc22db34a7..7cc80c5eff 100644 --- a/games/devtest/mods/testnodes/performance_test_nodes.lua +++ b/games/devtest/mods/testnodes/performance_test_nodes.lua @@ -2,29 +2,22 @@ local S = core.get_translator("testnodes") --- Complex mesh -core.register_node("testnodes:performance_mesh_clip", { - description = S("Performance Test Node") .. "\n" .. S("Marble with 'clip' transparency"), - drawtype = "mesh", - mesh = "testnodes_marble_glass.obj", - tiles = {"testnodes_marble_glass.png"}, - paramtype = "light", - use_texture_alpha = "clip", +for use_texture_alpha, description in pairs({ + opaque = S("Marble with 'opaque' transparency"), + clip = S("Marble with 'clip' transparency"), + blend = S("Marble with 'blend' transparency"), +}) do + core.register_node("testnodes:performance_mesh_" .. use_texture_alpha, { + description = S("Performance Test Node") .. "\n" .. description, + drawtype = "mesh", + mesh = "testnodes_marble_glass.obj", + tiles = {"testnodes_marble_glass.png"}, + paramtype = "light", + use_texture_alpha = use_texture_alpha, - groups = {dig_immediate=3}, -}) - --- Complex mesh, alpha blending -core.register_node("testnodes:performance_mesh_blend", { - description = S("Performance Test Node") .. "\n" .. S("Marble with 'blend' transparency"), - drawtype = "mesh", - mesh = "testnodes_marble_glass.obj", - tiles = {"testnodes_marble_glass.png"}, - paramtype = "light", - use_texture_alpha = "blend", - - groups = {dig_immediate=3}, -}) + groups = {dig_immediate=3}, + }) +end -- Overlay core.register_node("testnodes:performance_overlay_clip", { diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 35aa8ec27d..36d9525fbe 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -31,9 +31,10 @@ #define MIN_EXTRUSION_MESH_RESOLUTION 16 #define MAX_EXTRUSION_MESH_RESOLUTION 512 -ItemMeshBufferInfo::ItemMeshBufferInfo(const TileLayer &layer) : +ItemMeshBufferInfo::ItemMeshBufferInfo(int layer_num, const TileLayer &layer) : override_color(layer.color), override_color_set(layer.has_color), + layer(layer_num), animation_info((layer.material_flags & MATERIAL_FLAG_ANIMATION) ? std::make_unique(layer) : nullptr) @@ -131,6 +132,51 @@ static video::ITexture *extractTexture(const TileDef &def, const TileLayer &laye return nullptr; } +// (the function name represents the amount of time wasted on all of this) + +static void setAlphaBullshit(video::SMaterial &mat, + AlphaMode mode, bool overlay) +{ + switch (mode) { + case ALPHAMODE_BLEND: + mat.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + mat.MaterialTypeParam = 0; + return; + case ALPHAMODE_OPAQUE: + if (!overlay) { + mat.MaterialType = video::EMT_SOLID; + return; + } + [[fallthrough]]; + case ALPHAMODE_CLIP: + default: + mat.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + mat.MaterialTypeParam = 0.5f; + return; + } +} + +static void setAlphaBullshit(video::SMaterial &mat, IShaderSource *shdsrc, + AlphaMode mode, bool overlay) +{ + MaterialType mt; + switch (mode) { + case ALPHAMODE_BLEND: + mt = TILE_MATERIAL_ALPHA; + break; + case ALPHAMODE_OPAQUE: + mt = overlay ? TILE_MATERIAL_BASIC : TILE_MATERIAL_OPAQUE; + break; + case ALPHAMODE_CLIP: + default: + mt = TILE_MATERIAL_BASIC; + break; + } + + u32 shader_id = shdsrc->getShader("object_shader", mt, NDT_NORMAL); + mat.MaterialType = shdsrc->getShaderInfo(shader_id).material; +} + /* Caches extrusion meshes so that only one of them per resolution is needed. Also caches one cube (for convenience). @@ -255,8 +301,8 @@ void WieldMeshSceneNode::setExtruded(const TileDef &d0, const TileLayer &l0, extractTexture(d1, l1, tsrc), wield_scale); // Add color m_buffer_info.clear(); - m_buffer_info.emplace_back(l0.has_color, l0.color); - m_buffer_info.emplace_back(l1.has_color, l1.color); + m_buffer_info.emplace_back(0, l0.has_color, l0.color); + m_buffer_info.emplace_back(1, l1.has_color, l1.color); } void WieldMeshSceneNode::setExtruded(const std::string &image, @@ -359,7 +405,7 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, p.layer.applyMaterialOptions(buf->Material, layer); mesh->addMeshBuffer(buf.get()); - buffer_info->emplace_back(p.layer); + buffer_info->emplace_back(layer, p.layer); } } mesh->recalculateBoundingBox(); @@ -371,17 +417,19 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che ITextureSource *tsrc = client->getTextureSource(); IItemDefManager *idef = client->getItemDefManager(); ItemVisualsManager *item_visuals = client->getItemVisualsManager(); - IShaderSource *shdrsrc = client->getShaderSource(); + IShaderSource *shdsrc = client->getShaderSource(); const NodeDefManager *ndef = client->getNodeDefManager(); const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); - content_t id = ndef->getId(def.name); + + { + // Initialize material type used by setExtruded + u32 shader_id = shdsrc->getShader("object_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); + m_material_type = shdsrc->getShaderInfo(shader_id).material; + } scene::SMesh *mesh = nullptr; - u32 shader_id = shdrsrc->getShader("object_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); - m_material_type = shdrsrc->getShaderInfo(shader_id).material; - // Color-related m_buffer_info.clear(); m_base_color = item_visuals->getItemstackColor(item, client); @@ -393,9 +441,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // If wield_image needs to be checked and is defined, it overrides everything else if (!wield_image.empty() && check_wield_image) { setExtruded(wield_image, wield_overlay, wield_scale, tsrc); - m_buffer_info.emplace_back(); + m_buffer_info.emplace_back(0); // overlay is white, if present - m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF)); + m_buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); // initialize the color setColor(video::SColor(0xFFFFFFFF)); return; @@ -403,7 +451,6 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // Handle nodes if (def.type == ITEM_NODE) { - switch (f.drawtype) { case NDT_AIRLIKE: setExtruded("no_texture_airlike.png", "", @@ -429,7 +476,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che } default: { // Render all other drawtypes like the actual node - MapNode n(id); + MapNode n(ndef->getId(def.name)); if (def.place_param2) n.setParam2(*def.place_param2); @@ -446,9 +493,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che u32 material_count = m_meshnode->getMaterialCount(); for (u32 i = 0; i < material_count; ++i) { video::SMaterial &material = m_meshnode->getMaterial(i); - // FIXME: we should take different alpha modes of the mesh into account here - material.MaterialType = m_material_type; - material.MaterialTypeParam = 0.5f; + // apply node's alpha mode + setAlphaBullshit(material, shdsrc, f.alpha, + m_buffer_info[i].layer == 1); material.forEachTexture([this] (auto &tex) { setMaterialFilters(tex, m_bilinear_filter, m_trilinear_filter, m_anisotropic_filter); @@ -467,9 +514,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che setExtruded("no_texture.png", "", def.wield_scale, tsrc); } - m_buffer_info.emplace_back(); + m_buffer_info.emplace_back(0); // overlay is white, if present - m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF)); + m_buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); // initialize the color setColor(video::SColor(0xFFFFFFFF)); @@ -490,9 +537,9 @@ void WieldMeshSceneNode::setColor(video::SColor c) u8 green = c.getGreen(); u8 blue = c.getBlue(); - const u32 mc = mesh->getMeshBufferCount(); - if (mc > m_buffer_info.size()) - m_buffer_info.resize(mc); + u32 mc = mesh->getMeshBufferCount(); + assert(mc <= m_buffer_info.size()); + mc = std::min(mc, m_buffer_info.size()); for (u32 j = 0; j < mc; j++) { video::SColor bc(m_base_color); m_buffer_info[j].applyOverride(bc); @@ -555,7 +602,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) const NodeDefManager *ndef = client->getNodeDefManager(); const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); - content_t id = ndef->getId(def.name); + assert(result); FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized"); @@ -569,14 +616,16 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) const std::string inventory_overlay = item.getInventoryOverlay(idef); if (!inventory_image.empty()) { mesh = getExtrudedMesh(tsrc, inventory_image, inventory_overlay); - result->buffer_info.emplace_back(); + result->buffer_info.emplace_back(0); // overlay is white, if present - result->buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF)); - // Items with inventory images do not need shading + result->buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); result->needs_shading = false; } else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) { // Fallback image for airlike node mesh = getExtrudedMesh(tsrc, "no_texture_airlike.png", inventory_overlay); + result->buffer_info.emplace_back(0); + // overlay is white, if present + result->buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF)); result->needs_shading = false; } else if (def.type == ITEM_NODE) { switch (f.drawtype) { @@ -587,8 +636,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) extractTexture(f.tiledef[0], l0, tsrc), extractTexture(f.tiledef[1], l1, tsrc)); // Add color - result->buffer_info.emplace_back(l0.has_color, l0.color); - result->buffer_info.emplace_back(l1.has_color, l1.color); + result->buffer_info.emplace_back(0, l0.has_color, l0.color); + result->buffer_info.emplace_back(1, l1.has_color, l1.color); break; } case NDT_PLANTLIKE_ROOTED: { @@ -597,17 +646,17 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) mesh = getExtrudedMesh( extractTexture(f.tiledef_special[0], l0, tsrc), nullptr ); - result->buffer_info.emplace_back(l0.has_color, l0.color); + result->buffer_info.emplace_back(0, l0.has_color, l0.color); break; } default: { // Render all other drawtypes like the actual node - MapNode n(id); + MapNode n(ndef->getId(def.name)); if (def.place_param2) n.setParam2(*def.place_param2); mesh = createGenericNodeMesh(client, n, &result->buffer_info, f); - scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); + scaleMesh(mesh, v3f(0.12f)); break; } } @@ -615,9 +664,9 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); video::SMaterial &material = buf->getMaterial(); - // FIXME: overriding this breaks different alpha modes the mesh may have - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - material.MaterialTypeParam = 0.5f; + // apply node's alpha mode + setAlphaBullshit(material, f.alpha, + result->buffer_info[i].layer == 1); material.forEachTexture([] (auto &tex) { tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; tex.MagFilter = video::ETMAGF_NEAREST; @@ -675,7 +724,7 @@ scene::SMesh *getExtrudedMesh(video::ITexture *texture, tex.MagFilter = video::ETMAGF_NEAREST; }); material.BackfaceCulling = true; - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; material.MaterialTypeParam = 0.5f; } scaleMesh(mesh, v3f(2)); diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 849ba8c896..0b5f7be81a 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -49,13 +49,14 @@ class ItemMeshBufferInfo public: - ItemMeshBufferInfo() = default; + ItemMeshBufferInfo(int layer) : layer(layer) {} - ItemMeshBufferInfo(bool override, video::SColor color) : - override_color(color), override_color_set(override) + ItemMeshBufferInfo(int layer, bool override, video::SColor color) : + override_color(color), override_color_set(override), + layer(layer) {} - ItemMeshBufferInfo(const TileLayer &layer); + ItemMeshBufferInfo(int layer_num, const TileLayer &layer); void applyOverride(video::SColor &dest) const { if (override_color_set) @@ -70,6 +71,9 @@ public: return true; } + // Index of the tile layer this mesh buffer belongs to + u8 layer; + // Null for no animated parts std::unique_ptr animation_info; }; @@ -132,6 +136,7 @@ private: // Child scene node with the current wield mesh scene::IMeshSceneNode *m_meshnode = nullptr; + // Material types used as fallback video::E_MATERIAL_TYPE m_material_type; bool m_anisotropic_filter; @@ -156,6 +161,10 @@ private: ShadowRenderer *m_shadow; }; +/** + * NOTE: The item mesh is only suitable for inventory rendering (due to its + * material types). In-world rendering of items must go through WieldMeshSceneNode. + */ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); scene::SMesh *getExtrudedMesh(video::ITexture *texture, diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 111109fd40..9bfeddfc4e 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -118,9 +118,9 @@ void drawItemStack( video::SColor basecolor = item_visuals->getItemstackColor(item, client); - const u32 mc = mesh->getMeshBufferCount(); - if (mc > imesh->buffer_info.size()) - imesh->buffer_info.resize(mc); + u32 mc = mesh->getMeshBufferCount(); + assert(mc <= imesh->buffer_info.size()); + mc = std::min(mc, imesh->buffer_info.size()); for (u32 j = 0; j < mc; ++j) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); video::SColor c = basecolor; @@ -144,7 +144,6 @@ void drawItemStack( p.animation_info->updateTexture(material, client->getAnimationTime()); } - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; driver->setMaterial(material); driver->drawMeshBuffer(buf); }