Skip to content

Commit fa62c59

Browse files
committed
position-based variants
1 parent d9a0712 commit fa62c59

File tree

12 files changed

+89
-10
lines changed

12 files changed

+89
-10
lines changed

builtin/game/item.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ core.nodedef_default = {
643643
description = "",
644644
groups = {},
645645
variant_count = 1,
646+
variant_pos = false,
646647
inventory_image = "",
647648
wield_image = "",
648649
wield_scale = vector.new(1, 1, 1),

doc/lua_api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,11 @@ Example node with variants:
11521152
})
11531153

11541154

1155+
Alternatively, variants can depend on the position. This is convenient for minor variations
1156+
in textures such as stone or grass that solely serve the purpose of making the world look
1157+
more interesting. In this case, the variant cannot be controlled by the user, but it is
1158+
computed using `hash(pos) % variant_count`. To enable this, set `variant_pos = true`.
1159+
11551160

11561161
Sounds
11571162
======

games/devtest/mods/basenodes/init.lua

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ core.register_node("basenodes:stone", {
1212
description = "Stone",
1313
tiles = {"default_stone.png"},
1414
groups = {cracky=3},
15+
variant_count = 4,
16+
variants = {
17+
{ tiles = { "default_stone.png^[transformR180" } },
18+
{ tiles = { "default_stone.png^[transformFX" } },
19+
{ tiles = { "default_stone.png^[transformFY" } },
20+
},
21+
variant_pos = true,
1522
})
1623

1724
core.register_node("basenodes:desert_stone", {
@@ -32,6 +39,25 @@ core.register_node("basenodes:dirt_with_grass", {
3239
{name = "default_grass_side.png", tileable_vertical = false},
3340
},
3441
groups = {crumbly=3, soil=1},
42+
variant_count = 4,
43+
variants = {
44+
{ tiles = { "default_dirt.png^[transform1" },
45+
overlay_tiles = { "default_grass.png^[transform1",
46+
"basenodes_dirt_with_grass_bottom.png^[transform1",
47+
{name = "default_grass_side.png^[transformFX", tileable_vertical = false} },
48+
},
49+
{ tiles = { "default_dirt.png^[transform2" },
50+
overlay_tiles = { "default_grass.png^[transform2",
51+
"basenodes_dirt_with_grass_bottom.png^[transform2",
52+
{name = "default_grass_side.png", tileable_vertical = false} },
53+
},
54+
{ tiles = { "default_dirt.png^[transform3" },
55+
overlay_tiles = { "default_grass.png^[transform3",
56+
"basenodes_dirt_with_grass_bottom.png^[transform3",
57+
{name = "default_grass_side.png^[transformFX", tileable_vertical = false} },
58+
},
59+
},
60+
variant_pos = true,
3561
})
3662

3763
core.register_node("basenodes:dirt_with_snow", {
@@ -47,6 +73,17 @@ core.register_node("basenodes:dirt_with_snow", {
4773
core.register_node("basenodes:dirt", {
4874
description = "Dirt",
4975
tiles ={"default_dirt.png"},
76+
variant_count = 8,
77+
variants = {
78+
{tiles ={"default_dirt.png^[transform1"},},
79+
{tiles ={"default_dirt.png^[transform2"},},
80+
{tiles ={"default_dirt.png^[transform3"},},
81+
{tiles ={"default_dirt.png^[transform4"},},
82+
{tiles ={"default_dirt.png^[transform5"},},
83+
{tiles ={"default_dirt.png^[transform6"},},
84+
{tiles ={"default_dirt.png^[transform7"},},
85+
},
86+
variant_pos = true,
5087
groups = {crumbly=3, soil=1},
5188
})
5289

src/client/content_mapblock.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile_ret)
9898
// Returns a special tile, ready for use, non-rotated.
9999
void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool apply_crack)
100100
{
101-
*tile_ret = cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][index];
101+
*tile_ret = cur_node.f->special_tiles[cur_node.n.getVariant(cur_node.p,*cur_node.f)][index];
102102
TileLayer *top_layer = nullptr;
103103

104104
for (auto &layernum : tile_ret->layers) {
@@ -990,7 +990,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
990990
// Optionally render internal liquid level defined by param2
991991
// Liquid is textured with 1 tile defined in nodedef 'special_tiles'
992992
if (param2 > 0 && cur_node.f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
993-
cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][0].layers[0].texture) {
993+
cur_node.f->special_tiles[cur_node.n.getVariant(cur_node.p, *cur_node.f)][0].layers[0].texture) {
994994
// Internal liquid level has param2 range 0 .. 63,
995995
// convert it to -0.5 .. 0.5
996996
float vlev = (param2 / 63.0f) * 2.0f - 1.0f;

src/client/mapblock_mesh.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data,
344344
{
345345
const NodeDefManager *ndef = data->m_nodedef;
346346
const ContentFeatures &f = ndef->get(mn);
347-
tile = f.tiles[mn.getVariant(f)][tileindex];
347+
tile = f.tiles[mn.getVariant(p, f)][tileindex];
348348
bool has_crack = p == data->m_crack_pos_relative;
349349
for (TileLayer &layer : tile.layers) {
350350
if (layer.empty())

src/client/minimap.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ void Minimap::blitMinimapPixelsToImageSurface(
403403
MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size];
404404

405405
const ContentFeatures &f = m_ndef->get(mmpixel->n);
406-
u16 v = mmpixel->n.getVariant(f);
406+
u16 v = mmpixel->n.getVariant(v3s16(x, 0, z), f); // We don't have y here
407407
const TileDef *tile = &f.tiledef[v][0];
408408
video::SColor minimap_color = f.minimap_color[v];
409409

src/client/particles.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
867867
texid = tilenum - 1;
868868
else
869869
texid = myrand_range(0,5);
870-
const TileLayer &tile = f.tiles[n.getVariant(f)][texid].layers[0];
870+
const TileLayer &tile = f.tiles[n.getVariant(v3s16(p.pos.X, p.pos.Y, p.pos.Z), f)][texid].layers[0];
871871
p.animation.type = TAT_NONE;
872872

873873
// Only use first frame of animated texture

src/mapnode.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,35 @@ void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const
3838
*color = f.color;
3939
}
4040

41-
u16 MapNode::getVariant(const ContentFeatures &f) const
41+
#define rotl32(b, n) ((b << n) | (b >> (32-n)))
42+
u16 MapNode::getVariant(const v3s16 &p, const ContentFeatures &f) const
4243
{
43-
if (f.variant_count > 1)
44-
return (f.param2_variant.get(param2) + f.variant_offset) % f.variant_count;
45-
return 0;
44+
if (f.variant_count <= 1) return 0;
45+
if (f.variant_pos) {
46+
u32 hash = 0; // could use world seed
47+
// this is "almost Murmur3", but we trimmed it down to less
48+
// instructions that still give the desired effect
49+
// double cast, as float to unsigned int is not well specified
50+
// u32 hX = totl32(((u16)(s16)p.X) * (u32) 0xcc9e2d51, 15);
51+
// u32 hY = rotl32(((u16)(s16)p.Y) * (u32) 0xcc9e2d51, 15);
52+
// u32 hZ = rotl32(((u16)(s16)p.Z) * (u32) 0xcc9e2d51, 15);
53+
u32 hX = ((u16)(s16)p.X) * 0xcc9e2d51;
54+
u32 hY = ((u16)(s16)p.Y) * 0xcc9e2d51;
55+
u32 hZ = ((u16)(s16)p.Z) * 0xcc9e2d51;
56+
hash = rotl32(hash ^ hX, 13); // * 5 + 0xe6546b64;
57+
hash = rotl32(hash ^ hY, 13); // * 5 + 0xe6546b64;
58+
hash = rotl32(hash ^ hZ, 13); // * 5 + 0xe6546b64;
59+
// partial Murmur3 finalizer for avalanching
60+
hash ^= hash >> 16;
61+
hash *= 0x85ebca6b;
62+
hash ^= hash >> 13;
63+
//hash *= 0xc2b2ae35;
64+
//hash ^= hash >> 16;
65+
return hash % f.variant_count;
66+
}
67+
return (f.param2_variant.get(param2) + f.variant_offset) % f.variant_count;
4668
}
69+
#undef rotl32
4770

4871
u8 MapNode::getFaceDir(const NodeDefManager *nodemgr,
4972
bool allow_wallmounted) const

src/mapnode.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,10 @@ struct alignas(u32) MapNode
198198

199199
/*!
200200
* Returns the variant of the node.
201+
* \param p position of this node
201202
* \param f content features of this node
202203
*/
203-
u16 getVariant(const ContentFeatures &f) const;
204+
u16 getVariant(const v3s16 &p, const ContentFeatures &f) const;
204205

205206
inline void setLight(LightBank bank, u8 a_light, ContentLightingFlags f) noexcept
206207
{

src/nodedef.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ void ContentFeatures::reset()
371371
param_type_2 = CPT2_NONE;
372372
variant_count = 1;
373373
variant_offset = 0;
374+
variant_pos = false;
374375
param2_variant = BitField<u8>();
375376
is_ground_content = false;
376377
light_propagates = false;
@@ -556,6 +557,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
556557
for (const TileDef &td : tiledef_special[v])
557558
td.serialize(os, protocol_version);
558559
}
560+
writeU8(os, variant_pos);
559561
}
560562

561563
void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
@@ -723,6 +725,10 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
723725
for (TileDef &td : tiledef_special[v])
724726
td.deSerialize(is, drawtype, protocol_version);
725727
}
728+
729+
variant_pos = readU8(is);
730+
if (is.eof())
731+
throw SerializationError("");
726732
} catch (SerializationError &e) {};
727733
}
728734

0 commit comments

Comments
 (0)