Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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.
  • 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.