Skip to content

Commit

Permalink
Rework use_texture_alpha to provide three opaque/clip/blend modes
Browse files Browse the repository at this point in the history
The change that turns nodeboxes and meshes opaque when possible is kept,
as is the compatibility code that warns modders to adjust their nodedefs.
  • Loading branch information
sfan5 committed Jan 29, 2021
1 parent edd8c3c commit 8322992
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 66 deletions.
1 change: 1 addition & 0 deletions builtin/game/features.lua
Expand Up @@ -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)
Expand Down
18 changes: 14 additions & 4 deletions doc/lua_api.txt
Expand Up @@ -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`
Expand Down Expand Up @@ -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.
Expand Down
110 changes: 73 additions & 37 deletions src/nodedef.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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) {};
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -759,29 +793,33 @@ 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:
solidness = 0;
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:
Expand Down Expand Up @@ -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:
Expand All @@ -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;
}
}
Expand Down
56 changes: 42 additions & 14 deletions src/nodedef.h
Expand Up @@ -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)
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
};

/*!
Expand Down
32 changes: 23 additions & 9 deletions src/script/common/c_content.cpp
Expand Up @@ -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;

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

bool usealpha = getboolfield_default(L, index,
"use_texture_alpha", false);
if (usealpha)
f.alpha = 0;
/* 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);
Expand Down

0 comments on commit 8322992

Please sign in to comment.