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.
master
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;