Real global textures (#6105)

* Real global textures

* Add world-aligned textures
* Update minimal to support world-aligned tiles
* Update minimal
This commit is contained in:
Vitaliy 2017-10-15 10:34:14 +03:00 committed by Loïc Blot
parent 6bab695479
commit 75320e7e88
26 changed files with 469 additions and 160 deletions

@ -431,7 +431,9 @@ texture_clean_transparent (Clean transparent textures) bool false
# memory. Powers of 2 are recommended. Setting this higher than 1 may not
# have a visible effect unless bilinear/trilinear/anisotropic filtering is
# enabled.
texture_min_size (Minimum texture size for filters) int 64
# This is also used as the base node texture size for world-aligned
# texture autoscaling.
texture_min_size (Minimum texture size) int 64
# Experimental option, might cause visible spaces between blocks
# when set to higher number than 0.
@ -687,6 +689,22 @@ fog_start (Fog Start) float 0.4 0.0 0.99
# Makes all liquids opaque
opaque_water (Opaque liquids) bool false
# Textures on a node may be aligned either to the node or to the world.
# The former mode suits better things like machines, furniture, etc., while
# the latter makes stairs and microblocks fit surroundings better.
# However, as this possibility is new, thus may not be used by older servers,
# this option allows enforcing it for certain node types. Note though that
# that is considered EXPERIMENTAL and may not work properly.
world_aligned_mode (World-aligned textures mode) enum enable disable,enable,force_solid,force_nodebox
# World-aligned textures may be scaled to span several nodes. However,
# the server may not send the scale you want, especially if you use
# a specially-designed texture pack; with this option, the client tries
# to determine the scale automatically basing on the texture size.
# See also texture_min_size.
# Warning: this option is EXPERIMENTAL!
autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# Show entity selection boxes
show_entity_selectionbox (Show entity selection boxes) bool true

@ -284,11 +284,19 @@ on top of `cobble.png`.
### Advanced texture modifiers
#### `[crack:<n>:<p>`
#### Crack
* `[crack:<n>:<p>`
* `[cracko:<n>:<p>`
* `[crack:<t>:<n>:<p>`
* `[cracko:<t>:<n>:<p>`
Parameters:
* `<t>` = tile count (in each direction)
* `<n>` = animation frame count
* `<p>` = current animation frame
Draw a step of the crack animation on the texture.
`crack` draws it normally, while `cracko` lays it over, keeping transparent pixels intact.
Example:
@ -4420,12 +4428,21 @@ Definition tables
* `"image.png"`
* `{name="image.png", animation={Tile Animation definition}}`
* `{name="image.png", backface_culling=bool, tileable_vertical=bool,
tileable_horizontal=bool}`
tileable_horizontal=bool, align_style="node"/"world"/"user", scale=int}`
* backface culling enabled by default for most nodes
* tileable flags are info for shaders, how they should treat texture
when displacement mapping is used
Directions are from the point of view of the tile texture,
not the node it's on
* align style determines whether the texture will be rotated with the node
or kept aligned with its surroundings. "user" means that client
setting will be used, similar to `glasslike_framed_optional`.
Note: supported by solid nodes and nodeboxes only.
* scale is used to make texture span several (exactly `scale`) nodes,
instead of just one, in each direction. Works for world-aligned
textures only.
Note that as the effect is applied on per-mapblock basis, `16` should
be equally divisible by `scale` or you may get wrong results.
* `{name="image.png", color=ColorSpec}`
* the texture's color will be multiplied with this color.
* the tile's color overrides the owning node's color in all cases.

@ -1,2 +1,2 @@
default
stairs

@ -501,6 +501,57 @@ minetest.register_node("experimental:tester_node_1", {
end,
})
minetest.register_node("experimental:tiled", {
description = "Tiled stone",
tiles = {{
name = "experimental_tiled.png",
align_style = "world",
scale = 8,
}},
groups = {cracky=2},
})
stairs.register_stair_and_slab("tiled_n", "experimental:tiled",
{cracky=2},
{{name="experimental_tiled.png", align_style="node", scale=8}},
"Tiled stair (node-aligned)",
"Tiled slab (node-aligned)")
stairs.register_stair_and_slab("tiled", "experimantal:tiled",
{cracky=2},
{{name="experimental_tiled.png", align_style="world", scale=8}},
"Tiled stair",
"Tiled slab")
minetest.register_craft({
output = 'experimental:tiled 4',
recipe = {
{'default:cobble', '', 'default:cobble'},
{'', '', ''},
{'default:cobble', '', 'default:cobble'},
}
})
minetest.register_craft({
output = 'stairs:stair_tiled',
recipe = {{'stairs:stair_tiled_n'}}
})
minetest.register_craft({
output = 'stairs:stair_tiled_n',
recipe = {{'stairs:stair_tiled'}}
})
minetest.register_craft({
output = 'stairs:slab_tiled',
recipe = {{'stairs:slab_tiled_n'}}
})
minetest.register_craft({
output = 'stairs:slab_tiled_n',
recipe = {{'stairs:slab_tiled'}}
})
minetest.register_craftitem("experimental:tester_tool_1", {
description = "Tester Tool 1",
inventory_image = "experimental_tester_tool_1.png",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -2,7 +2,7 @@ stairs = {}
-- Node will be called stairs:stair_<subname>
function stairs.register_stair(subname, recipeitem, groups, images, description)
minetest.register_node("stairs:stair_" .. subname, {
minetest.register_node(":stairs:stair_" .. subname, {
description = description,
drawtype = "nodebox",
tiles = images,
@ -31,7 +31,7 @@ end
-- Node will be called stairs:slab_<subname>
function stairs.register_slab(subname, recipeitem, groups, images, description)
minetest.register_node("stairs:slab_" .. subname, {
minetest.register_node(":stairs:slab_" .. subname, {
description = description,
drawtype = "nodebox",
tiles = images,

@ -489,6 +489,8 @@
# memory. Powers of 2 are recommended. Setting this higher than 1 may not
# have a visible effect unless bilinear/trilinear/anisotropic filtering is
# enabled.
# This is also used as the base node texture size for world-aligned
# texture autoscaling.
# type: int
# texture_min_size = 64
@ -808,6 +810,24 @@
# type: bool
# opaque_water = false
# Textures on a node may be aligned either to the node or to the world.
# The former mode sutis better things like machines, furniture, etc., while
# the latter makes stairs and microblocks fit surroundings better.
# However, as this possibility is new, thus may not be used by older servers,
# this option allows enforcing it for certain node types. Note though that
# that is considered EXPERIMENTAL and may not work properly.
# type: enum values: disable, enable, force_solid, force_nodebox
# world_aligned_mode = enable
# World-aligned textures may be scaled to span several nodes. However,
# the server may not send the scale you want, especially if you use
# a specially-designed texture pack; with this option, the client tries
# to determine the scale automatically basing on the texture size.
# See also texture_min_size.
# Warning: this option is EXPERIMENTAL!
# type: enum values: disable, enable, force
# autoscale_mode = disable
# Show entity selection boxes
# type: bool
# show_entity_selectionbox = true

@ -545,7 +545,7 @@ static void apply_mask(video::IImage *mask, video::IImage *dst,
// Draw or overlay a crack
static void draw_crack(video::IImage *crack, video::IImage *dst,
bool use_overlay, s32 frame_count, s32 progression,
video::IVideoDriver *driver);
video::IVideoDriver *driver, u8 tiles = 1);
// Brighten image
void brighten(video::IImage *image);
@ -1280,11 +1280,22 @@ bool TextureSource::generateImagePart(std::string part_of_name,
}
// Crack image number and overlay option
// Format: crack[o][:<tiles>]:<frame_count>:<frame>
bool use_overlay = (part_of_name[6] == 'o');
Strfnd sf(part_of_name);
sf.next(":");
s32 frame_count = stoi(sf.next(":"));
s32 progression = stoi(sf.next(":"));
s32 tiles = 1;
// Check whether there is the <tiles> argument, that is,
// whether there are 3 arguments. If so, shift values
// as the first and not the last argument is optional.
auto s = sf.next(":");
if (!s.empty()) {
tiles = frame_count;
frame_count = progression;
progression = stoi(s);
}
if (progression >= 0) {
/*
@ -1299,7 +1310,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
if (img_crack) {
draw_crack(img_crack, baseimg,
use_overlay, frame_count,
progression, driver);
progression, driver, tiles);
img_crack->drop();
}
}
@ -2102,74 +2113,78 @@ static void apply_mask(video::IImage *mask, video::IImage *dst,
}
}
video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
{
core::dimension2d<u32> strip_size = crack->getDimension();
core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
core::dimension2d<u32> tile_size(size / tiles);
s32 frame_count = strip_size.Height / strip_size.Width;
if (frame_index >= frame_count)
frame_index = frame_count - 1;
core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
video::IImage *result = nullptr;
// extract crack frame
video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
if (!crack_tile)
return nullptr;
if (tile_size == frame_size) {
crack->copyTo(crack_tile, v2s32(0, 0), frame);
} else {
video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
if (!crack_frame)
goto exit__has_tile;
crack->copyTo(crack_frame, v2s32(0, 0), frame);
crack_frame->copyToScaling(crack_tile);
crack_frame->drop();
}
if (tiles == 1)
return crack_tile;
// tile it
result = driver->createImage(video::ECF_A8R8G8B8, size);
if (!result)
goto exit__has_tile;
result->fill({});
for (u8 i = 0; i < tiles; i++)
for (u8 j = 0; j < tiles; j++)
crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
exit__has_tile:
crack_tile->drop();
return result;
}
static void draw_crack(video::IImage *crack, video::IImage *dst,
bool use_overlay, s32 frame_count, s32 progression,
video::IVideoDriver *driver)
video::IVideoDriver *driver, u8 tiles)
{
// Dimension of destination image
core::dimension2d<u32> dim_dst = dst->getDimension();
// Dimension of original image
core::dimension2d<u32> dim_crack = crack->getDimension();
// Count of crack stages
s32 crack_count = dim_crack.Height / dim_crack.Width;
// Limit frame_count
if (frame_count > (s32) dim_dst.Height)
frame_count = dim_dst.Height;
if (frame_count < 1)
frame_count = 1;
// Limit progression
if (progression > crack_count-1)
progression = crack_count-1;
// Dimension of a single crack stage
core::dimension2d<u32> dim_crack_cropped(
dim_crack.Width,
dim_crack.Width
);
// Dimension of the scaled crack stage,
// which is the same as the dimension of a single destination frame
core::dimension2d<u32> dim_crack_scaled(
core::dimension2d<u32> frame_size(
dim_dst.Width,
dim_dst.Height / frame_count
);
// Create cropped and scaled crack images
video::IImage *crack_cropped = driver->createImage(
video::ECF_A8R8G8B8, dim_crack_cropped);
video::IImage *crack_scaled = driver->createImage(
video::ECF_A8R8G8B8, dim_crack_scaled);
video::IImage *crack_scaled = create_crack_image(crack, progression,
frame_size, tiles, driver);
if (!crack_scaled)
return;
if (crack_cropped && crack_scaled)
{
// Crop crack image
v2s32 pos_crack(0, progression*dim_crack.Width);
crack->copyTo(crack_cropped,
v2s32(0,0),
core::rect<s32>(pos_crack, dim_crack_cropped));
// Scale crack image by copying
crack_cropped->copyToScaling(crack_scaled);
// Copy or overlay crack image onto each frame
for (s32 i = 0; i < frame_count; ++i)
{
v2s32 dst_pos(0, dim_crack_scaled.Height * i);
if (use_overlay)
{
blit_with_alpha_overlay(crack_scaled, dst,
v2s32(0,0), dst_pos,
dim_crack_scaled);
}
else
{
blit_with_alpha(crack_scaled, dst,
v2s32(0,0), dst_pos,
dim_crack_scaled);
}
}
auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
for (s32 i = 0; i < frame_count; ++i) {
v2s32 dst_pos(0, frame_size.Height * i);
blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
}
if (crack_scaled)
crack_scaled->drop();
if (crack_cropped)
crack_cropped->drop();
crack_scaled->drop();
}
void brighten(video::IImage *image)

@ -209,7 +209,8 @@ struct TileLayer
texture_id == other.texture_id &&
material_type == other.material_type &&
material_flags == other.material_flags &&
color == other.color;
color == other.color &&
scale == other.scale;
}
/*!
@ -298,6 +299,8 @@ struct TileLayer
* a color then the color of the node owning this tile.
*/
video::SColor color;
u8 scale;
};
/*!
@ -325,6 +328,9 @@ struct TileSpec
&& emissive_light == other.emissive_light;
}
//! If true, the tile rotation is ignored.
bool world_aligned = false;
//! Tile rotation.
u8 rotation = 0;
//! This much light does the tile emit.
u8 emissive_light = 0;

@ -77,7 +77,7 @@ void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, boo
if (special)
getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
else
getNodeTileN(n, p, index, data, tile);
getTile(index, &tile);
if (!data->m_smooth_lighting)
color = encode_light(light, f->light_source);
@ -87,14 +87,19 @@ void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, boo
}
}
// Returns a tile, ready for use, non-rotated.
void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
{
getNodeTileN(n, p, index, data, *tile);
}
// Returns a tile, ready for use, rotated according to the node facedir.
void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
{
getNodeTile(n, p, direction, data, *tile);
}
/*!
* Returns the i-th special tile for a map node.
*/
// Returns a special tile, ready for use, non-rotated.
void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
{
*tile = f->special_tiles[index];
@ -1256,10 +1261,8 @@ void MapblockMeshGenerator::drawMeshNode()
// Convert wallmounted to 6dfacedir.
// When cache enabled, it is already converted.
facedir = n.getWallMounted(nodedef);
if (!enable_mesh_cache) {
static const u8 wm_to_6d[6] = {20, 0, 16 + 1, 12 + 3, 8, 4 + 2};
facedir = wm_to_6d[facedir];
}
if (!enable_mesh_cache)
facedir = wallmounted_to_facedir[facedir];
}
if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {

@ -63,6 +63,7 @@ public:
void useTile(int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY,
u8 reset_flags = 0, bool special = false);
void getTile(int index, TileSpec *tile);
void getTile(v3s16 direction, TileSpec *tile);
void getSpecialTile(int index, TileSpec *tile, bool apply_crack = false);

@ -139,6 +139,8 @@ void set_default_settings(Settings *settings)
#endif
settings->setDefault("fsaa", "0");
settings->setDefault("undersampling", "0");
settings->setDefault("world_aligned_mode", "enable");
settings->setDefault("autoscale_mode", "disable");
settings->setDefault("enable_fog", "true");
settings->setDefault("fog_start", "0.4");
settings->setDefault("3d_mode", "none");

@ -466,6 +466,31 @@ static void getNodeVertexDirs(v3s16 dir, v3s16 *vertex_dirs)
}
}
static void getNodeTextureCoords(v3f base, const v3f &scale, v3s16 dir, float *u, float *v)
{
if (dir.X > 0 || dir.Y > 0 || dir.Z < 0)
base -= scale;
if (dir == v3s16(0,0,1)) {
*u = -base.X - 1;
*v = -base.Y - 1;
} else if (dir == v3s16(0,0,-1)) {
*u = base.X + 1;
*v = -base.Y - 2;
} else if (dir == v3s16(1,0,0)) {
*u = base.Z + 1;
*v = -base.Y - 2;
} else if (dir == v3s16(-1,0,0)) {
*u = -base.Z - 1;
*v = -base.Y - 1;
} else if (dir == v3s16(0,1,0)) {
*u = base.X + 1;
*v = -base.Z - 2;
} else if (dir == v3s16(0,-1,0)) {
*u = base.X;
*v = base.Z;
}
}
struct FastFace
{
TileLayer layer;
@ -477,10 +502,11 @@ struct FastFace
*/
bool vertex_0_2_connected;
u8 layernum;
bool world_aligned;
};
static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3,
const v3f &p, v3s16 dir, v3f scale, std::vector<FastFace> &dest)
v3f tp, v3f p, v3s16 dir, v3f scale, std::vector<FastFace> &dest)
{
// Position is at the center of the cube.
v3f pos = p * BS;
@ -493,6 +519,8 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li
v3f vertex_pos[4];
v3s16 vertex_dirs[4];
getNodeVertexDirs(dir, vertex_dirs);
if (tile.world_aligned)
getNodeTextureCoords(tp, scale, dir, &x0, &y0);
v3s16 t;
u16 t1;
@ -671,6 +699,8 @@ static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li
face.layer = *layer;
face.layernum = layernum;
face.world_aligned = tile.world_aligned;
}
}
@ -806,7 +836,7 @@ void getNodeTile(MapNode mn, v3s16 p, v3s16 dir, MeshMakeData *data, TileSpec &t
};
u16 tile_index = facedir * 16 + dir_i;
getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile);
tile.rotation = dir_to_tile[tile_index + 1];
tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
}
static void getTileInfo(
@ -965,7 +995,7 @@ static void updateFastFaceRow(
scale.Z = continuous_tiles_count;
makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
sp, face_dir_corrected, scale, dest);
pf, sp, face_dir_corrected, scale, dest);
g_profiler->avg("Meshgen: faces drawn by tiling", 0);
for (int i = 1; i < continuous_tiles_count; i++)
@ -1092,7 +1122,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
f.vertex_0_2_connected ? indices : indices_alternate;
collector.append(f.layer, f.vertices, 4, indices_p, 6,
f.layernum);
f.layernum, f.world_aligned);
}
}
@ -1128,6 +1158,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack";
if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
os << "o"; // use ^[cracko
u8 tiles = p.layer.scale;
if (tiles > 1)
os << ":" << (u32)tiles;
os << ":" << (u32)p.layer.animation_frame_count << ":";
m_crack_materials.insert(std::make_pair(
std::pair<u8, u32>(layer, i), os.str()));
@ -1392,13 +1425,15 @@ void MeshCollector::append(const TileSpec &tile,
const TileLayer *layer = &tile.layers[layernum];
if (layer->texture_id == 0)
continue;
append(*layer, vertices, numVertices, indices, numIndices, layernum);
append(*layer, vertices, numVertices, indices, numIndices,
layernum, tile.world_aligned);
}
}
void MeshCollector::append(const TileLayer &layer,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, u8 layernum)
const u16 *indices, u32 numIndices, u8 layernum,
bool use_scale)
{
if (numIndices > 65535) {
dstream << "FIXME: MeshCollector::append() called with numIndices="
@ -1422,20 +1457,24 @@ void MeshCollector::append(const TileLayer &layer,
p = &(*buffers)[buffers->size() - 1];
}
f32 scale = 1.0;
if (use_scale)
scale = 1.0 / layer.scale;
u32 vertex_count;
if (m_use_tangent_vertices) {
vertex_count = p->tangent_vertices.size();
for (u32 i = 0; i < numVertices; i++) {
video::S3DVertexTangents vert(vertices[i].Pos, vertices[i].Normal,
vertices[i].Color, vertices[i].TCoords);
vertices[i].Color, scale * vertices[i].TCoords);
p->tangent_vertices.push_back(vert);
}
} else {
vertex_count = p->vertices.size();
for (u32 i = 0; i < numVertices; i++) {
video::S3DVertex vert(vertices[i].Pos, vertices[i].Normal,
vertices[i].Color, vertices[i].TCoords);
vertices[i].Color, scale * vertices[i].TCoords);
p->vertices.push_back(vert);
}
@ -1461,14 +1500,15 @@ void MeshCollector::append(const TileSpec &tile,
if (layer->texture_id == 0)
continue;
append(*layer, vertices, numVertices, indices, numIndices, pos,
c, light_source, layernum);
c, light_source, layernum, tile.world_aligned);
}
}
void MeshCollector::append(const TileLayer &layer,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices,
v3f pos, video::SColor c, u8 light_source, u8 layernum)
v3f pos, video::SColor c, u8 light_source, u8 layernum,
bool use_scale)
{
if (numIndices > 65535) {
dstream << "FIXME: MeshCollector::append() called with numIndices="
@ -1492,6 +1532,10 @@ void MeshCollector::append(const TileLayer &layer,
p = &(*buffers)[buffers->size() - 1];
}
f32 scale = 1.0;
if (use_scale)
scale = 1.0 / layer.scale;
video::SColor original_c = c;
u32 vertex_count;
if (m_use_tangent_vertices) {
@ -1502,7 +1546,7 @@ void MeshCollector::append(const TileLayer &layer,
applyFacesShading(c, vertices[i].Normal);
}
video::S3DVertexTangents vert(vertices[i].Pos + pos,
vertices[i].Normal, c, vertices[i].TCoords);
vertices[i].Normal, c, scale * vertices[i].TCoords);
p->tangent_vertices.push_back(vert);
}
} else {
@ -1513,7 +1557,7 @@ void MeshCollector::append(const TileLayer &layer,
applyFacesShading(c, vertices[i].Normal);
}
video::S3DVertex vert(vertices[i].Pos + pos, vertices[i].Normal, c,
vertices[i].TCoords);
scale * vertices[i].TCoords);
p->vertices.push_back(vert);
}
}

@ -200,7 +200,8 @@ struct MeshCollector
const u16 *indices, u32 numIndices);
void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, u8 layernum);
const u16 *indices, u32 numIndices, u8 layernum,
bool use_scale = false);
void append(const TileSpec &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, v3f pos,
@ -208,7 +209,8 @@ struct MeshCollector
void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, v3f pos,
video::SColor c, u8 light_source, u8 layernum);
video::SColor c, u8 light_source, u8 layernum,
bool use_scale = false);
/*!
* Colorizes all vertices in the collector.
*/

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serialization.h" // For ser_ver_supported
#include "util/serialize.h"
#include "log.h"
#include "util/directiontables.h"
#include "util/numeric.h"
#include <string>
#include <sstream>
@ -152,12 +153,15 @@ bool MapNode::getLightBanks(u8 &lightday, u8 &lightnight, INodeDefManager *nodem
return f.param_type == CPT_LIGHT || f.light_source != 0;
}
u8 MapNode::getFaceDir(INodeDefManager *nodemgr) const
u8 MapNode::getFaceDir(INodeDefManager *nodemgr, bool allow_wallmounted) const
{
const ContentFeatures &f = nodemgr->get(*this);
if (f.param_type_2 == CPT2_FACEDIR ||
f.param_type_2 == CPT2_COLORED_FACEDIR)
return (getParam2() & 0x1F) % 24;
if (allow_wallmounted && (f.param_type_2 == CPT2_WALLMOUNTED ||
f.param_type_2 == CPT2_COLORED_WALLMOUNTED))
return wallmounted_to_facedir[getParam2() & 0x07];
return 0;
}
@ -246,7 +250,7 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox,
if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) {
const std::vector<aabb3f> &fixed = nodebox.fixed;
int facedir = n.getFaceDir(nodemgr);
int facedir = n.getFaceDir(nodemgr, true);
u8 axisdir = facedir>>2;
facedir&=0x03;
for (aabb3f box : fixed) {

@ -240,7 +240,7 @@ struct MapNode
return blend_light(daylight_factor, lightday, lightnight);
}
u8 getFaceDir(INodeDefManager *nodemgr) const;
u8 getFaceDir(INodeDefManager *nodemgr, bool allow_wallmounted = false) const;
u8 getWallMounted(INodeDefManager *nodemgr) const;
v3s16 getWallMountedDir(INodeDefManager *nodemgr) const;

@ -180,6 +180,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Backwards compatibility drop
Add 'can_zoom' to player object properties
Add glow to object properties
Change TileDef serialization format.
Add world-aligned tiles.
Mod channels
*/

@ -169,40 +169,72 @@ void NodeBox::deSerialize(std::istream &is)
TileDef
*/
#define TILE_FLAG_BACKFACE_CULLING (1 << 0)
#define TILE_FLAG_TILEABLE_HORIZONTAL (1 << 1)
#define TILE_FLAG_TILEABLE_VERTICAL (1 << 2)
#define TILE_FLAG_HAS_COLOR (1 << 3)
#define TILE_FLAG_HAS_SCALE (1 << 4)
#define TILE_FLAG_HAS_ALIGN_STYLE (1 << 5)
void TileDef::serialize(std::ostream &os, u16 protocol_version) const
{
// protocol_version >= 36
u8 version = 5;
u8 version = 6;
writeU8(os, version);
os << serializeString(name);
animation.serialize(os, version);
writeU8(os, backface_culling);
writeU8(os, tileable_horizontal);
writeU8(os, tileable_vertical);
writeU8(os, has_color);
bool has_scale = scale > 0;
u16 flags = 0;
if (backface_culling)
flags |= TILE_FLAG_BACKFACE_CULLING;
if (tileable_horizontal)
flags |= TILE_FLAG_TILEABLE_HORIZONTAL;
if (tileable_vertical)
flags |= TILE_FLAG_TILEABLE_VERTICAL;
if (has_color)
flags |= TILE_FLAG_HAS_COLOR;
if (has_scale)
flags |= TILE_FLAG_HAS_SCALE;
if (align_style != ALIGN_STYLE_NODE)
flags |= TILE_FLAG_HAS_ALIGN_STYLE;
writeU16(os, flags);
if (has_color) {
writeU8(os, color.getRed());
writeU8(os, color.getGreen());
writeU8(os, color.getBlue());
}
if (has_scale)
writeU8(os, scale);
if (align_style != ALIGN_STYLE_NODE)
writeU8(os, align_style);
}
void TileDef::deSerialize(std::istream &is, u8 contentfeatures_version,
NodeDrawType drawtype)
{
int version = readU8(is);
if (version < 6)
throw SerializationError("unsupported TileDef version");
name = deSerializeString(is);
animation.deSerialize(is, version);
backface_culling = readU8(is);
tileable_horizontal = readU8(is);
tileable_vertical = readU8(is);
has_color = readU8(is);
u16 flags = readU16(is);
backface_culling = flags & TILE_FLAG_BACKFACE_CULLING;
tileable_horizontal = flags & TILE_FLAG_TILEABLE_HORIZONTAL;
tileable_vertical = flags & TILE_FLAG_TILEABLE_VERTICAL;
has_color = flags & TILE_FLAG_HAS_COLOR;
bool has_scale = flags & TILE_FLAG_HAS_SCALE;
bool has_align_style = flags & TILE_FLAG_HAS_ALIGN_STYLE;
if (has_color) {
color.setRed(readU8(is));
color.setGreen(readU8(is));
color.setBlue(readU8(is));
}
scale = has_scale ? readU8(is) : 0;
if (has_align_style)
align_style = static_cast<AlignStyle>(readU8(is));
else
align_style = ALIGN_STYLE_NODE;
}
@ -235,7 +267,10 @@ void TextureSettings::readSettings()
bool smooth_lighting = g_settings->getBool("smooth_lighting");
enable_mesh_cache = g_settings->getBool("enable_mesh_cache");
enable_minimap = g_settings->getBool("enable_minimap");
node_texture_size = g_settings->getU16("texture_min_size");
std::string leaves_style_str = g_settings->get("leaves_style");
std::string world_aligned_mode_str = g_settings->get("world_aligned_mode");
std::string autoscale_mode_str = g_settings->get("autoscale_mode");
// Mesh cache is not supported in combination with smooth lighting
if (smooth_lighting)
@ -250,6 +285,22 @@ void TextureSettings::readSettings()
} else {
leaves_style = LEAVES_OPAQUE;
}
if (world_aligned_mode_str == "enable")
world_aligned_mode = WORLDALIGN_ENABLE;
else if (world_aligned_mode_str == "force_solid")
world_aligned_mode = WORLDALIGN_FORCE;
else if (world_aligned_mode_str == "force_nodebox")
world_aligned_mode = WORLDALIGN_FORCE_NODEBOX;
else
world_aligned_mode = WORLDALIGN_DISABLE;
if (autoscale_mode_str == "enable")
autoscale_mode = AUTOSCALE_ENABLE;
else if (autoscale_mode_str == "force")
autoscale_mode = AUTOSCALE_FORCE;
else
autoscale_mode = AUTOSCALE_DISABLE;
}
/*
@ -541,77 +592,108 @@ void ContentFeatures::deSerialize(std::istream &is)
}
#ifndef SERVER
void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileLayer *tile,
TileDef *tiledef, u32 shader_id, bool use_normal_texture,
bool backface_culling, u8 material_type)
static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
const TileSpec &tile, const TileDef &tiledef, video::SColor color,
u8 material_type, u32 shader_id, bool backface_culling,
const TextureSettings &tsettings)
{
tile->shader_id = shader_id;
tile->texture = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id);
tile->material_type = material_type;
layer->shader_id = shader_id;
layer->texture = tsrc->getTextureForMesh(tiledef.name, &layer->texture_id);
layer->material_type = material_type;
bool has_scale = tiledef.scale > 0;
if (((tsettings.autoscale_mode == AUTOSCALE_ENABLE) && !has_scale) ||
(tsettings.autoscale_mode == AUTOSCALE_FORCE)) {
auto texture_size = layer->texture->getOriginalSize();
float base_size = tsettings.node_texture_size;
float size = std::fmin(texture_size.Width, texture_size.Height);
layer->scale = std::fmax(base_size, size) / base_size;
} else if (has_scale) {
layer->scale = tiledef.scale;
} else {
layer->scale = 1;
}
if (!tile.world_aligned)
layer->scale = 1;
// Normal texture and shader flags texture
if (use_normal_texture) {
tile->normal_texture = tsrc->getNormalTexture(tiledef->name);
if (tsettings.use_normal_texture) {
layer->normal_texture = tsrc->getNormalTexture(tiledef.name);
}
tile->flags_texture = tsrc->getShaderFlagsTexture(tile->normal_texture ? true : false);
layer->flags_texture = tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false);
// Material flags
tile->material_flags = 0;
layer->material_flags = 0;
if (backface_culling)
tile->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
if (tiledef->animation.type != TAT_NONE)
tile->material_flags |= MATERIAL_FLAG_ANIMATION;
if (tiledef->tileable_horizontal)
tile->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL;
if (tiledef->tileable_vertical)
tile->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL;
layer->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
if (tiledef.animation.type != TAT_NONE)
layer->material_flags |= MATERIAL_FLAG_ANIMATION;
if (tiledef.tileable_horizontal)
layer->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL;
if (tiledef.tileable_vertical)
layer->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL;
// Color
tile->has_color = tiledef->has_color;
if (tiledef->has_color)
tile->color = tiledef->color;
layer->has_color = tiledef.has_color;
if (tiledef.has_color)
layer->color = tiledef.color;
else
tile->color = color;
layer->color = color;
// Animation parameters
int frame_count = 1;
if (tile->material_flags & MATERIAL_FLAG_ANIMATION) {
if (layer->material_flags & MATERIAL_FLAG_ANIMATION) {
int frame_length_ms;
tiledef->animation.determineParams(tile->texture->getOriginalSize(),
tiledef.animation.determineParams(layer->texture->getOriginalSize(),
&frame_count, &frame_length_ms, NULL);
tile->animation_frame_count = frame_count;
tile->animation_frame_length_ms = frame_length_ms;
layer->animation_frame_count = frame_count;
layer->animation_frame_length_ms = frame_length_ms;
}
if (frame_count == 1) {
tile->material_flags &= ~MATERIAL_FLAG_ANIMATION;
layer->material_flags &= ~MATERIAL_FLAG_ANIMATION;
} else {
std::ostringstream os(std::ios::binary);
if (!tile->frames) {
tile->frames = std::make_shared<std::vector<FrameSpec>>();
if (!layer->frames) {
layer->frames = std::make_shared<std::vector<FrameSpec>>();
}
tile->frames->resize(frame_count);
layer->frames->resize(frame_count);
for (int i = 0; i < frame_count; i++) {
FrameSpec frame;
os.str("");
os << tiledef->name;
tiledef->animation.getTextureModifer(os,
tile->texture->getOriginalSize(), i);
os << tiledef.name;
tiledef.animation.getTextureModifer(os,
layer->texture->getOriginalSize(), i);
frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id);
if (tile->normal_texture)
if (layer->normal_texture)
frame.normal_texture = tsrc->getNormalTexture(os.str());
frame.flags_texture = tile->flags_texture;
(*tile->frames)[i] = frame;
frame.flags_texture = layer->flags_texture;
(*layer->frames)[i] = frame;
}
}
}
#endif
#ifndef SERVER
bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype)
{
if (style == ALIGN_STYLE_WORLD)
return true;
if (mode == WORLDALIGN_DISABLE)
return false;
if (style == ALIGN_STYLE_USER_DEFINED)
return true;
if (drawtype == NDT_NORMAL)
return mode >= WORLDALIGN_FORCE;
if (drawtype == NDT_NODEBOX)
return mode >= WORLDALIGN_FORCE_NODEBOX;
return false;
}
void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
{
@ -751,13 +833,15 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
// Tiles (fill in f->tiles[])
for (u16 j = 0; j < 6; j++) {
fillTileAttribs(tsrc, &tiles[j].layers[0], &tdef[j], tile_shader,
tsettings.use_normal_texture,
tdef[j].backface_culling, material_type);
tiles[j].world_aligned = isWorldAligned(tdef[j].align_style,
tsettings.world_aligned_mode, drawtype);
fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j],
color, material_type, tile_shader,
tdef[j].backface_culling, tsettings);
if (!tdef_overlay[j].name.empty())
fillTileAttribs(tsrc, &tiles[j].layers[1], &tdef_overlay[j],
overlay_shader, tsettings.use_normal_texture,
tdef[j].backface_culling, overlay_material);
fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j],
color, overlay_material, overlay_shader,
tdef[j].backface_culling, tsettings);
}
u8 special_material = material_type;
@ -770,11 +854,10 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype);
// Special tiles (fill in f->special_tiles[])
for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) {
fillTileAttribs(tsrc, &special_tiles[j].layers[0], &tdef_spec[j],
special_shader, tsettings.use_normal_texture,
tdef_spec[j].backface_culling, special_material);
}
for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j],
color, special_material, special_shader,
tdef_spec[j].backface_culling, tsettings);
if (param_type_2 == CPT2_COLOR ||
param_type_2 == CPT2_COLORED_FACEDIR ||
@ -791,18 +874,6 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
recalculateBoundingBox(mesh_ptr[0]);
meshmanip->recalculateNormals(mesh_ptr[0], true, false);
}
} else if ((drawtype == NDT_NODEBOX) &&
((node_box.type == NODEBOX_REGULAR) ||
(node_box.type == NODEBOX_FIXED)) &&
(!node_box.fixed.empty())) {
//Convert regular nodebox nodes to meshnodes
//Change the drawtype and apply scale
drawtype = NDT_MESH;
mesh_ptr[0] = convertNodeboxesToMesh(node_box.fixed);
v3f scale = v3f(1.0, 1.0, 1.0) * visual_scale;
scaleMesh(mesh_ptr[0], scale);
recalculateBoundingBox(mesh_ptr[0]);
meshmanip->recalculateNormals(mesh_ptr[0], true, false);
}
//Cache 6dfacedir and wallmounted rotated clones of meshes

@ -125,9 +125,25 @@ enum LeavesStyle {
LEAVES_OPAQUE,
};
enum AutoScale : u8 {
AUTOSCALE_DISABLE,
AUTOSCALE_ENABLE,
AUTOSCALE_FORCE,
};
enum WorldAlignMode : u8 {
WORLDALIGN_DISABLE,
WORLDALIGN_ENABLE,
WORLDALIGN_FORCE,
WORLDALIGN_FORCE_NODEBOX,
};
class TextureSettings {
public:
LeavesStyle leaves_style;
WorldAlignMode world_aligned_mode;
AutoScale autoscale_mode;
int node_texture_size;
bool opaque_water;
bool connected_glass;
bool use_normal_texture;
@ -198,6 +214,12 @@ enum PlantlikeStyle {
PLANT_STYLE_HASH2,
};
enum AlignStyle : u8 {
ALIGN_STYLE_NODE,
ALIGN_STYLE_WORLD,
ALIGN_STYLE_USER_DEFINED,
};
/*
Stand-alone definition of a TileSpec (basically a server-side TileSpec)
*/
@ -212,6 +234,8 @@ struct TileDef
bool has_color = false;
//! The color of the tile.
video::SColor color = video::SColor(0xFFFFFFFF);
AlignStyle align_style = ALIGN_STYLE_NODE;
u8 scale = 0;
struct TileAnimationParams animation;
@ -401,9 +425,6 @@ struct ContentFeatures
}
#ifndef SERVER
void fillTileAttribs(ITextureSource *tsrc, TileLayer *tile, TileDef *tiledef,
u32 shader_id, bool use_normal_texture, bool backface_culling,
u8 material_type);
void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings);
#endif

@ -598,6 +598,8 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef,
float size = rand() % 64 / 512.;
float visual_size = BS * size;
if (tile.scale)
size /= tile.scale;
v2f texsize(size * 2, size * 2);
v2f texpos;
texpos.X = ((rand() % 64) / 64. - texsize.X);

@ -431,6 +431,16 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype)
L, index, "tileable_horizontal", default_tiling);
tiledef.tileable_vertical = getboolfield_default(
L, index, "tileable_vertical", default_tiling);
std::string align_style;
if (getstringfield(L, index, "align_style", align_style)) {
if (align_style == "user")
tiledef.align_style = ALIGN_STYLE_USER_DEFINED;
else if (align_style == "world")
tiledef.align_style = ALIGN_STYLE_WORLD;
else
tiledef.align_style = ALIGN_STYLE_NODE;
}
tiledef.scale = getintfield_default(L, index, "scale", 0);
// color = ...
lua_getfield(L, index, "color");
tiledef.has_color = read_color(L, -1, &tiledef.color);

@ -172,12 +172,13 @@ fake_function() {
gettext("Use trilinear filtering when scaling textures.");
gettext("Clean transparent textures");
gettext("Filtered textures can blend RGB values with fully-transparent neighbors,\nwhich PNG optimizers usually discard, sometimes resulting in a dark or\nlight edge to transparent textures. Apply this filter to clean that up\nat texture load time.");
gettext("Minimum texture size for filters");
gettext("When using bilinear/trilinear/anisotropic filters, low-resolution textures\ncan be blurred, so automatically upscale them with nearest-neighbor\ninterpolation to preserve crisp pixels. This sets the minimum texture size\nfor the upscaled textures; higher values look sharper, but require more\nmemory. Powers of 2 are recommended. Setting this higher than 1 may not\nhave a visible effect unless bilinear/trilinear/anisotropic filtering is\nenabled.");
gettext("Minimum texture size");
gettext("When using bilinear/trilinear/anisotropic filters, low-resolution textures\ncan be blurred, so automatically upscale them with nearest-neighbor\ninterpolation to preserve crisp pixels. This sets the minimum texture size\nfor the upscaled textures; higher values look sharper, but require more\nmemory. Powers of 2 are recommended. Setting this higher than 1 may not\nhave a visible effect unless bilinear/trilinear/anisotropic filtering is\nenabled.\nThis is also used as the base node texture size for world-aligned\ntexture autoscaling.");
gettext("FSAA");
gettext("Experimental option, might cause visible spaces between blocks\nwhen set to higher number than 0.");
gettext("Undersampling");
gettext("Undersampling is similar to using lower screen resolution, but it applies\nto the game world only, keeping the GUI intact.\nIt should give significant performance boost at the cost of less detailed image.");
gettext("Autoscaling mode");
gettext("Shaders");
gettext("Shaders");
gettext("Shaders allow advanced visual effects and may increase performance on some video cards.\nThis only works with the OpenGL video backend.");
@ -303,6 +304,10 @@ fake_function() {
gettext("Fraction of the visible distance at which fog starts to be rendered");
gettext("Opaque liquids");
gettext("Makes all liquids opaque");
gettext("World-aligned textures mode");
gettext("Textures on a node may be aligned either to the node or to the world.\nThe former mode sutis better things like machines, furniture, etc., while\nthe latter makes stairs and microblocks fit surroundings better.\nHowever, as this possibility is new, thus may not be used by older servers,\nthis option allows enforcing it for certain node types. Note though that\nthat is considered EXPERIMENTAL and may not work properly.");
gettext("Autoscaling mode");
gettext("World-aligned textures may be scaled to span several nodes. However,\nthe server may not send the scale you want, especially if you use\na specially-designed texture pack; with this option, the client tries\nto determine the scale automatically basing on the texture size.\nSee also min_texture_size.\nWarning: this option is EXPERIMENTAL!");
gettext("Menus");
gettext("Clouds in menu");
gettext("Use a cloud animation for the main menu background.");

@ -110,3 +110,11 @@ const v3s16 g_27dirs[27] =
v3s16(0,0,0),
};
constexpr u8 wallmounted_to_facedir[6] = {
20,
0,
16 + 1,
12 + 3,
8,
4 + 2
};

@ -31,6 +31,8 @@ extern const v3s16 g_26dirs[26];
// 26th is (0,0,0)
extern const v3s16 g_27dirs[27];
extern const u8 wallmounted_to_facedir[6];
/// Direction in the 6D format. g_27dirs contains corresponding vectors.
/// Here P means Positive, N stands for Negative.
enum Direction6D {

@ -233,7 +233,7 @@ void WieldMeshSceneNode::setCube(const ContentFeatures &f,
scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
scene::SMesh *copy = cloneMesh(cubemesh);
cubemesh->drop();
postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors);
postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true);
changeToMesh(copy);
copy->drop();
m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
@ -554,7 +554,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
// add overlays (since getMesh() returns
// the base layer only)
postProcessNodeMesh(mesh, f, false, false, nullptr,
&result->buffer_colors);
&result->buffer_colors, f.drawtype == NDT_NORMAL);
}
}
}
@ -622,7 +622,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f,
bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype,
std::vector<ItemPartColor> *colors)
std::vector<ItemPartColor> *colors, bool apply_scale)
{
u32 mc = mesh->getMeshBufferCount();
// Allocate colors for existing buffers
@ -670,6 +670,11 @@ void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f,
}
material.setTexture(2, layer->flags_texture);
}
if (apply_scale && tile->world_aligned) {
u32 n = buf->getVertexCount();
for (u32 k = 0; k != n; ++k)
buf->getTCoords(k) /= layer->scale;
}
}
}
}

@ -137,4 +137,4 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename
*/
void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders,
bool set_material, const video::E_MATERIAL_TYPE *mattype,
std::vector<ItemPartColor> *colors);
std::vector<ItemPartColor> *colors, bool apply_scale = false);