1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Add node texture variants

This commit is contained in:
Jude Melton-Houghton 2022-11-03 21:18:49 -04:00 committed by SFENCE
parent 81d62d01d1
commit e638072e15
29 changed files with 984 additions and 208 deletions

View file

@ -180,6 +180,25 @@ function core.strip_param2_color(param2, paramtype2)
end
end
function core.strip_param2_variant(param2, def)
if not def or def.variant_count <= 1 or not def.param2_variant then
return 0
end
local bf = def.param2_variant
local right_mask = bit.lshift(1, bf.width) - 1
return bit.band(bit.rshift(param2, bf.offset), right_mask) % def.variant_count
end
function core.set_param2_variant(param2, variant, def)
if not def or not def.param2_variant then
return param2
end
local bf = def.param2_variant
local mask = bit.lshift(bit.lshift(1, bf.width) - 1, bf.offset)
local new_bits = bit.band(bit.lshift(variant, bf.offset), mask)
return bit.bor(bit.band(param2, bit.bnot(mask)), new_bits)
end
-- Content ID caching
local old_get_content_id = core.get_content_id

View file

@ -47,6 +47,7 @@ core.features = {
particle_blend_clip = true,
remove_item_match_meta = true,
httpfetch_additional_methods = true,
texture_variants = true,
}
function core.has_feature(arg)

View file

@ -52,14 +52,18 @@ function core.get_node_drops(node, toolname)
local ptype = def and def.paramtype2
-- get color, if there is color (otherwise nil)
local palette_index = core.strip_param2_color(param2, ptype)
-- get variant (always a number)
local variant = core.strip_param2_variant(param2, def)
if drop == nil then
-- default drop
local itemstring = nodename
if palette_index then
local stack = ItemStack(nodename)
stack:get_meta():set_int("palette_index", palette_index)
return {stack:to_string()}
itemstring = core.itemstring_with_palette(itemstring, palette_index)
end
return {nodename}
if variant > 0 then
itemstring = core.itemstring_with_variant(itemstring, variant)
end
return {itemstring}
elseif type(drop) == "string" then
-- itemstring drop
return drop ~= "" and {drop} or {}
@ -118,9 +122,10 @@ function core.get_node_drops(node, toolname)
for _, add_item in ipairs(item.items) do
-- add color, if necessary
if item.inherit_color and palette_index then
local stack = ItemStack(add_item)
stack:get_meta():set_int("palette_index", palette_index)
add_item = stack:to_string()
add_item = core.itemstring_with_palette(add_item, palette_index)
end
if item.inherit_variant then
add_item = core.itemstring_with_variant(add_item, variant)
end
got_items[#got_items+1] = add_item
end
@ -270,6 +275,13 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end
end
-- Transfer variant
if not def.place_param2 and def.variant_count > 1 then
local variant = math.min(math.max(math.floor(metatable.variant or 0), 0),
def.variant_count - 1)
newnode.param2 = core.set_param2_variant(newnode.param2, variant, def)
end
-- Check if the node is attached and if it can be placed there
local an = core.get_item_group(def.name, "attached_node")
if an ~= 0 and
@ -604,6 +616,12 @@ function core.itemstring_with_color(item, colorstring)
return stack:to_string()
end
function core.itemstring_with_variant(item, variant)
local stack = ItemStack(item) -- convert to ItemStack
stack:get_meta():set_string("variant", variant > 0 and variant or "")
return stack:to_string()
end
-- This is used to allow mods to redefine core.item_place and so on
-- NOTE: This is not the preferred way. Preferred way is to provide enough
-- callbacks to not require redefining global functions. -celeron55
@ -625,6 +643,7 @@ core.nodedef_default = {
-- name intentionally not defined here
description = "",
groups = {},
variant_count = 1,
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
@ -655,6 +674,7 @@ core.nodedef_default = {
post_effect_color = {a=0, r=0, g=0, b=0},
paramtype = "none",
paramtype2 = "none",
param2_variant = {width = 0, offset = 0},
is_ground_content = true,
sunlight_propagates = false,
walkable = true,
@ -680,6 +700,7 @@ core.craftitemdef_default = {
-- name intentionally not defined here
description = "",
groups = {},
variant_count = 1,
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
@ -700,6 +721,7 @@ core.tooldef_default = {
-- name intentionally not defined here
description = "",
groups = {},
variant_count = 1,
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),
@ -720,6 +742,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
-- name intentionally not defined here
description = "",
groups = {},
variant_count = 1,
inventory_image = "",
wield_image = "",
wield_scale = vector.new(1, 1, 1),

View file

@ -1109,6 +1109,63 @@ core.register_node("default:dirt_with_grass", {
})
```
Variants
--------
Items can have "variants", which are numbered states that can determine certain
properties. The number of variants is specified with the item definition field
`variant_count`. Each variant is numbered from 0 to `variant_count` - 1. Every
item has at least one variant (numbered 0). Nodes can specify a part of param2
to read as the variant number by setting the `param2_variant` field to a
`BitField`.
The currently supported variant properties are:
* `tiles`
* `overlay_tiles`
* `special_tiles`
The `variants` table in the item definition is a mapping from
variant numbers to variant tables. These tables can include the aforementioned
fields to set the properties for particular variants. Variants not present in
the mapping default to the values of the aforementioned fields specified in the
item definition table. Old clients will receive only the variant 0 tiles.
Items with multiple variants can specify a variant number with the "variant" key
in their metadata. The absence of the key indicates a variant number of 0, and
this is the canonical representation of the 0 variant. The helper function
`core.itemstring_with_variant` is a shortcut for creating such itemstrings.
The variant is preserved when a node is placed or dug. Custom drops will inherit
the variant only if `inherit_variant` is set to `true` in their specification.
Example node with variants:
core.register_node("mod:grass", {
description = "Grass",
drawtype = "plantlike",
paramtype = "light",
sunlight_propagates = true,
walkable = false,
groups = {dig_immediate = 3},
-- There are 4 variants numbered 0 to 3.
variant_count = 4,
-- The lowest 2 bits store the variant number.
param2_variant = {width = 2, offset = 0},
-- These tiles will be used for variants not otherwise specified,
-- in this case variant 0.
tiles = {"mod_grass1.png"},
-- Tiles for variants 1, 2, and 3 are specified here.
variants = {
{tiles = {"mod_grass2.png"}},
{tiles = {"mod_grass3.png"}},
{tiles = {"mod_grass4.png"}},
},
drop = {
items = {
-- The seeds will inherit the variant of the grass.
{items = {"mod:seeds"}, inherit_variant = true},
}
}
})
Sounds
@ -1991,6 +2048,13 @@ Exact pointing location (currently only `Raycast` supports these fields):
For entities with rotated selection boxes, this will be rotated properly
by the entity's rotation - it will always be in absolute world space.
`BitField`
----------
A `BitField` specifies a part of an unsigned integer whose bits represent
another unsigned integer. A `BitField` is represented as follows:
{width = <integer bit width>, offset = <offset from least significant bit>}
@ -2715,6 +2779,8 @@ Some of the values in the key-value store are handled specially:
* `color`: A `ColorString`, which sets the stack's color.
* `palette_index`: If the item has a palette, this is used to get the
current color from the palette.
* `variant`: If the item has more than one variant, this is the variant number.
The canonical form of variant 0 is the absence of this key.
* `count_meta`: Replace the displayed count with any string.
* `count_alignment`: Set the alignment of the displayed count value. This is an
int value. The lowest 2 bits specify the alignment in x-direction, the 3rd and
@ -5810,6 +5876,8 @@ Utilities
remove_item_match_meta = true,
-- The HTTP API supports the HEAD and PATCH methods (5.12.0)
httpfetch_additional_methods = true,
-- Node/Item texture variants is supported (5.13.0)
texture_variants = true,
}
```
@ -6962,11 +7030,23 @@ Item handling
given `param2` value.
* Returns `nil` if the given `paramtype2` does not contain color
information.
<<<<<<< HEAD
* `core.get_node_drops(node, toolname[, tool, digger, pos])`
* Returns list of itemstrings that are dropped by `node` when dug with the
item `toolname` (not limited to tools). The default implementation doesn't
use `tool`, `digger`, and `pos`, but these are provided by `core.node_dig`
since 5.12.0 for games/mods implementing customized drops.
=======
* `core.strip_param2_variant(param2, def)`
* Returns the variant from `param2` with the given node definition `def`.
* Always returns a non-negative integer less than `def.variant_count`.
* `core.set_param2_variant(param2, variant, def)`
* Returns a modified `param2` with the variant bitfield set to `variant`
with the given node definition `def`.
* `core.get_node_drops(node, toolname)`
* Returns list of itemstrings that are dropped by `node` when dug
with the item `toolname` (not limited to tools).
>>>>>>> 324240af9 (Add node texture variants)
* `node`: node as table or node name
* `toolname`: name of the item used to dig (can be `nil`)
* `tool`: `ItemStack` used to dig (can be `nil`)
@ -7035,6 +7115,11 @@ Item handling
* `item`: the item stack which becomes colored. Can be in string,
table and native form.
* `colorstring`: the new color of the item stack
* `core.itemstring_with_variant(item, variant)`: returns an item string
* Creates an item string with an associated item variant.
* `item`: the item stack which is given the variant. Can be in string,
table or native form.
* `variant`: the new variant of the item stack
Rollback
--------
@ -9779,6 +9864,9 @@ Used by `core.register_node`, `core.register_craftitem`, and
-- {bendy = 2, snappy = 1},
-- {hard = 1, metal = 1, spikes = 1}
variant_count = 1,
-- The number item variants, a positive integer.
inventory_image = "",
-- Texture shown in the inventory GUI
-- Defaults to a 3D rendering of the node if left empty.
@ -10003,6 +10091,9 @@ Used by `core.register_node`.
{
-- <all fields allowed in item definitions>
param2_variant = BitField,
-- The part of param2 from which to read the variant number.
drawtype = "normal", -- See "Node drawtypes"
visual_scale = 1.0,
@ -10016,6 +10107,7 @@ Used by `core.register_node`.
tiles = {tile definition 1, def2, def3, def4, def5, def6},
-- Textures of node; +Y, -Y, +X, -X, +Z, -Z
-- List can be shortened to needed length.
-- This field is also used for Variant number 0, see "Variants" for details.
overlay_tiles = {tile definition 1, def2, def3, def4, def5, def6},
-- Same as `tiles`, but these textures are drawn on top of the base
@ -10023,10 +10115,37 @@ Used by `core.register_node`.
-- texture. If the texture name is an empty string, that overlay is not
-- drawn. Since such tiles are drawn twice, it is not recommended to use
-- overlays on very common nodes.
-- This field is also used for Variant number 0, see "Variants" for details.
special_tiles = {tile definition 1, Tile definition 2},
-- Special textures of node; used rarely.
-- List can be shortened to needed length.
-- This field is also used for Variant number 0, see "Variants" for details.
-- See "Variants"
-- This field is optional.
variants = {
-- Variant number 0 is created from fields
-- tiles, overlay_tiles and special_tiles
-- defined above (outside from variants table).
{ -- Variant number 1.
-- this field is reused from above definition if is not specified.
tiles = {tile definition 1, def2, def3, def4, def5, def6},
-- this field is reused from above definition if is not specified.
overlay_tiles = {def1, def2, def3, def4, def5, def6},
-- this field is reused from above definition if is not specified.
special_tiles = {def1, def2},
},
{ -- Variant number 2.
-- reuse tiles and special_tiles from variant number 0
-- no overlay_tiles
overlay_tiles = {},
...
},
...
},
color = ColorSpec,
-- The node's original color will be multiplied with this color.
@ -10311,6 +10430,9 @@ Used by `core.register_node`.
-- hardware coloring palette color from the dug node.
-- Default is 'false'.
inherit_color = true,
-- variant of the dug node.
-- Default is 'false'.
inherit_variant = true,
},
{
-- Only drop if using an item whose name contains

View file

@ -25,6 +25,23 @@ core.register_node("testnodes:normal", {
groups = { dig_immediate = 3 },
})
-- A regular cube with tiles using color
core.register_node("testnodes:normal_with_color", {
description = S("\"normal\" Drawtype Test Node").."\n"..
S("Opaque texture with color parameter"),
drawtype = "normal",
tiles = {
{ name = "testnodes_normal.png", color = "#A00" },
{ name = "testnodes_normal.png", color = "#00B" },
{ name = "testnodes_normal.png", color = "#0C0" },
{ name = "testnodes_normal.png", color = "#DD0" },
{ name = "testnodes_normal.png", color = "#0EE" },
{ name = "testnodes_normal.png", color = "#F0F" },
},
groups = { dig_immediate = 3 },
})
-- Standard glasslike node
core.register_node("testnodes:glasslike", {
description = S("\"glasslike\" Drawtype Test Node").."\n"..

View file

@ -10,4 +10,5 @@ dofile(path.."/liquids.lua")
dofile(path.."/light.lua")
dofile(path.."/textures.lua")
dofile(path.."/overlays.lua")
dofile(path.."/variants.lua")
dofile(path.."/commands.lua")

View file

@ -7,6 +7,20 @@ core.register_node("testnodes:overlay", {
overlay_tiles = {{name = "testnodes_overlay.png"}},
groups = { dig_immediate = 2 },
})
core.register_node("testnodes:overlay_tile_colors", {
description = S("Texture Overlay Test Node, Tile Colors") .. "\n" ..
S("Uncolorized"),
tiles = {{name = "testnodes_overlayable.png"}},
overlay_tiles = {
{name = "testnodes_overlay.png", color = "#F00"},
{name = "testnodes_overlay.png", color = "#0F0"},
{name = "testnodes_overlay.png", color = "#00F"},
{name = "testnodes_overlay.png", color = "#FF0"},
{name = "testnodes_overlay.png", color = "#0FF"},
{name = "testnodes_overlay.png", color = "#F0F"},
},
groups = { dig_immediate = 2 },
})
core.register_node("testnodes:overlay_color_all", {
description = S("Texture Overlay Test Node, Colorized") .. "\n" ..
S("param2 changes color"),

View file

@ -0,0 +1,290 @@
-- This file is for variant properties.
local S = core.get_translator("testnodes")
local animated_tile = {
name = "testnodes_anim.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 4.0,
},
}
core.register_node("testnodes:variant_animated", {
description = S("Variant Animated Test Node").."\n"..
S("Tiles animate from A to D in 4s cycle").."\n"..
S("Has two variants"),
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 2,
param2_variant = {width = 1, offset = 0},
tiles = {
"testnodes_node.png", animated_tile,
"testnodes_node.png", animated_tile,
"testnodes_node.png", animated_tile,
},
variants = {
{
tiles = {
animated_tile, "testnodes_node.png",
animated_tile, "testnodes_node.png",
animated_tile, "testnodes_node.png",
},
},
},
})
core.register_node("testnodes:variant_color", {
description = S("Variant Color Test Node").."\n"..
S("param2 = color + 3 bit variant").."\n"..
S("Has six unique variants").."\n"..
S("Variants 6 and 7 are same as variant 0"),
paramtype2 = "color",
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 8, -- Last two variants are the same as the first.
param2_variant = {width = 3, offset = 0},
tiles = {"testnodes_1g.png"},
variants = {
{tiles = {"testnodes_2g.png"}},
{tiles = {"testnodes_3g.png"}},
{tiles = {"testnodes_4g.png"}},
{tiles = {"testnodes_5g.png"}},
{tiles = {"testnodes_6g.png"}},
},
palette = "testnodes_palette_wallmounted.png",
})
core.register_node("testnodes:variant_tile_color", {
description = S("Variant Tile Color Test Node").."\n"..
S("Has seven unique variants").."\n"..
S("Variant 7 is same as variant 0 (red colored)"),
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 8, -- Last one variant are the same as the first.
param2_variant = {width = 3, offset = 0},
tiles = {{ name = "testnodes_1g.png", color = "#F00"}},
variants = {
{tiles = {{ name = "testnodes_1g.png", color = "#0F0"}}},
{tiles = {{ name = "testnodes_1g.png", color = "#00F"}}},
{tiles = {{ name = "testnodes_1g.png", color = "#FF0"}}},
{tiles = {{ name = "testnodes_1g.png", color = "#0FF"}}},
{tiles = {{ name ="testnodes_1g.png", color = "#F0F"}}},
{tiles = {{ name ="testnodes_1g.png"}}},
},
})
core.register_node("testnodes:variant_drop", {
description = S("Variant Drop Test Node").."\n"..
S("Has five variants").."\n"..
S("Variiant 1 is copied from variant 0").."\n"..
S("Drops one node with an inherited variant and one without"),
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 5,
param2_variant = {width = 3, offset = 0},
tiles = {"testnodes_1.png"},
variants = {
nil,
{tiles = {"testnodes_2.png"}},
{tiles = {"testnodes_3.png"}},
{tiles = {"testnodes_4.png"}},
},
drop = {
max_items = 2,
items = {
{items = {"testnodes:variant_drop"}, inherit_variant = true},
{items = {"testnodes:variant_drop"}, inherit_variant = false},
},
},
})
core.register_node("testnodes:variant_facedir", {
description = S("Variant Facedir Test Node").."\n"..
S("param2 = 3 bit variant + facedir").."\n"..
S("Has six variants"),
paramtype2 = "facedir",
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 6,
param2_variant = {width = 3, offset = 5},
tiles = {"testnodes_1f.png"},
variants = {
{tiles = {"testnodes_2f.png"}},
{tiles = {"testnodes_3f.png"}},
{tiles = {"testnodes_4f.png"}},
{tiles = {"testnodes_5f.png"}},
{tiles = {"testnodes_6f.png"}},
},
})
core.register_node("testnodes:variant_falling", {
description = S("Variant Falling Test Node").."\n"..
S("Has six variants"),
is_ground_content = false,
groups = {dig_immediate = 3, falling_node = 1},
variant_count = 6,
param2_variant = {width = 3, offset = 0},
tiles = {"testnodes_1.png"},
variants = {
{tiles = {"testnodes_2.png"}},
{tiles = {"testnodes_3.png"}},
{tiles = {"testnodes_4.png"}},
{tiles = {"testnodes_5.png"}},
{tiles = {"testnodes_6.png"}},
},
})
core.register_node("testnodes:variant_falling_torchlike", {
description = S("Variant Falling Torchlike Test Node").."\n"..
S("Has six variants"),
paramtype = "light",
sunlight_propagates = true,
is_ground_content = false,
groups = {dig_immediate = 3, falling_node = 1},
variant_count = 6,
param2_variant = {width = 3, offset = 0},
drawtype = "torchlike",
tiles = {"testnodes_1.png"},
variants = {
{tiles = {"testnodes_2.png"}},
{tiles = {"testnodes_3.png"}},
{tiles = {"testnodes_4.png"}},
{tiles = {"testnodes_5.png"}},
{tiles = {"testnodes_6.png"}},
},
})
core.register_node("testnodes:variant_mesh", {
description = S("Variant Mesh Test Node").."\n"..
S("Has ten variants"),
paramtype = "light",
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 10,
param2_variant = {width = 8, offset = 0},
drawtype = "mesh",
mesh = "testnodes_ocorner.obj",
tiles = {"testnodes_mesh_stripes.png"},
variants = {
{tiles = {"testnodes_mesh_stripes2.png"}},
{tiles = {"testnodes_mesh_stripes3.png"}},
{tiles = {"testnodes_mesh_stripes4.png"}},
{tiles = {"testnodes_mesh_stripes5.png"}},
{tiles = {"testnodes_mesh_stripes6.png"}},
{tiles = {"testnodes_mesh_stripes7.png"}},
{tiles = {"testnodes_mesh_stripes8.png"}},
{tiles = {"testnodes_mesh_stripes9.png"}},
{tiles = {"testnodes_mesh_stripes10.png"}},
},
})
core.register_node("testnodes:variant_overlay", {
description = S("Variant Overlay Test Node").."\n"..
S("Has four variants"),
S("Variant 1 is same as variant 0"),
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 4,
param2_variant = {width = 2, offset = 0},
tiles = {"testnodes_node.png"},
overlay_tiles = {{name = "testnodes_overlay.png", color = "#F00"}},
variants = {
{
tiles = {"testnodes_node.png"},
},
{
tiles = {"testnodes_node.png"},
overlay_tiles = {{name = "testnodes_overlay.png", color = "#0FF"}},
},
{
tiles = {"testnodes_node.png"},
overlay_tiles = {},
},
},
})
core.register_node("testnodes:variant_overlay_color", {
description = S("Variant Overlay Test Node With Node Color").."\n"..
S("Has four variants"),
S("Variant 1 is same as variant 1"),
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 4,
param2_variant = {width = 2, offset = 0},
tiles = {"testnodes_node.png"},
overlay_tiles = {{name = "testnodes_overlay.png", color = "#F00"}},
color = "#00F",
variants = {
{
tiles = {"testnodes_node.png"},
},
{
tiles = {"testnodes_node.png"},
overlay_tiles = {{name = "testnodes_overlay.png", color = "#0FF"}},
},
{
tiles = {"testnodes_node.png"},
overlay_tiles = {},
},
},
})
core.register_node("testnodes:variant_plantlike_rooted", {
description = S("Variant Plantlike Rooted Test Node").."\n"..
S("Has six variants"),
paramtype = "light",
is_ground_content = false,
groups = {dig_immediate = 3},
drawtype = "plantlike_rooted",
variant_count = 8,
param2_variant = {width = 8, offset = 0},
tiles = {"testnodes_1.png"},
special_tiles = {"testnodes_6.png"},
variants = {
{tiles = {"testnodes_2.png"}, special_tiles = {"testnodes_5.png"}},
{tiles = {"testnodes_3.png"}, special_tiles = {"testnodes_4.png"}},
{tiles = {"testnodes_4.png"}, special_tiles = {"testnodes_3.png"}},
{tiles = {"testnodes_5.png"}, special_tiles = {"testnodes_2.png"}},
{tiles = {"testnodes_6.png"}, special_tiles = {"testnodes_1.png"}},
{tiles = {"testnodes_6.png"}},
{special_tiles = {"testnodes_1.png"}},
},
})
core.register_node("testnodes:variant_raillike", {
description = S("Variant Raillike Test Node").."\n"..
S("Has four variants"),
paramtype = "light",
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 4,
param2_variant = {width = 2, offset = 0},
drawtype = "raillike",
variants = {
[0] = {tiles = {"testnodes_1.png"}},
{tiles = {"testnodes_2.png"}},
{tiles = {"testnodes_3.png"}},
{tiles = {"testnodes_4.png"}},
},
})
core.register_node("testnodes:variant_wallmounted", {
description = S("Variant Wallmounted Test Node").."\n"..
S("Has six variants corresponding to direction"),
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = false,
groups = {dig_immediate = 3},
variant_count = 6,
param2_variant = {width = 3, offset = 0},
tiles = {"testnodes_1w.png"},
variants = {
{tiles = {"testnodes_2w.png"}},
{tiles = {"testnodes_3w.png"}},
{tiles = {"testnodes_4w.png"}},
{tiles = {"testnodes_5w.png"}},
{tiles = {"testnodes_6w.png"}},
},
})

View file

@ -98,7 +98,7 @@ void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile_ret)
// Returns a special tile, ready for use, non-rotated.
void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool apply_crack)
{
*tile_ret = cur_node.f->special_tiles[index];
*tile_ret = cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][index];
TileLayer *top_layer = nullptr;
for (auto &layernum : tile_ret->layers) {
@ -1023,7 +1023,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
// Optionally render internal liquid level defined by param2
// Liquid is textured with 1 tile defined in nodedef 'special_tiles'
if (param2 > 0 && cur_node.f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
cur_node.f->special_tiles[0].layers[0].texture) {
cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][0].layers[0].texture) {
// Internal liquid level has param2 range 0 .. 63,
// convert it to -0.5 .. 0.5
float vlev = (param2 / 63.0f) * 2.0f - 1.0f;

View file

@ -3648,6 +3648,14 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
}
}
// Apply variant
if (!place_param2 && predicted_f.variant_count > 1) {
u16 variant = mystoi(selected_item.metadata.getString("variant"),
0, predicted_f.variant_count - 1);
predicted_node.setParam2(
predicted_f.param2_variant.set(predicted_node.getParam2(), variant));
}
// Add node to client map
try {
LocalPlayer *player = client->getEnv().getLocalPlayer();

View file

@ -15,8 +15,8 @@ ItemVisualsManager::ItemVisuals::~ItemVisuals() {
wield_mesh.mesh->drop();
}
ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item,
Client *client) const
ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals(
const ItemStack &item, Client *client, u16 &variant_count) const
{
// This is not thread-safe
sanity_check(std::this_thread::get_id() == m_main_thread);
@ -32,6 +32,8 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It
if (!inventory_overlay.empty())
cache_key += ":" + inventory_overlay;
variant_count = client->ndef()->get(cache_key).variant_count;
// Skip if already in cache
auto it = m_cached_item_visuals.find(cache_key);
if (it != m_cached_item_visuals.end())
@ -43,14 +45,23 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It
ITextureSource *tsrc = client->getTextureSource();
// Create new ItemVisuals
auto cc = std::make_unique<ItemVisuals>();
auto cc = std::make_unique<ItemVisuals[]>(variant_count);
cc->inventory_texture = NULL;
for (u16 v = 0; v < variant_count; v++) {
// Create an inventory texture
cc[v].inventory_texture = NULL;
if (!inventory_image.empty())
cc->inventory_texture = tsrc->getTexture(inventory_image);
getItemMesh(client, item, &(cc->wield_mesh));
cc[v].inventory_texture = tsrc->getTexture(inventory_image);
cc->palette = tsrc->getPalette(def.palette_image);
ItemStack item = ItemStack();
item.name = def.name;
if (v > 0)
item.metadata.setString("variant", std::to_string(v));
getItemMesh(client, item, &(cc[v].wield_mesh));
cc[v].palette = tsrc->getPalette(def.palette_image);
}
// Put in cache
ItemVisuals *ptr = cc.get();
@ -59,25 +70,29 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It
}
video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item,
Client *client) const
u16 variant, Client *client) const
{
ItemVisuals *iv = createItemVisuals(item, client);
if (!iv)
u16 variant_count;
ItemVisuals *iv = createItemVisuals(item, client, variant_count);
if (!iv || (variant >= variant_count))
return nullptr;
return iv->inventory_texture;
return iv[variant].inventory_texture;
}
ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, Client *client) const
ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, u16 variant,
Client *client) const
{
ItemVisuals *iv = createItemVisuals(item, client);
if (!iv)
u16 variant_count;
ItemVisuals *iv = createItemVisuals(item, client, variant_count);
if (!iv || (variant >= variant_count))
return nullptr;
return &(iv->wield_mesh);
return &(iv[variant].wield_mesh);
}
Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const
{
ItemVisuals *iv = createItemVisuals(item, client);
u16 variant_count;
ItemVisuals *iv = createItemVisuals(item, client, variant_count);
if (!iv)
return nullptr;
return iv->palette;

View file

@ -30,11 +30,11 @@ struct ItemVisualsManager
}
// Get item inventory texture
video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const;
video::ITexture* getInventoryTexture(const ItemStack &item, u16 variant, Client *client) const;
// Get item wield mesh
// Once said to return nullptr if there is an inventory image, but this is wrong
ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const;
ItemMesh* getWieldMesh(const ItemStack &item, u16 variant, Client *client) const;
// Get item palette
Palette* getPalette(const ItemStack &item, Client *client) const;
@ -63,7 +63,7 @@ private:
// The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread;
// Cached textures and meshes
mutable std::unordered_map<std::string, std::unique_ptr<ItemVisuals>> m_cached_item_visuals;
mutable std::unordered_map<std::string, std::unique_ptr<ItemVisuals[]>> m_cached_item_visuals;
ItemVisuals* createItemVisuals(const ItemStack &item, Client *client) const;
ItemVisuals* createItemVisuals(const ItemStack &item, Client *client, u16 &variant_count) const;
};

View file

@ -344,7 +344,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data,
{
const NodeDefManager *ndef = data->m_nodedef;
const ContentFeatures &f = ndef->get(mn);
tile = f.tiles[tileindex];
tile = f.tiles[mn.getVariant(f)][tileindex];
bool has_crack = p == data->m_crack_pos_relative;
for (TileLayer &layer : tile.layers) {
if (layer.empty())

View file

@ -403,7 +403,9 @@ void Minimap::blitMinimapPixelsToImageSurface(
MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size];
const ContentFeatures &f = m_ndef->get(mmpixel->n);
const TileDef *tile = &f.tiledef[0];
u16 v = mmpixel->n.getVariant(f);
const TileDef *tile = &f.tiledef[v][0];
video::SColor minimap_color = f.minimap_color[v];
// Color of the 0th tile (mostly this is the topmost)
if(tile->has_color)
@ -411,9 +413,9 @@ void Minimap::blitMinimapPixelsToImageSurface(
else
mmpixel->n.getColor(f, &tilecolor);
tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255);
tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255);
tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255);
tilecolor.setRed(tilecolor.getRed() * minimap_color.getRed() / 255);
tilecolor.setGreen(tilecolor.getGreen() * minimap_color.getGreen() / 255);
tilecolor.setBlue(tilecolor.getBlue() * minimap_color.getBlue() / 255);
tilecolor.setAlpha(240);
map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor);

View file

@ -867,7 +867,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
texid = tilenum - 1;
else
texid = myrand_range(0,5);
const TileLayer &tile = f.tiles[texid].layers[0];
const TileLayer &tile = f.tiles[n.getVariant(f)][texid].layers[0];
p.animation.type = TAT_NONE;
// Only use first frame of animated texture

View file

@ -17,6 +17,7 @@
#include "client/texturesource.h"
#include "log.h"
#include "util/numeric.h"
#include "util/string.h"
#include <map>
#include <IMeshManipulator.h>
#include "client/renderingengine.h"
@ -292,7 +293,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
}
}
static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, u16 variant,
std::vector<ItemMeshBufferInfo> *buffer_info, const ContentFeatures &f)
{
n.setParam1(0xff);
@ -310,6 +311,9 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
n.setParam2(1);
}
if (f.variant_count > 1)
n.setParam2(f.param2_variant.set(n.getParam2(), variant));
MeshCollector collector(v3f(0), v3f());
{
MeshMakeData mmd(client->ndef(), 1, MeshGrid{1});
@ -354,6 +358,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
const ItemDefinition &def = item.getDefinition(idef);
const ContentFeatures &f = ndef->get(def.name);
content_t id = ndef->getId(def.name);
u16 variant = f.variant_count > 1 ?
mystoi(item.metadata.getString("variant"), 0, f.variant_count - 1) : 0;
scene::SMesh *mesh = nullptr;
@ -395,8 +401,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
v3f wscale = wield_scale;
if (f.drawtype == NDT_FLOWINGLIQUID)
wscale.Z *= 0.1f;
const TileLayer &l0 = f.tiles[0].layers[0];
const TileLayer &l1 = f.tiles[0].layers[1];
const TileLayer &l0 = f.tiles[variant][0].layers[0];
const TileLayer &l1 = f.tiles[variant][0].layers[1];
setExtruded(tsrc->getTextureName(l0.texture_id),
tsrc->getTextureName(l1.texture_id),
wscale, tsrc,
@ -408,7 +414,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
}
case NDT_PLANTLIKE_ROOTED: {
// use the plant tile
const TileLayer &l0 = f.special_tiles[0].layers[0];
const TileLayer &l0 = f.special_tiles[variant][0].layers[0];
setExtruded(tsrc->getTextureName(l0.texture_id),
"", wield_scale, tsrc,
l0.animation_frame_count);
@ -421,7 +427,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
if (def.place_param2)
n.setParam2(*def.place_param2);
mesh = createGenericNodeMesh(client, n, &m_buffer_info, f);
mesh = createGenericNodeMesh(client, n, variant, &m_buffer_info, f);
changeToMesh(mesh);
mesh->drop();
m_meshnode->setScale(
@ -544,6 +550,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
const ItemDefinition &def = item.getDefinition(idef);
const ContentFeatures &f = ndef->get(def.name);
content_t id = ndef->getId(def.name);
u16 variant = f.variant_count > 1 ?
mystoi(item.metadata.getString("variant"), 0, f.variant_count - 1) : 0;
FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized");
@ -569,8 +577,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
} else if (def.type == ITEM_NODE) {
switch (f.drawtype) {
case NDT_PLANTLIKE: {
const TileLayer &l0 = f.tiles[0].layers[0];
const TileLayer &l1 = f.tiles[0].layers[1];
const TileLayer &l0 = f.tiles[variant][0].layers[0];
const TileLayer &l1 = f.tiles[variant][0].layers[1];
mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(l0.texture_id),
tsrc->getTextureName(l1.texture_id));
@ -581,7 +589,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
}
case NDT_PLANTLIKE_ROOTED: {
// Use the plant tile
const TileLayer &l0 = f.special_tiles[0].layers[0];
const TileLayer &l0 = f.special_tiles[variant][0].layers[0];
mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(l0.texture_id), "");
result->buffer_info.emplace_back(l0.has_color, l0.color);
@ -593,7 +601,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
if (def.place_param2)
n.setParam2(*def.place_param2);
mesh = createGenericNodeMesh(client, n, &result->buffer_info, f);
mesh = createGenericNodeMesh(client, n, variant, &result->buffer_info, f);
scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
break;
}

View file

@ -45,6 +45,9 @@ void drawItemStack(
auto *idef = client->idef();
const ItemDefinition &def = item.getDefinition(idef);
ItemVisualsManager* item_visuals = client->getItemVisualsManager();
const ContentFeatures &f = client->ndef()->get(def.name);
u16 variant = f.variant_count > 1 ?
mystoi(item.metadata.getString("variant"), 0, f.variant_count - 1) : 0;
bool draw_overlay = false;
@ -60,7 +63,7 @@ void drawItemStack(
// Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) {
imesh = item_visuals->getWieldMesh(item, client);
imesh = item_visuals->getWieldMesh(item, variant, client);
has_mesh = imesh && imesh->mesh;
}
if (has_mesh) {
@ -155,7 +158,7 @@ void drawItemStack(
draw_overlay = def.type == ITEM_NODE && inventory_image.empty();
} else { // Otherwise just draw as 2D
video::ITexture *texture = item_visuals->getInventoryTexture(item, client);
video::ITexture *texture = item_visuals->getInventoryTexture(item, variant, client);
video::SColor color;
if (texture) {
color = item_visuals->getItemstackColor(item, client);

View file

@ -38,6 +38,11 @@ void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const
*color = f.color;
}
u16 MapNode::getVariant(const ContentFeatures &f) const
{
return f.variant_count > 1 ? f.param2_variant.get(param2) % f.variant_count : 0;
}
u8 MapNode::getFaceDir(const NodeDefManager *nodemgr,
bool allow_wallmounted) const
{

View file

@ -196,6 +196,12 @@ struct alignas(u32) MapNode
*/
void getColor(const ContentFeatures &f, video::SColor *color) const;
/*!
* Returns the variant of the node.
* \param f content features of this node
*/
u16 getVariant(const ContentFeatures &f) const;
inline void setLight(LightBank bank, u8 a_light, ContentLightingFlags f) noexcept
{
// If node doesn't contain light data, ignore this

View file

@ -316,12 +316,16 @@ ContentFeatures::ContentFeatures()
ContentFeatures::~ContentFeatures()
{
#if CHECK_CLIENT_BUILD()
for (auto &t : tiles) {
for (u16 j = 0; j < 6; j++) {
delete tiles[j].layers[0].frames;
delete tiles[j].layers[1].frames;
delete t[j].layers[0].frames;
delete t[j].layers[1].frames;
}
}
for (auto &t : special_tiles) {
for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
delete special_tiles[j].layers[0].frames;
delete t[j].layers[0].frames;
}
#endif
}
@ -331,6 +335,8 @@ void ContentFeatures::reset()
Cached stuff
*/
#if CHECK_CLIENT_BUILD()
tiles = std::vector<std::array<TileSpec, 6> >(1);
special_tiles = std::vector<std::array<TileSpec, CF_SPECIAL_COUNT> >(1);
solidness = 2;
visual_solidness = 0;
backface_culling = true;
@ -355,17 +361,18 @@ void ContentFeatures::reset()
mesh.clear();
#if CHECK_CLIENT_BUILD()
mesh_ptr = nullptr;
minimap_color = video::SColor(0, 0, 0, 0);
minimap_color = std::vector<video::SColor>(1, video::SColor(0, 0, 0, 0));
#endif
visual_scale = 1.0;
for (auto &i : tiledef)
i = TileDef();
for (auto &j : tiledef_special)
j = TileDef();
tiledef = std::vector<std::array<TileDef, 6> >(1);
tiledef_overlay = std::vector<std::array<TileDef, 6> >(1);
tiledef_special = std::vector<std::array<TileDef, CF_SPECIAL_COUNT> >(1);
alpha = ALPHAMODE_OPAQUE;
post_effect_color = video::SColor(0, 0, 0, 0);
param_type = CPT_NONE;
param_type_2 = CPT2_NONE;
variant_count = 1;
param2_variant = BitField<u8>();
is_ground_content = false;
light_propagates = false;
sunlight_propagates = false;
@ -457,12 +464,12 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
os << serializeString16(mesh);
writeF32(os, visual_scale);
writeU8(os, 6);
for (const TileDef &td : tiledef)
for (const TileDef &td : tiledef[0])
td.serialize(os, protocol_version);
for (const TileDef &td : tiledef_overlay)
for (const TileDef &td : tiledef_overlay[0])
td.serialize(os, protocol_version);
writeU8(os, CF_SPECIAL_COUNT);
for (const TileDef &td : tiledef_special) {
for (const TileDef &td : tiledef_special[0]) {
td.serialize(os, protocol_version);
}
writeU8(os, getAlphaForLegacy());
@ -535,6 +542,21 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
writeU8(os, move_resistance);
writeU8(os, liquid_move_physics);
writeU8(os, post_effect_color_shaded);
writeU16(os, variant_count);
writeU8(os, param2_variant.getWidth());
writeU8(os, param2_variant.getOffset());
for (u16 v = 1; v < variant_count; v++) {
for (const TileDef &td : tiledef[v])
td.serialize(os, protocol_version);
}
for (u16 v = 1; v < variant_count; v++) {
for (const TileDef &td : tiledef_overlay[v])
td.serialize(os, protocol_version);
}
for (u16 v = 1; v < variant_count; v++) {
for (const TileDef &td : tiledef_special[v])
td.serialize(os, protocol_version);
}
}
void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
@ -568,13 +590,13 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
visual_scale = readF32(is);
if (readU8(is) != 6)
throw SerializationError("unsupported tile count");
for (TileDef &td : tiledef)
for (TileDef &td : tiledef[0])
td.deSerialize(is, drawtype, protocol_version);
for (TileDef &td : tiledef_overlay)
for (TileDef &td : tiledef_overlay[0])
td.deSerialize(is, drawtype, protocol_version);
if (readU8(is) != CF_SPECIAL_COUNT)
throw SerializationError("unsupported CF_SPECIAL_COUNT");
for (TileDef &td : tiledef_special)
for (TileDef &td : tiledef_special[0])
td.deSerialize(is, drawtype, protocol_version);
setAlphaFromLegacy(readU8(is));
color.setRed(readU8(is));
@ -665,6 +687,43 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
if (is.eof())
throw SerializationError("");
post_effect_color_shaded = tmp;
u16 tmp_variant_count = readU16(is);
if (is.eof())
throw SerializationError("");
// Reserve space first for memory safety.
tiledef.reserve(variant_count);
tiledef_overlay.reserve(variant_count);
tiledef_special.reserve(variant_count);
for (u16 v = 1; v < tmp_variant_count; v++) {
tiledef.emplace_back();
tiledef_overlay.emplace_back();
tiledef_special.emplace_back();
}
variant_count = tmp_variant_count;
tmp = readU8(is);
u8 tmp2 = readU8(is);
if (is.eof())
throw SerializationError("");
param2_variant = BitField<u8>(tmp, tmp2);
for (u16 v = 1; v < variant_count; v++) {
for (TileDef &td : tiledef[v])
td.deSerialize(is, drawtype, protocol_version);
}
for (u16 v = 1; v < variant_count; v++) {
for (TileDef &td : tiledef_overlay[v])
td.deSerialize(is, drawtype, protocol_version);
}
for (u16 v = 1; v < variant_count; v++) {
for (TileDef &td : tiledef_special[v])
td.deSerialize(is, drawtype, protocol_version);
}
} catch (SerializationError &e) {};
}
@ -763,22 +822,27 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
{
// Figure out the actual tiles to use
TileDef tdef[6];
std::vector<std::array<TileDef, 6> > tdef(variant_count);
for (u16 v = 0; v < variant_count; v++) {
for (u32 j = 0; j < 6; j++) {
tdef[j] = tiledef[j];
if (tdef[j].name.empty()) {
tdef[j].name = "no_texture.png";
tdef[j].backface_culling = false;
tdef[v][j] = tiledef[v][j];
if (tdef[v][j].name.empty()) {
tdef[v][j].name = "no_texture.png";
tdef[v][j].backface_culling = false;
}
}
}
// also the overlay tiles
TileDef tdef_overlay[6];
std::vector<std::array<TileDef, 6> > tdef_overlay(variant_count);
for (u16 v = 0; v < variant_count; v++) {
for (u32 j = 0; j < 6; j++)
tdef_overlay[j] = tiledef_overlay[j];
tdef_overlay[v][j] = tiledef_overlay[v][j];
}
// also the special tiles
TileDef tdef_spec[6];
for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) {
tdef_spec[j] = tiledef_special[j];
std::vector<std::array<TileDef, CF_SPECIAL_COUNT> > tdef_spec(variant_count);
for (u16 v = 0; v < variant_count; v++) {
for (u32 j = 0; j < CF_SPECIAL_COUNT; j++)
tdef_spec[v][j] = tiledef_special[v][j];
}
bool is_liquid = false;
@ -830,9 +894,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
solidness = 0;
visual_solidness = 1;
} else if (tsettings.leaves_style == LEAVES_SIMPLE) {
for (u16 v = 0; v < variant_count; v++) {
for (u32 j = 0; j < 6; j++) {
if (!tdef_spec[j].name.empty())
tdef[j].name = tdef_spec[j].name;
if (!tdef_spec[v][j].name.empty())
tdef[v][j].name = tdef_spec[v][j].name;
}
}
drawtype = NDT_GLASSLIKE;
solidness = 0;
@ -847,9 +913,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
drawtype = NDT_NORMAL;
solidness = 2;
}
for (TileDef &td : tdef)
for (u16 v = 0; v < variant_count; v++) {
for (TileDef &td : tdef[v])
td.name += std::string("^[noalpha");
}
}
if (waving >= 1)
material_type = TILE_MATERIAL_WAVING_LEAVES;
break;
@ -907,24 +975,31 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype);
// minimap pixel color = average color of top tile
if (tsettings.enable_minimap && !tdef[0].name.empty() && drawtype != NDT_AIRLIKE)
minimap_color = tsrc->getTextureAverageColor(tdef[0].name);
if (tsettings.enable_minimap && !tdef[0][0].name.empty() && drawtype != NDT_AIRLIKE) {
minimap_color = std::vector<video::SColor>(variant_count);
for (u16 v = 0; v < variant_count; v++) {
if (!tdef[v][0].name.empty())
minimap_color[v] = tsrc->getTextureAverageColor(tdef[v][0].name);
}
}
// Tiles (fill in f->tiles[])
bool any_polygon_offset = false;
tiles = std::vector<std::array<TileSpec, 6> >(variant_count);
for (u16 v = 0; v < variant_count; v++) {
for (u16 j = 0; j < 6; j++) {
tiles[j].world_aligned = isWorldAligned(tdef[j].align_style,
tiles[v][j].world_aligned = isWorldAligned(tdef[v][j].align_style,
tsettings.world_aligned_mode, drawtype);
fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j],
fillTileAttribs(tsrc, &tiles[v][j].layers[0], tiles[v][j], tdef[v][j],
color, material_type, tile_shader,
tdef[j].backface_culling, tsettings);
if (!tdef_overlay[j].name.empty())
fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j],
tdef[v][j].backface_culling, tsettings);
if (!tdef_overlay[v][j].name.empty())
fillTileAttribs(tsrc, &tiles[v][j].layers[1], tiles[v][j], tdef_overlay[v][j],
color, overlay_material, overlay_shader,
tdef[j].backface_culling, tsettings);
tdef[v][j].backface_culling, tsettings);
tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty();
any_polygon_offset |= tiles[j].layers[0].need_polygon_offset;
tiles[v][j].layers[0].need_polygon_offset = !tiles[v][j].layers[1].empty();
any_polygon_offset |= tiles[v][j].layers[0].need_polygon_offset;
}
if (drawtype == NDT_MESH && any_polygon_offset) {
@ -934,7 +1009,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
// We can't assume this for mesh nodes, so apply it to all tiles (= materials)
// then.
for (u16 j = 0; j < 6; j++)
tiles[j].layers[0].need_polygon_offset = true;
tiles[v][j].layers[0].need_polygon_offset = true;
}
}
MaterialType special_material = material_type;
@ -947,10 +1023,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype);
// Special tiles (fill in f->special_tiles[])
special_tiles = std::vector<std::array<TileSpec, CF_SPECIAL_COUNT> >(variant_count);
for (u16 v = 0; v < variant_count; v++) {
for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j],
fillTileAttribs(tsrc,
&special_tiles[v][j].layers[0], special_tiles[v][j], tdef_spec[v][j],
color, special_material, special_shader,
tdef_spec[j].backface_culling, tsettings);
tdef_spec[v][j].backface_culling, tsettings);
}
if (param_type_2 == CPT2_COLOR ||
param_type_2 == CPT2_COLORED_FACEDIR ||
@ -1043,7 +1123,7 @@ void NodeDefManager::clear()
ContentFeatures f;
f.name = "unknown";
for (int t = 0; t < 6; t++)
f.tiledef[t].name = "unknown_node.png";
f.tiledef[0][t].name = "unknown_node.png";
// Insert directly into containers
content_t c = CONTENT_UNKNOWN;
m_content_features[c] = f;
@ -1409,52 +1489,55 @@ void NodeDefManager::applyTextureOverrides(const std::vector<TextureOverride> &o
ContentFeatures &nodedef = m_content_features[id];
auto apply = [&] (TileDef &tile) {
tile.name = texture_override.texture;
// For now this works with tiledef_special since CF_SPECIAL_COUNT == 6.
auto apply = [&] (std::vector<std::array<TileDef, 6> > &tiledef, u32 i) {
for (u16 v = 0; v < nodedef.variant_count; v++) {
tiledef[v][i].name = texture_override.texture;
if (texture_override.world_scale > 0) {
tile.align_style = ALIGN_STYLE_WORLD;
tile.scale = texture_override.world_scale;
tiledef[v][i].align_style = ALIGN_STYLE_WORLD;
tiledef[v][i].scale = texture_override.world_scale;
}
}
};
// Override tiles
if (texture_override.hasTarget(OverrideTarget::TOP))
apply(nodedef.tiledef[0]);
apply(nodedef.tiledef, 0);
if (texture_override.hasTarget(OverrideTarget::BOTTOM))
apply(nodedef.tiledef[1]);
apply(nodedef.tiledef, 1);
if (texture_override.hasTarget(OverrideTarget::RIGHT))
apply(nodedef.tiledef[2]);
apply(nodedef.tiledef, 2);
if (texture_override.hasTarget(OverrideTarget::LEFT))
apply(nodedef.tiledef[3]);
apply(nodedef.tiledef, 3);
if (texture_override.hasTarget(OverrideTarget::BACK))
apply(nodedef.tiledef[4]);
apply(nodedef.tiledef, 4);
if (texture_override.hasTarget(OverrideTarget::FRONT))
apply(nodedef.tiledef[5]);
apply(nodedef.tiledef, 5);
// Override special tiles, if applicable
if (texture_override.hasTarget(OverrideTarget::SPECIAL_1))
apply(nodedef.tiledef_special[0]);
apply(nodedef.tiledef_special, 0);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_2))
apply(nodedef.tiledef_special[1]);
apply(nodedef.tiledef_special, 1);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_3))
apply(nodedef.tiledef_special[2]);
apply(nodedef.tiledef_special, 2);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_4))
apply(nodedef.tiledef_special[3]);
apply(nodedef.tiledef_special, 3);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_5))
apply(nodedef.tiledef_special[4]);
apply(nodedef.tiledef_special, 4);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_6))
apply(nodedef.tiledef_special[5]);
apply(nodedef.tiledef_special, 5);
}
}

View file

@ -9,6 +9,7 @@
#include <iostream>
#include <memory> // shared_ptr
#include <map>
#include <array>
#include "mapnode.h"
#include "nameidmapping.h"
#if CHECK_CLIENT_BUILD()
@ -22,6 +23,7 @@ class Client;
#include "texture_override.h" // TextureOverride
#include "tileanimation.h"
#include "util/pointabilities.h"
#include "util/numeric.h"
class IItemDefManager;
class ITextureSource;
@ -308,9 +310,9 @@ struct ContentFeatures
#if CHECK_CLIENT_BUILD()
// 0 1 2 3 4 5
// up down right left back front
TileSpec tiles[6];
std::vector<std::array<TileSpec, 6> > tiles; // Length is variant count
// Special tiles
TileSpec special_tiles[CF_SPECIAL_COUNT];
std::vector<std::array<TileSpec, CF_SPECIAL_COUNT> > special_tiles;
u8 solidness; // Used when choosing which face is drawn
u8 visual_solidness; // When solidness=0, this tells how it looks like
bool backface_culling;
@ -336,6 +338,10 @@ struct ContentFeatures
ContentParamType param_type;
// Type of MapNode::param2
ContentParamType2 param_type_2;
// Number of node variants
u16 variant_count = 1;
// Bit field for variant in param2
BitField<u8> param2_variant;
// --- VISUAL PROPERTIES ---
@ -343,13 +349,13 @@ struct ContentFeatures
std::string mesh;
#if CHECK_CLIENT_BUILD()
scene::SMesh *mesh_ptr; // mesh in case of mesh node
video::SColor minimap_color;
std::vector<video::SColor> minimap_color; // Length is variant count
#endif
float visual_scale; // Misc. scale parameter
TileDef tiledef[6];
std::vector<std::array<TileDef, 6> > tiledef; // Length is variant count
// These will be drawn over the base tiles.
TileDef tiledef_overlay[6];
TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid
std::vector<std::array<TileDef, 6> > tiledef_overlay; // Length is variant count
std::vector<std::array<TileDef, CF_SPECIAL_COUNT> > tiledef_special; // eg. flowing liquid
AlphaMode alpha;
// The color of the node.
video::SColor color;

View file

@ -201,6 +201,8 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
{
std::string type(enum_to_string(es_ItemType, i.type));
// variant_count is presently not included, unlike in the server-side definition table.
lua_newtable(L);
lua_pushstring(L, i.name.c_str());
lua_setfield(L, -2, "name");
@ -660,6 +662,8 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
// color = ...
lua_getfield(L, index, "color");
tiledef.has_color = read_color(L, -1, &tiledef.color);
if (!tiledef.has_color)
tiledef.color = video::SColor();
lua_pop(L, 1);
// animation = {}
lua_getfield(L, index, "animation");
@ -673,12 +677,76 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
return tiledef;
}
void read_tiledefs(lua_State *L, int index, u8 drawtype, bool special,
std::array<TileDef, 6> &tiledefs)
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
if (lua_istable(L, index)) {
lua_pushnil(L);
int i = 0;
while (lua_next(L, index) != 0) {
// Read tiledef from value
tiledefs[i] = read_tiledef(L, -1, drawtype, special);
// removes value, keeps key for next iteration
lua_pop(L, 1);
i++;
if (i == 6) {
lua_pop(L, 1);
break;
}
}
// Allow empthy {} definition of tile
if (i==0) {
tiledefs[0] = TileDef();
i++;
}
// Copy last value to all remaining textures
if (i >= 1) {
TileDef lasttile = tiledefs[i - 1];
while (i < 6) {
tiledefs[i] = lasttile;
i++;
}
}
}
}
void read_default_tiledefs(lua_State *L, int index, u8 drawtype, bool special,
std::vector<std::array<TileDef, 6> > &tiledefs)
{
read_tiledefs(L, index, drawtype, special, tiledefs[0]);
for (u16 v = 1; v < tiledefs.size(); v++)
tiledefs[v] = tiledefs[0];
}
/******************************************************************************/
void read_content_features(lua_State *L, ContentFeatures &f, int index)
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
{
int variant_count = getintfield_default(L, index, "variant_count", 1);
if (variant_count < 1 || variant_count > UINT16_MAX)
throw LuaError("Invalid variant_count " + variant_count);
if (variant_count > 1) {
// Reserve memory first to avoid out-of-bounds indexing.
f.tiledef = std::vector<std::array<TileDef, 6> >(variant_count);
f.tiledef_overlay = std::vector<std::array<TileDef, 6> >(variant_count);
f.tiledef_special =
std::vector<std::array<TileDef, CF_SPECIAL_COUNT> >(variant_count);
}
f.variant_count = variant_count;
}
lua_getfield(L, index, "param2_variant");
f.param2_variant = read_bitfield<u8>(L, -1);
lua_pop(L, 1);
/* Cache existence of some callbacks */
lua_getfield(L, index, "on_construct");
f.has_on_construct = !lua_isnil(L, -1);
@ -718,76 +786,45 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
// tiles = {}
lua_getfield(L, index, "tiles");
if(lua_istable(L, -1)){
int table = lua_gettop(L);
lua_pushnil(L);
int i = 0;
while(lua_next(L, table) != 0){
// Read tiledef from value
f.tiledef[i] = read_tiledef(L, -1, f.drawtype, false);
// removes value, keeps key for next iteration
lua_pop(L, 1);
i++;
if(i==6){
lua_pop(L, 1);
break;
}
}
// Copy last value to all remaining textures
if(i >= 1){
TileDef lasttile = f.tiledef[i-1];
while(i < 6){
f.tiledef[i] = lasttile;
i++;
}
}
}
read_default_tiledefs(L, -1, f.drawtype, false, f.tiledef);
lua_pop(L, 1);
// overlay_tiles = {}
lua_getfield(L, index, "overlay_tiles");
if (lua_istable(L, -1)) {
int table = lua_gettop(L);
lua_pushnil(L);
int i = 0;
while (lua_next(L, table) != 0) {
// Read tiledef from value
f.tiledef_overlay[i] = read_tiledef(L, -1, f.drawtype, false);
// removes value, keeps key for next iteration
lua_pop(L, 1);
i++;
if (i == 6) {
lua_pop(L, 1);
break;
}
}
// Copy last value to all remaining textures
if (i >= 1) {
TileDef lasttile = f.tiledef_overlay[i - 1];
while (i < 6) {
f.tiledef_overlay[i] = lasttile;
i++;
}
}
}
read_default_tiledefs(L, -1, f.drawtype, false, f.tiledef_overlay);
lua_pop(L, 1);
// special_tiles = {}
lua_getfield(L, index, "special_tiles");
if(lua_istable(L, -1)){
int table = lua_gettop(L);
// This works as long as CF_SPECIAL_COUNT == 6.
read_default_tiledefs(L, -1, f.drawtype, true, f.tiledef_special);
lua_pop(L, 1);
// variants = {}
lua_getfield(L, index, "variants");
if (lua_istable(L, -1)) {
int variants_index = lua_gettop(L);
lua_pushnil(L);
int i = 0;
while(lua_next(L, table) != 0){
// Read tiledef from value
f.tiledef_special[i] = read_tiledef(L, -1, f.drawtype, true);
while (lua_next(L, variants_index) != 0) {
size_t v = lua_tointeger(L, -2);
if (v < f.variant_count && lua_istable(L, -1)) {
// tiles = {}
lua_getfield(L, -1, "tiles");
read_tiledefs(L, -1, f.drawtype, false, f.tiledef[v]);
lua_pop(L, 1);
// overlay_tiles = {}
lua_getfield(L, -1, "overlay_tiles");
read_tiledefs(L, -1, f.drawtype, false, f.tiledef_overlay[v]);
lua_pop(L, 1);
// special_tiles = {}
lua_getfield(L, -1, "special_tiles");
read_tiledefs(L, -1, f.drawtype, true, f.tiledef_special[v]);
lua_pop(L, 1);
}
// removes value, keeps key for next iteration
lua_pop(L, 1);
i++;
if(i==CF_SPECIAL_COUNT){
lua_pop(L, 1);
break;
}
}
}
lua_pop(L, 1);
@ -1022,9 +1059,14 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
std::string drawtype(enum_to_string(ScriptApiNode::es_DrawType, c.drawtype));
std::string liquid_type(enum_to_string(ScriptApiNode::es_LiquidType, c.liquid_type));
/* Missing "tiles" because I don't see a usecase (at least not yet). */
// Missing "tiles" because I don't see a usecase (at least not yet).
// "variants" is also missing as the only variant properties are tiles.
lua_newtable(L);
lua_pushinteger(L, c.variant_count);
lua_setfield(L, -2, "variant_count");
push_bitfield(L, c.param2_variant);
lua_setfield(L, -2, "param2_variant");
lua_pushboolean(L, c.has_on_construct);
lua_setfield(L, -2, "has_on_construct");
lua_pushboolean(L, c.has_on_destruct);
@ -1046,7 +1088,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
lua_setfield(L, -2, "mesh");
}
#if CHECK_CLIENT_BUILD()
push_ARGB8(L, c.minimap_color); // I know this is not set-able w/ register_node,
push_ARGB8(L, c.minimap_color[0]); // I know this is not set-able w/ register_node,
lua_setfield(L, -2, "minimap_color"); // but the people need to know!
#endif
lua_pushnumber(L, c.visual_scale);

View file

@ -369,6 +369,15 @@ void push_aabb3f(lua_State *L, aabb3f box, f32 divisor)
lua_rawseti(L, -2, 6);
}
void push_bitfield_parts(lua_State *L, u8 width, u8 offset)
{
lua_createtable(L, 0, 2);
lua_pushinteger(L, width);
lua_setfield(L, -2, "width");
lua_pushinteger(L, offset);
lua_setfield(L, -2, "offset");
}
std::vector<aabb3f> read_aabb3f_vector(lua_State *L, int index, f32 scale)
{
std::vector<aabb3f> boxes;
@ -431,6 +440,16 @@ size_t read_stringlist(lua_State *L, int index, std::vector<std::string> *result
return num_strings;
}
void read_bitfield_parts(lua_State *L, int index, u8 *width, u8 *offset)
{
*width = 0;
*offset = 0;
if (lua_istable(L, index)) {
getintfield(L, index, "width", *width);
getintfield(L, index, "offset", *offset);
}
}
/*
Table field getters
*/

View file

@ -16,6 +16,8 @@
#include <string_view>
#include "irrlichttypes_bloated.h"
//#include "common/c_types.h"
#include "util/numeric.h"
extern "C" {
#include <lua.h>
@ -108,6 +110,15 @@ aabb3f read_aabb3f(lua_State *L, int index, f32 scale);
std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale);
size_t read_stringlist(lua_State *L, int index,
std::vector<std::string> *result);
void read_bitfield_parts (lua_State *L, int index, u8 *width, u8 *offset);
template<typename T>
BitField<T> read_bitfield(lua_State *L, int index)
{
u8 width, offset;
read_bitfield_parts(L, index, &width, &offset);
return BitField<T>(width, offset);
}
void push_v2s16(lua_State *L, v2s16 p);
void push_v2s32(lua_State *L, v2s32 p);
@ -120,6 +131,13 @@ void push_v3f(lua_State *L, v3f p);
void push_v2f(lua_State *L, v2f p);
void push_aabb3f_vector(lua_State *L, const std::vector<aabb3f> &boxes,
f32 divisor = 1.0f);
void push_bitfield_parts (lua_State *L, u8 width, u8 offset);
template<typename T>
void push_bitfield(lua_State *L, const BitField<T> &bitfield)
{
push_bitfield_parts(L, bitfield.getWidth(), bitfield.getOffset());
}
void warn_if_field_exists(lua_State *L, int table, const char *fieldname,
std::string_view name, std::string_view message);

View file

@ -80,7 +80,7 @@ void TestGameDef::defineSomeNodes()
"{default_stone.png";
f = ContentFeatures();
f.name = itemdef.name;
for (TileDef &tiledef : f.tiledef)
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_stone.png";
f.is_ground_content = true;
idef->registerItem(itemdef);
@ -98,10 +98,10 @@ void TestGameDef::defineSomeNodes()
"{default_dirt.png&default_grass_side.png";
f = ContentFeatures();
f.name = itemdef.name;
f.tiledef[0].name = "default_grass.png";
f.tiledef[1].name = "default_dirt.png";
f.tiledef[0][0].name = "default_grass.png";
f.tiledef[0][1].name = "default_dirt.png";
for(int i = 2; i < 6; i++)
f.tiledef[i].name = "default_dirt.png^default_grass_side.png";
f.tiledef[0][i].name = "default_dirt.png^default_grass_side.png";
f.is_ground_content = true;
idef->registerItem(itemdef);
t_CONTENT_GRASS = ndef->set(f.name, f);
@ -137,7 +137,7 @@ void TestGameDef::defineSomeNodes()
f.liquid_viscosity = 4;
f.is_ground_content = true;
f.groups["liquids"] = 3;
for (TileDef &tiledef : f.tiledef)
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_water.png";
idef->registerItem(itemdef);
t_CONTENT_WATER = ndef->set(f.name, f);
@ -159,7 +159,7 @@ void TestGameDef::defineSomeNodes()
f.light_source = LIGHT_MAX-1;
f.is_ground_content = true;
f.groups["liquids"] = 3;
for (TileDef &tiledef : f.tiledef)
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_lava.png";
idef->registerItem(itemdef);
t_CONTENT_LAVA = ndef->set(f.name, f);
@ -177,7 +177,7 @@ void TestGameDef::defineSomeNodes()
"{default_brick.png";
f = ContentFeatures();
f.name = itemdef.name;
for (TileDef &tiledef : f.tiledef)
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_brick.png";
f.is_ground_content = true;
idef->registerItem(itemdef);

View file

@ -62,9 +62,9 @@ public:
f.drawtype = NDT_NORMAL;
f.solidness = 2;
f.alpha = ALPHAMODE_OPAQUE;
for (TileDef &tiledef : f.tiledef)
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = name + ".png";
for (TileSpec &tile : f.tiles)
for (TileSpec &tile : f.tiles[0])
tile.layers[0].texture_id = texture;
return registerNode(itemdef, f);
@ -89,9 +89,9 @@ public:
f.groups["liquids"] = 3;
f.liquid_alternative_source = "test:" + name + "_source";
f.liquid_alternative_flowing = "test:" + name + "_flowing";
for (TileDef &tiledef : f.tiledef)
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = name + ".png";
for (TileSpec &tile : f.tiles)
for (TileSpec &tile : f.tiles[0])
tile.layers[0].texture_id = texture;
return registerNode(itemdef, f);
@ -116,10 +116,10 @@ public:
f.groups["liquids"] = 3;
f.liquid_alternative_source = "test:" + name + "_source";
f.liquid_alternative_flowing = "test:" + name + "_flowing";
f.tiledef_special[0].name = name + "_top.png";
f.tiledef_special[1].name = name + "_side.png";
f.special_tiles[0].layers[0].texture_id = texture_top;
f.special_tiles[1].layers[0].texture_id = texture_side;
f.tiledef_special[0][0].name = name + "_top.png";
f.tiledef_special[0][1].name = name + "_side.png";
f.special_tiles[0][0].layers[0].texture_id = texture_top;
f.special_tiles[1][1].layers[0].texture_id = texture_side;
return registerNode(itemdef, f);
}

View file

@ -19,7 +19,8 @@ TEST_CASE("Given a node definition, "
{
ContentFeatures f;
f.name = "default:stone";
for (TileDef &tiledef : f.tiledef)
f.tiledef.emplace_back();
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_stone.png";
f.is_ground_content = true;

View file

@ -50,6 +50,7 @@ public:
void testSanitizeUntrusted();
void testReadSeed();
void testMyDoubleStringConversions();
void testBitField();
};
static TestUtilities g_test_instance;
@ -87,6 +88,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
TEST(testSanitizeUntrusted);
TEST(testReadSeed);
TEST(testMyDoubleStringConversions);
TEST(testBitField);
}
////////////////////////////////////////////////////////////////////////////////
@ -800,3 +802,23 @@ void TestUtilities::testMyDoubleStringConversions()
test_round_trip(0.3);
test_round_trip(0.1 + 0.2);
}
void TestUtilities::testBitField()
{
UASSERTEQ(int, BitField<u16>(5, 4).getWidth(), 5);
UASSERTEQ(int, BitField<u16>(5, 4).getOffset(), 4);
UASSERTEQ(u16, BitField<u16>(5, 4).get(0xEFDCU), 0x1DU);
UASSERTEQ(u16, BitField<u16>(5, 4).set(0xEFDCU, 0xF0FU), 0xEEFCU);
UASSERTEQ(int, BitField<u16>(17, 0).getWidth(), 16);
UASSERTEQ(int, BitField<u16>(17, 0).getOffset(), 0);
UASSERTEQ(u16, BitField<u16>(17, 0).get(0xEFDCU), 0xEFDCU);
UASSERTEQ(u16, BitField<u16>(17, 0).set(0xEFDCU, 0xFFFFU), 0xFFFFU);
UASSERTEQ(int, BitField<u16>(16, 17).getWidth(), 0);
UASSERTEQ(int, BitField<u16>(16, 17).getOffset(), 0);
UASSERTEQ(u16, BitField<u16>(16, 17).get(0xEFDCU), 0);
UASSERTEQ(u16, BitField<u16>(16, 17).set(0xEFDCU, 0), 0xEFDCU);
UASSERTEQ(int, BitField<u16>(8, 15).getWidth(), 1);
UASSERTEQ(int, BitField<u16>(8, 15).getOffset(), 15);
UASSERTEQ(u16, BitField<u16>(8, 15).get(0xEFDCU), 1);
UASSERTEQ(u16, BitField<u16>(8, 15).set(0xEFDCU, 0), 0x6FDCU);
}

View file

@ -14,6 +14,10 @@
#include <matrix4.h>
#include <cmath>
#include <algorithm>
#include <climits>
#if _MSC_VER
#include <intrin.h> // For popcnt
#endif
// Like std::clamp but allows mismatched types
template <typename T, typename T2, typename T3>
@ -277,6 +281,53 @@ inline void set_bits(u32 *x, u32 pos, u32 len, u32 val)
*x |= (val & mask) << pos;
}
inline unsigned popcount(unsigned b)
{
#if _MSC_VER
return __popcnt(b);
#else
return __builtin_popcount(b);
#endif
}
template<typename T>
class BitField {
public:
static_assert(static_cast<T>(-1) > static_cast<T>(0),
"Bit fields only work with unsigned integer types");
BitField(): m_mask(0), m_offset(0) {}
BitField(u8 width, u8 offset)
{
// Truncate to the type width to avoid undefined behavior.
unsigned bit_count = sizeof(T) * CHAR_BIT;
offset = MYMIN(offset, bit_count);
width = MYMIN(width, bit_count - offset);
m_mask = ((width < bit_count ? 1 << width : 0) - 1) << (offset % bit_count);
m_offset = offset % bit_count;
}
inline u8 getWidth() const { return popcount(m_mask); }
inline u8 getOffset() const { return m_offset; }
inline T get(T bits) const
{
return (bits & m_mask) >> m_offset;
}
inline T set(T bits, T value) const
{
bits &= ~m_mask;
bits |= (value << m_offset) & m_mask;
return bits;
}
private:
T m_mask;
u8 m_offset;
};
inline u32 calc_parity(u32 v)
{
v ^= v >> 16;