# 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.
# 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
### 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:
* `"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.
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",
-- 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,
-- 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,
# 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
# 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
// 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);
}
// 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) {
/*
if (img_crack) {
draw_crack(img_crack, baseimg,
use_overlay, frame_count,
- progression, driver);
+ progression, driver, tiles);
img_crack->drop();
}
}
}
}
+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)
texture_id == other.texture_id &&
material_type == other.material_type &&
material_flags == other.material_flags &&
- color == other.color;
+ color == other.color &&
+ scale == other.scale;
}
/*!
* a color then the color of the node owning this tile.
*/
video::SColor color;
+
+ u8 scale;
};
/*!
&& 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;
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);
}
}
+// 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];
// 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]) {
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);
#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");
}
}
+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;
*/
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;
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;
face.layer = *layer;
face.layernum = layernum;
+
+ face.world_aligned = tile.world_aligned;
}
}
};
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(
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++)
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);
}
}
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()));
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="
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);
}
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="
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) {
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 {
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);
}
}
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,
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.
*/
#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>
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;
}
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) {
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;
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
*/
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;
}
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)
} 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;
}
/*
}
#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)
{
// 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;
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 ||
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
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;
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)
*/
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;
}
#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
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);
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);
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.");
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.");
v3s16(0,0,0),
};
+constexpr u8 wallmounted_to_facedir[6] = {
+ 20,
+ 0,
+ 16 + 1,
+ 12 + 3,
+ 8,
+ 4 + 2
+};
// 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 {
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);
// 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);
}
}
}
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
}
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;
+ }
}
}
}
*/
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);