Rework use_texture_alpha to provide three opaque/clip/blend modes

The change that turns nodeboxes and meshes opaque when possible is kept,
as is the compatibility code that warns modders to adjust their nodedefs.
This commit is contained in:
sfan5 2021-01-17 01:56:50 +01:00
parent edd8c3c664
commit 83229921e5
8 changed files with 164 additions and 66 deletions

View File

@ -18,6 +18,7 @@ core.features = {
pathfinder_works = true,
object_step_has_moveresult = true,
direct_velocity_on_players = true,
use_texture_alpha_string_modes = true,
}
function core.has_feature(arg)

View File

@ -4386,6 +4386,8 @@ Utilities
object_step_has_moveresult = true,
-- Whether get_velocity() and add_velocity() can be used on players (5.4.0)
direct_velocity_on_players = true,
-- nodedef's use_texture_alpha accepts new string modes (5.4.0)
use_texture_alpha_string_modes = true,
}
* `minetest.has_feature(arg)`: returns `boolean, missing_features`
@ -7340,10 +7342,18 @@ Used by `minetest.register_node`.
-- If the node has a palette, then this setting only has an effect in
-- the inventory and on the wield item.
use_texture_alpha = false,
-- Use texture's alpha channel
-- If this is set to false, the node will be rendered fully opaque
-- regardless of any texture transparency.
use_texture_alpha = ...,
-- Specifies how the texture's alpha channel will be used for rendering.
-- possible values:
-- * "opaque": Node is rendered opaque regardless of alpha channel
-- * "clip": A given pixel is either fully see-through or opaque
-- depending on the alpha channel being below/above 50% in value
-- * "blend": The alpha channel specifies how transparent a given pixel
-- of the rendered node is
-- The default is "opaque" for drawtypes normal, liquid and flowingliquid;
-- "clip" otherwise.
-- If set to a boolean value (deprecated): true either sets it to blend
-- or clip, false sets it to clip or opaque mode depending on the drawtype.
palette = "palette.png",
-- The node's `param2` is used to select a pixel from the image.

View File

@ -360,7 +360,7 @@ void ContentFeatures::reset()
i = TileDef();
for (auto &j : tiledef_special)
j = TileDef();
alpha = 255;
alpha = ALPHAMODE_OPAQUE;
post_effect_color = video::SColor(0, 0, 0, 0);
param_type = CPT_NONE;
param_type_2 = CPT2_NONE;
@ -405,6 +405,31 @@ void ContentFeatures::reset()
node_dig_prediction = "air";
}
void ContentFeatures::setAlphaFromLegacy(u8 legacy_alpha)
{
// No special handling for nodebox/mesh here as it doesn't make sense to
// throw warnings when the server is too old to support the "correct" way
switch (drawtype) {
case NDT_NORMAL:
alpha = legacy_alpha == 255 ? ALPHAMODE_OPAQUE : ALPHAMODE_CLIP;
break;
case NDT_LIQUID:
case NDT_FLOWINGLIQUID:
alpha = legacy_alpha == 255 ? ALPHAMODE_OPAQUE : ALPHAMODE_BLEND;
break;
default:
alpha = legacy_alpha == 255 ? ALPHAMODE_CLIP : ALPHAMODE_BLEND;
break;
}
}
u8 ContentFeatures::getAlphaForLegacy() const
{
// This is so simple only because 255 and 0 mean wildly different things
// depending on drawtype...
return alpha == ALPHAMODE_OPAQUE ? 255 : 0;
}
void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
{
const u8 version = CONTENTFEATURES_VERSION;
@ -433,7 +458,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
for (const TileDef &td : tiledef_special) {
td.serialize(os, protocol_version);
}
writeU8(os, alpha);
writeU8(os, getAlphaForLegacy());
writeU8(os, color.getRed());
writeU8(os, color.getGreen());
writeU8(os, color.getBlue());
@ -489,6 +514,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
os << serializeString16(node_dig_prediction);
writeU8(os, leveled_max);
writeU8(os, alpha);
}
void ContentFeatures::deSerialize(std::istream &is)
@ -524,7 +550,7 @@ void ContentFeatures::deSerialize(std::istream &is)
throw SerializationError("unsupported CF_SPECIAL_COUNT");
for (TileDef &td : tiledef_special)
td.deSerialize(is, version, drawtype);
alpha = readU8(is);
setAlphaFromLegacy(readU8(is));
color.setRed(readU8(is));
color.setGreen(readU8(is));
color.setBlue(readU8(is));
@ -582,10 +608,16 @@ void ContentFeatures::deSerialize(std::istream &is)
try {
node_dig_prediction = deSerializeString16(is);
u8 tmp_leveled_max = readU8(is);
u8 tmp = readU8(is);
if (is.eof()) /* readU8 doesn't throw exceptions so we have to do this */
throw SerializationError("");
leveled_max = tmp_leveled_max;
leveled_max = tmp;
tmp = readU8(is);
if (is.eof())
throw SerializationError("");
alpha = static_cast<enum AlphaMode>(tmp);
} catch(SerializationError &e) {};
}
@ -677,6 +709,7 @@ bool ContentFeatures::textureAlphaCheck(ITextureSource *tsrc, const TileDef *til
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
static thread_local bool long_warning_printed = false;
std::set<std::string> seen;
for (int i = 0; i < length; i++) {
if (seen.find(tiles[i].name) != seen.end())
continue;
@ -701,20 +734,21 @@ bool ContentFeatures::textureAlphaCheck(ITextureSource *tsrc, const TileDef *til
break_loop:
image->drop();
if (!ok) {
warningstream << "Texture \"" << tiles[i].name << "\" of "
<< name << " has transparent pixels, assuming "
"use_texture_alpha = true." << std::endl;
if (!long_warning_printed) {
warningstream << " This warning can be a false-positive if "
"unused pixels in the texture are transparent. However if "
"it is meant to be transparent, you *MUST* update the "
"nodedef and set use_texture_alpha = true! This compatibility "
"code will be removed in a few releases." << std::endl;
long_warning_printed = true;
}
return true;
if (ok)
continue;
warningstream << "Texture \"" << tiles[i].name << "\" of "
<< name << " has transparency, assuming "
"use_texture_alpha = \"clip\"." << std::endl;
if (!long_warning_printed) {
warningstream << " This warning can be a false-positive if "
"unused pixels in the texture are transparent. However if "
"it is meant to be transparent, you *MUST* update the "
"nodedef and set use_texture_alpha = \"clip\"! This "
"compatibility code will be removed in a few releases."
<< std::endl;
long_warning_printed = true;
}
return true;
}
return false;
}
@ -759,14 +793,18 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
bool is_liquid = false;
MaterialType material_type = (alpha == 255) ?
TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA;
if (alpha == ALPHAMODE_LEGACY_COMPAT) {
// Before working with the alpha mode, resolve any legacy kludges
alpha = textureAlphaCheck(tsrc, tdef, 6) ? ALPHAMODE_CLIP : ALPHAMODE_OPAQUE;
}
MaterialType material_type = alpha == ALPHAMODE_OPAQUE ?
TILE_MATERIAL_OPAQUE : (alpha == ALPHAMODE_CLIP ? TILE_MATERIAL_BASIC :
TILE_MATERIAL_ALPHA);
switch (drawtype) {
default:
case NDT_NORMAL:
material_type = (alpha == 255) ?
TILE_MATERIAL_OPAQUE : TILE_MATERIAL_ALPHA;
solidness = 2;
break;
case NDT_AIRLIKE:
@ -774,14 +812,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
break;
case NDT_LIQUID:
if (tsettings.opaque_water)
alpha = 255;
alpha = ALPHAMODE_OPAQUE;
solidness = 1;
is_liquid = true;
break;
case NDT_FLOWINGLIQUID:
solidness = 0;
if (tsettings.opaque_water)
alpha = 255;
alpha = ALPHAMODE_OPAQUE;
is_liquid = true;
break;
case NDT_GLASSLIKE:
@ -833,19 +871,16 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
break;
case NDT_MESH:
case NDT_NODEBOX:
if (alpha == 255 && textureAlphaCheck(tsrc, tdef, 6))
alpha = 0;
solidness = 0;
if (waving == 1)
if (waving == 1) {
material_type = TILE_MATERIAL_WAVING_PLANTS;
else if (waving == 2)
} else if (waving == 2) {
material_type = TILE_MATERIAL_WAVING_LEAVES;
else if (waving == 3)
material_type = (alpha == 255) ? TILE_MATERIAL_WAVING_LIQUID_OPAQUE :
TILE_MATERIAL_WAVING_LIQUID_BASIC;
else if (alpha == 255)
material_type = TILE_MATERIAL_OPAQUE;
} else if (waving == 3) {
material_type = alpha == ALPHAMODE_OPAQUE ?
TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ?
TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
}
break;
case NDT_TORCHLIKE:
case NDT_SIGNLIKE:
@ -860,10 +895,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
if (is_liquid) {
if (waving == 3) {
material_type = (alpha == 255) ? TILE_MATERIAL_WAVING_LIQUID_OPAQUE :
TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT;
material_type = alpha == ALPHAMODE_OPAQUE ?
TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ?
TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
} else {
material_type = (alpha == 255) ? TILE_MATERIAL_LIQUID_OPAQUE :
material_type = alpha == ALPHAMODE_OPAQUE ? TILE_MATERIAL_LIQUID_OPAQUE :
TILE_MATERIAL_LIQUID_TRANSPARENT;
}
}

View File

@ -231,6 +231,14 @@ enum AlignStyle : u8 {
ALIGN_STYLE_USER_DEFINED,
};
enum AlphaMode : u8 {
ALPHAMODE_BLEND,
ALPHAMODE_CLIP,
ALPHAMODE_OPAQUE,
ALPHAMODE_LEGACY_COMPAT, /* means either opaque or clip */
};
/*
Stand-alone definition of a TileSpec (basically a server-side TileSpec)
*/
@ -315,9 +323,7 @@ struct ContentFeatures
// These will be drawn over the base tiles.
TileDef tiledef_overlay[6];
TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid
// If 255, the node is opaque.
// Otherwise it uses texture alpha.
u8 alpha;
AlphaMode alpha;
// The color of the node.
video::SColor color;
std::string palette_name;
@ -418,20 +424,27 @@ struct ContentFeatures
void serialize(std::ostream &os, u16 protocol_version) const;
void deSerialize(std::istream &is);
#ifndef SERVER
/*
* Checks if any tile texture has any transparent pixels.
* Prints a warning and returns true if that is the case, false otherwise.
* This is supposed to be used for use_texture_alpha backwards compatibility.
*/
bool textureAlphaCheck(ITextureSource *tsrc, const TileDef *tiles,
int length);
#endif
/*
Some handy methods
*/
void setDefaultAlphaMode()
{
switch (drawtype) {
case NDT_NORMAL:
case NDT_LIQUID:
case NDT_FLOWINGLIQUID:
alpha = ALPHAMODE_OPAQUE;
break;
case NDT_NODEBOX:
case NDT_MESH:
alpha = ALPHAMODE_LEGACY_COMPAT; // this should eventually be OPAQUE
break;
default:
alpha = ALPHAMODE_CLIP;
break;
}
}
bool needsBackfaceCulling() const
{
switch (drawtype) {
@ -465,6 +478,21 @@ struct ContentFeatures
void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings);
#endif
private:
#ifndef SERVER
/*
* Checks if any tile texture has any transparent pixels.
* Prints a warning and returns true if that is the case, false otherwise.
* This is supposed to be used for use_texture_alpha backwards compatibility.
*/
bool textureAlphaCheck(ITextureSource *tsrc, const TileDef *tiles,
int length);
#endif
void setAlphaFromLegacy(u8 legacy_alpha);
u8 getAlphaForLegacy() const;
};
/*!

View File

@ -618,25 +618,39 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
}
lua_pop(L, 1);
/* alpha & use_texture_alpha */
// This is a bit complicated due to compatibility
f.setDefaultAlphaMode();
warn_if_field_exists(L, index, "alpha",
"Obsolete, only limited compatibility provided");
"Obsolete, only limited compatibility provided; "
"replaced by \"use_texture_alpha\"");
if (getintfield_default(L, index, "alpha", 255) != 255)
f.alpha = 0;
f.alpha = ALPHAMODE_BLEND;
bool usealpha = getboolfield_default(L, index,
"use_texture_alpha", false);
if (usealpha)
f.alpha = 0;
lua_getfield(L, index, "use_texture_alpha");
if (lua_isboolean(L, -1)) {
warn_if_field_exists(L, index, "use_texture_alpha",
"Boolean values are deprecated; use the new choices");
if (lua_toboolean(L, -1))
f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND;
} else if (check_field_or_nil(L, -1, LUA_TSTRING, "use_texture_alpha")) {
int result = f.alpha;
string_to_enum(ScriptApiNode::es_TextureAlphaMode, result,
std::string(lua_tostring(L, -1)));
f.alpha = static_cast<enum AlphaMode>(result);
}
lua_pop(L, 1);
/* Other stuff */
// Read node color.
lua_getfield(L, index, "color");
read_color(L, -1, &f.color);
lua_pop(L, 1);
getstringfield(L, index, "palette", f.palette_name);
/* Other stuff */
lua_getfield(L, index, "post_effect_color");
read_color(L, -1, &f.post_effect_color);
lua_pop(L, 1);

View File

@ -93,6 +93,14 @@ struct EnumString ScriptApiNode::es_NodeBoxType[] =
{0, NULL},
};
struct EnumString ScriptApiNode::es_TextureAlphaMode[] =
{
{ALPHAMODE_OPAQUE, "opaque"},
{ALPHAMODE_CLIP, "clip"},
{ALPHAMODE_BLEND, "blend"},
{0, NULL},
};
bool ScriptApiNode::node_on_punch(v3s16 p, MapNode node,
ServerActiveObject *puncher, const PointedThing &pointed)
{

View File

@ -54,4 +54,5 @@ public:
static struct EnumString es_ContentParamType2[];
static struct EnumString es_LiquidType[];
static struct EnumString es_NodeBoxType[];
static struct EnumString es_TextureAlphaMode[];
};

View File

@ -180,7 +180,7 @@ void TestGameDef::defineSomeNodes()
"{default_water.png";
f = ContentFeatures();
f.name = itemdef.name;
f.alpha = 128;
f.alpha = ALPHAMODE_BLEND;
f.liquid_type = LIQUID_SOURCE;
f.liquid_viscosity = 4;
f.is_ground_content = true;
@ -201,7 +201,7 @@ void TestGameDef::defineSomeNodes()
"{default_lava.png";
f = ContentFeatures();
f.name = itemdef.name;
f.alpha = 128;
f.alpha = ALPHAMODE_OPAQUE;
f.liquid_type = LIQUID_SOURCE;
f.liquid_viscosity = 7;
f.light_source = LIGHT_MAX-1;