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

fix merge conflict

This commit is contained in:
Miguel 2025-06-04 14:52:10 -06:00
commit c94c466d3d
121 changed files with 2791 additions and 2948 deletions

View file

@ -132,16 +132,16 @@ jobs:
run: |
./util/test_multiplayer.sh
# Build with prometheus-cpp (server-only)
clang_11_prometheus:
name: "clang_11 (PROMETHEUS=1)"
runs-on: ubuntu-22.04
# Build with prometheus-cpp (server-only), also runs on ARM64
clang_prometheus_arm:
name: "clang (with Prometheus, ARM64)"
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-11
install_linux_deps --headless clang libluajit-5.1-dev
- name: Build prometheus-cpp
run: ./util/ci/build_prometheus_cpp.sh
@ -150,8 +150,8 @@ jobs:
run: |
./util/ci/build.sh
env:
CC: clang-11
CXX: clang++-11
CC: clang
CXX: clang++
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0"
- name: Test

View file

@ -14,7 +14,7 @@ set(CLANG_MINIMUM_VERSION "7.0.1")
# You should not need to edit these manually, use util/bump_version.sh
set(VERSION_MAJOR 5)
set(VERSION_MINOR 12)
set(VERSION_MINOR 13)
set(VERSION_PATCH 0)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")

View file

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
project.ext.set("versionMajor", 5) // Version Major
project.ext.set("versionMinor", 12) // Version Minor
project.ext.set("versionMinor", 13) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch
// ^ keep in sync with cmake

View file

@ -102,7 +102,7 @@ end
local translation_file_header = [[
// This file is automatically generated
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
// To update it, refer to the bottom of builtin/common/settings/init.lua
fake_function() {]]
@ -110,15 +110,15 @@ local function create_translation_file(settings)
local result = { translation_file_header }
for _, entry in ipairs(settings) do
if entry.type == "category" then
insert(result, sprintf("\tgettext(%q);", entry.name))
insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.name))
else
if entry.readable_name then
insert(result, sprintf("\tgettext(%q);", entry.readable_name))
insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.readable_name))
end
if entry.comment ~= "" then
local comment_escaped = entry.comment:gsub("\n", "\\n")
comment_escaped = comment_escaped:gsub("\"", "\\\"")
insert(result, "\tgettext(\"" .. comment_escaped .. "\");")
insert(result, "\t/* xgettext:no-c-format */ gettext(\"" .. comment_escaped .. "\");")
end
end
end

View file

@ -19,6 +19,7 @@ function meta:__newindex(name, value)
return
end
local info = getinfo(2, "Sl")
if info ~= nil then
local desc = ("%s:%d"):format(info.short_src, info.currentline)
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
@ -26,6 +27,7 @@ function meta:__newindex(name, value)
:format(name, desc))
warned[warn_key] = true
end
end
declared[name] = true
end
@ -35,6 +37,9 @@ function meta:__index(name)
return
end
local info = getinfo(2, "Sl")
if info == nil then
return
end
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not warned[warn_key] and info.what ~= "C" then
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")

View file

@ -118,7 +118,7 @@ function ui.update()
if (active_toplevel_ui_elements > 1) then
core.log("warning", "more than one active ui "..
"element, self most likely isn't intended")
"element, this most likely isn't intended")
end
if (active_toplevel_ui_elements == 0) then
@ -166,6 +166,10 @@ end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
core.button_handler = function(fields)
if fields["try_quit"] and not fields["key_enter"] then
core.event_handler("MenuQuit")
return
end
if fields["btn_reconnect_yes"] then
gamedata.reconnect_requested = false
gamedata.errormessage = nil

View file

@ -170,14 +170,16 @@ function contentdb.get_package_by_id(id)
end
function contentdb.calculate_package_id(type, author, name)
local id = author:lower() .. "/"
local function strip_game_suffix(type, name)
if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
id = id .. name:sub(1, #name - 5)
return name:sub(1, #name - 5)
else
id = id .. name
return name
end
return id
end
function contentdb.calculate_package_id(type, author, name)
return author:lower() .. "/" .. strip_game_suffix(type, name)
end
@ -427,7 +429,7 @@ function contentdb.set_packages_from_api(packages)
-- We currently don't support name changing
local suffix = "/" .. package.name
if alias:sub(-#suffix) == suffix then
contentdb.aliases[alias:lower()] = package.id
contentdb.aliases[strip_game_suffix(packages.type, alias:lower())] = package.id
end
end
end

View file

@ -47,22 +47,20 @@
],
"#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py",
"contributors": [
"JosiahWI",
"Erich Schubert",
"wrrrzr",
"1F616EMO",
"red-001 <red-001@outlook.ie>",
"veprogames",
"paradust7",
"AFCMS",
"siliconsniffer",
"Wuzzy",
"Zemtzov7"
"JosiahWI",
"veprogames",
"Miguel P.L",
"AFCMS"
],
"previous_contributors": [
"Ælla Chiana Moskopp (erle) <erle@dieweltistgarnichtso.net> [Logo]",
"numzero",
"red-001 <red-001@outlook.ie>",
"Giuseppe Bilotta",
"HybridDog",
"ClobberXD",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
@ -75,6 +73,7 @@
"stujones11",
"Rogier <rogier777@gmail.com>",
"Gregory Currie (gregorycu)",
"paradust7",
"JacobF",
"Jeija <jeija@mesecons.net>"
]

View file

@ -33,13 +33,13 @@ end
local function buttonhandler(this, fields)
if fields.reconfigure then
local parent = this.parent
close_dialog(this)
local maintab = ui.find_by_name("maintab")
local dlg = create_settings_dlg("controls_keyboard_and_mouse")
dlg:set_parent(maintab)
maintab:hide()
dlg:set_parent(parent)
parent:hide()
dlg:show()
return true
@ -74,7 +74,7 @@ local function create_rebind_keys_dlg()
return dlg
end
function migrate_keybindings()
function migrate_keybindings(parent)
-- Show migration dialog if the user upgraded from an earlier version
-- and this has not yet been shown before, *or* if keys settings had to be changed
if core.is_first_run then
@ -95,14 +95,14 @@ function migrate_keybindings()
end
if not has_migration then
return
return parent
end
local maintab = ui.find_by_name("maintab")
local dlg = create_rebind_keys_dlg()
dlg:set_parent(maintab)
maintab:hide()
dlg:set_parent(parent)
parent:hide()
dlg:show()
ui.update()
return dlg
end

View file

@ -11,7 +11,7 @@
local SETTING_NAME = "no_mtg_notification"
function check_reinstall_mtg()
function check_reinstall_mtg(parent)
-- used to be in minetest.conf
if core.settings:get_bool(SETTING_NAME) then
cache_settings:set_bool(SETTING_NAME, true)
@ -19,14 +19,14 @@ function check_reinstall_mtg()
end
if cache_settings:get_bool(SETTING_NAME) then
return
return parent
end
local games = core.get_games()
for _, game in ipairs(games) do
if game.id == "minetest" then
cache_settings:set_bool(SETTING_NAME, true)
return
return parent
end
end
@ -40,16 +40,16 @@ function check_reinstall_mtg()
end
if not mtg_world_found then
cache_settings:set_bool(SETTING_NAME, true)
return
return parent
end
local maintab = ui.find_by_name("maintab")
local dlg = create_reinstall_mtg_dlg()
dlg:set_parent(maintab)
maintab:hide()
dlg:set_parent(parent)
parent:hide()
dlg:show()
ui.update()
return dlg
end
local function get_formspec(dialogdata)
@ -74,22 +74,22 @@ end
local function buttonhandler(this, fields)
if fields.reinstall then
local parent = this.parent
-- Don't set "no_mtg_notification" here so that the dialog will be shown
-- again if downloading MTG fails for whatever reason.
this:delete()
local maintab = ui.find_by_name("maintab")
local dlg = create_contentdb_dlg(nil, "minetest/minetest")
dlg:set_parent(maintab)
maintab:hide()
dlg:set_parent(parent)
parent:hide()
dlg:show()
return true
end
if fields.dismiss then
cache_settings:set_bool("no_mtg_notification", true)
cache_settings:set_bool(SETTING_NAME, true)
this:delete()
return true
end

View file

@ -112,8 +112,12 @@ local function init_globals()
tv_main:show()
ui.update()
check_reinstall_mtg()
migrate_keybindings()
-- synchronous, chain parents to only show one at a time
local parent = tv_main
parent = migrate_keybindings(parent)
check_reinstall_mtg(parent)
-- asynchronous, will only be shown if we're still on "maintab"
check_new_version()
end

View file

@ -25,3 +25,4 @@ This list is largely advisory and items may be reevaluated once the time comes.
* remove built-in knockback and related functions entirely
* remove `safe` parameter from `core.serialize`, always enforce `safe = true`.
possibly error when `loadstring` calls are encountered in `core.deserialize`.
* introduce strict type checking for all instances of `v3s16` / `v3f` read from Lua

View file

@ -1,12 +1,15 @@
Luanti Lua Client Modding API Reference 5.12.0
Luanti Lua Client Modding API Reference 5.13.0
==============================================
**WARNING**: if you're looking for the `minetest` namespace (e.g. `minetest.something`),
it's now called `core` due to the renaming of Luanti (formerly Minetest).
`minetest` will keep existing as an alias, so that old code won't break.
Note that `core` has already existed since version 0.4.10, so you can use it
safely without breaking backwards compatibility.
* More information at <http://www.luanti.org/>
* Luanti Documentation: <https://docs.luanti.org/>
* Additional documentation: <https://docs.luanti.org/>
Introduction
------------

View file

@ -5,9 +5,12 @@ Luanti Lua Modding API Reference
it's now called `core` due to the renaming of Luanti (formerly Minetest).
`minetest` will keep existing as an alias, so that old code won't break.
Note that `core` has already existed since version 0.4.10, so you can use it
safely without breaking backwards compatibility.
* More information at <http://www.luanti.org/>
* Luanti Documentation: <https://docs.luanti.org/>
* (Unofficial) Minetest Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
* Additional documentation: <https://docs.luanti.org/>
* (Unofficial) Luanti Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
* Modding tools: <https://github.com/luanti-org/modtools>
Introduction
@ -312,6 +315,9 @@ due to their space savings.
Bone weights should be normalized, e.g. using ["normalize all" in Blender](https://docs.blender.org/manual/en/4.2/grease_pencil/modes/weight_paint/weights_menu.html#normalize-all).
Note that nodes using matrix transforms must not be animated.
This also extends to bone overrides, which must not be applied to them.
You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator)
to check whether a model is a valid glTF file.
@ -4581,11 +4587,13 @@ and offset the noise variation.
The final fractal value noise variation is created as follows:
```
noise = offset + scale * (octave1 +
octave2 * persistence +
octave3 * persistence ^ 2 +
octave4 * persistence ^ 3 +
...)
```
Noise Parameters
----------------
@ -4699,11 +4707,13 @@ with restraint.
The absolute value of each octave's noise variation is used when combining the
octaves. The final value noise variation is created as follows:
```
noise = offset + scale * (abs(octave1) +
abs(octave2) * persistence +
abs(octave3) * persistence ^ 2 +
abs(octave4) * persistence ^ 3 +
...)
```
### Format example
@ -4998,7 +5008,8 @@ A VoxelManip object can be created any time using either:
If the optional position parameters are present for either of these routines,
the specified region will be pre-loaded into the VoxelManip object on creation.
Otherwise, the area of map you wish to manipulate must first be loaded into the
VoxelManip object using `VoxelManip:read_from_map()`.
VoxelManip object using `VoxelManip:read_from_map()`, or an empty one created
with `VoxelManip:initialize()`.
Note that `VoxelManip:read_from_map()` returns two position vectors. The region
formed by these positions indicate the minimum and maximum (respectively)
@ -5009,14 +5020,14 @@ be queried any time after loading map data with `VoxelManip:get_emerged_area()`.
Now that the VoxelManip object is populated with map data, your mod can fetch a
copy of this data using either of two methods. `VoxelManip:get_node_at()`,
which retrieves an individual node in a MapNode formatted table at the position
requested is the simplest method to use, but also the slowest.
requested. This is the simplest method to use, but also the slowest.
Nodes in a VoxelManip object may also be read in bulk to a flat array table
using:
* `VoxelManip:get_data()` for node content (in Content ID form, see section
[Content IDs]),
* `VoxelManip:get_light_data()` for node light levels, and
* `VoxelManip:get_light_data()` for node param (usually light levels), and
* `VoxelManip:get_param2_data()` for the node type-dependent "param2" values.
See section [Flat array format] for more details.
@ -5031,17 +5042,16 @@ internal state unless otherwise explicitly stated.
Once the bulk data has been edited to your liking, the internal VoxelManip
state can be set using:
* `VoxelManip:set_data()` for node content (in Content ID form, see section
[Content IDs]),
* `VoxelManip:set_light_data()` for node light levels, and
* `VoxelManip:set_param2_data()` for the node type-dependent `param2` values.
* `VoxelManip:set_data()` or
* `VoxelManip:set_light_data()` or
* `VoxelManip:set_param2_data()`
The parameter to each of the above three functions can use any table at all in
the same flat array format as produced by `get_data()` etc. and is not required
to be a table retrieved from `get_data()`.
Once the internal VoxelManip state has been modified to your liking, the
changes can be committed back to the map by calling `VoxelManip:write_to_map()`
changes can be committed back to the map by calling `VoxelManip:write_to_map()`.
### Flat array format
@ -5173,15 +5183,22 @@ inside the VoxelManip.
Methods
-------
* `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object
* `read_from_map(p1, p2)`: Loads a part of the map into the VoxelManip object
containing the region formed by `p1` and `p2`.
* returns actual emerged `pmin`, actual emerged `pmax`
* returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned)
* Note that calling this multiple times will *add* to the area loaded in the
VoxelManip, and not reset it.
* `initialize(p1, p2, [node])`: Clears and resizes the VoxelManip object to
comprise the region formed by `p1` and `p2`.
* **No data** is read from the map, so you can use this to treat `VoxelManip`
objects as general containers of node data.
* `node`: if present the data will be filled with this node; if not it will
be uninitialized
* returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned)
* (introduced in 5.13.0)
* `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to
the map.
* **important**: data must be set using `VoxelManip:set_data()` before
calling this.
* **important**: you should call `set_data()` before this, or nothing will change.
* if `light` is true, then lighting is automatically recalculated.
The default value is true.
If `light` is false, no light calculations happen, and you should correct
@ -5242,6 +5259,15 @@ Methods
where the engine will keep the map and the VM in sync automatically.
* Note: this doesn't do what you think it does and is subject to removal. Don't use it!
* `get_emerged_area()`: Returns actual emerged minimum and maximum positions.
* "Emerged" does not imply that this region was actually loaded from the map,
if `initialize()` has been used.
* `close()`: Frees the data buffers associated with the VoxelManip object.
It will become empty.
* Since Lua's garbage collector is not aware of the potentially significant
memory behind a VoxelManip, frequent VoxelManip usage can cause the server to
run out of RAM. Therefore it's recommend to call this method once you're done
with the VoxelManip.
* (introduced in 5.13.0)
`VoxelArea`
-----------
@ -6562,13 +6588,10 @@ Environment access
* The actual seed used is the noiseparams seed plus the world seed.
* `core.get_value_noise(seeddiff, octaves, persistence, spread)`
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
* Return world-specific value noise
* `core.get_perlin(noiseparams)`
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
* Return world-specific value noise (was not Perlin noise)
* Deprecated: renamed to `core.get_value_noise` in version 5.12.0.
* `core.get_perlin(seeddiff, octaves, persistence, spread)`
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
* Return world-specific value noise (was not Perlin noise)
* Deprecated: renamed to `core.get_value_noise` in version 5.12.0.
* `core.get_voxel_manip([pos1, pos2])`
* Return voxel manipulator object.
* Loads the manipulator from the map if positions are passed.
@ -9061,78 +9084,6 @@ offering very strong randomness.
* `get_state()`: return generator state encoded in string
* `set_state(state_string)`: restore generator state from encoded string
`ValueNoise`
-------------
A value noise generator.
It can be created via `ValueNoise()` or `core.get_value_noise()`.
For legacy reasons, it can also be created via `PerlinNoise()` or `core.get_perlin()`,
but the implemented noise is not Perlin noise.
For `core.get_value_noise()`, the actual seed used is the noiseparams seed
plus the world seed, to create world-specific noise.
* `ValueNoise(noiseparams)
* `ValueNoise(seed, octaves, persistence, spread)` (Deprecated)
* `PerlinNoise(noiseparams)` (Deprecated)
* `PerlinNoise(seed, octaves, persistence, spread)` (Deprecated)
* `core.get_value_noise(noiseparams)`
* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (Deprecated)
* `core.get_perlin(noiseparams)` (Deprecated)
* `core.get_perlin(seeddiff, octaves, persistence, spread)` (Deprecated)
### Methods
* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}`
* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}`
`ValueNoiseMap`
----------------
A fast, bulk noise generator.
It can be created via `ValueNoiseMap(noiseparams, size)` or
`core.get_value_noise_map(noiseparams, size)`.
For legacy reasons, it can also be created via `PerlinNoiseMap(noiseparams, size)`
or `core.get_perlin_map(noiseparams, size)`, but it is not Perlin noise.
For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed
plus the world seed, to create world-specific noise.
Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted
for 2D noise, and it must be larger than 1 for 3D noise (otherwise
`nil` is returned).
For each of the functions with an optional `buffer` parameter: If `buffer` is
not nil, this table will be used to store the result instead of creating a new
table.
### Methods
* `get_2d_map(pos)`: returns a `<size.x>` times `<size.y>` 2D array of 2D noise
with values starting at `pos={x=,y=}`
* `get_3d_map(pos)`: returns a `<size.x>` times `<size.y>` times `<size.z>`
3D array of 3D noise with values starting at `pos={x=,y=,z=}`.
* `get_2d_map_flat(pos, buffer)`: returns a flat `<size.x * size.y>` element
array of 2D noise with values starting at `pos={x=,y=}`
* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise
* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result
is stored internally.
* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result
is stored internally.
* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array,
returns a slice of the most recently computed noise results. The result slice
begins at coordinates `slice_offset` and takes a chunk of `slice_size`.
E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer
offset y = 20:
`noisevals = noise:get_map_slice({y=20}, {y=2})`
It is important to note that `slice_offset` offset coordinates begin at 1,
and are relative to the starting position of the most recently calculated
noise.
To grab a single vertical column of noise starting at map coordinates
x = 1023, y=1000, z = 1000:
`noise:calc_3d_map({x=1000, y=1000, z=1000})`
`noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})`
`PlayerMetaRef`
---------------
@ -9184,14 +9135,17 @@ end
The map is loaded as the ray advances. If the map is modified after the
`Raycast` is created, the changes may or may not have an effect on the object.
It can be created via `Raycast(pos1, pos2, objects, liquids)` or
`core.raycast(pos1, pos2, objects, liquids)` where:
It can be created via `Raycast(pos1, pos2, objects, liquids, pointabilities)`
or `core.raycast(pos1, pos2, objects, liquids, pointabilities)` where:
* `pos1`: start of the ray
* `pos2`: end of the ray
* `objects`: if false, only nodes will be returned. Default is true.
* `objects`: if false, only nodes will be returned. Default is `true`.
* `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be
returned. Default is false.
returned. Default is `false`.
* `pointabilities`: Allows overriding the `pointable` property of
nodes and objects. Uses the same format as the `pointabilities` property
of item definitions. Default is `nil`.
### Limitations
@ -9307,6 +9261,81 @@ to restrictions of JSON.
* All methods in MetaDataRef
`ValueNoise`
-------------
A value noise generator.
It can be created via `ValueNoise()` or `core.get_value_noise()`.
For `core.get_value_noise()`, the actual seed used is the noiseparams seed
plus the world seed, to create world-specific noise.
* `ValueNoise(noiseparams)`
* `ValueNoise(seed, octaves, persistence, spread)` (deprecated)
* `core.get_value_noise(noiseparams)`
* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (deprecated)
These were previously called `PerlinNoise()` and `core.get_perlin()`, but the
implemented noise was not Perlin noise. They were renamed in 5.12.0. The old
names still exist as aliases.
### Methods
* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}`
* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}`
`ValueNoiseMap`
----------------
A fast, bulk noise generator.
It can be created via `ValueNoiseMap(noiseparams, size)` or
`core.get_value_noise_map(noiseparams, size)`.
For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed
plus the world seed, to create world-specific noise.
These were previously called `PerlinNoiseMap()` and `core.get_perlin_map()`,
but the implemented noise was not Perlin noise. They were renamed in 5.12.0.
The old names still exist as aliases.
Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted
for 2D noise, and it must be larger than 1 for 3D noise (otherwise
`nil` is returned).
For each of the functions with an optional `buffer` parameter: If `buffer` is
not nil, this table will be used to store the result instead of creating a new
table.
### Methods
* `get_2d_map(pos)`: returns a `<size.x>` times `<size.y>` 2D array of 2D noise
with values starting at `pos={x=,y=}`
* `get_3d_map(pos)`: returns a `<size.x>` times `<size.y>` times `<size.z>`
3D array of 3D noise with values starting at `pos={x=,y=,z=}`.
* `get_2d_map_flat(pos, buffer)`: returns a flat `<size.x * size.y>` element
array of 2D noise with values starting at `pos={x=,y=}`
* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise
* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result
is stored internally.
* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result
is stored internally.
* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array,
returns a slice of the most recently computed noise results. The result slice
begins at coordinates `slice_offset` and takes a chunk of `slice_size`.
E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer
offset `y = 20`:
```lua
noisevals = noise:get_map_slice({y=20}, {y=2})
```
It is important to note that `slice_offset` offset coordinates begin at 1,
and are relative to the starting position of the most recently calculated
noise.
To grab a single vertical column of noise starting at map coordinates
`x = 1023, y=1000, z = 1000`:
```lua
noise:calc_3d_map({x=1000, y=1000, z=1000})
noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})
```
@ -10172,9 +10201,12 @@ Used by `core.register_node`.
mesh = "",
-- File name of mesh when using "mesh" drawtype
-- The center of the node is the model origin.
-- For legacy reasons, models in OBJ format use a scale of 1 node = 1 unit;
-- all other model file formats use a scale of 1 node = 10 units,
-- consistent with the scale used for entities.
-- For legacy reasons, this uses a different scale depending on the mesh:
-- 1. For glTF models: 10 units = 1 node (consistent with the scale for entities).
-- 2. For obj models: 1 unit = 1 node.
-- 3. For b3d and x models: 1 unit = 1 node if static, otherwise 10 units = 1 node.
-- Using static glTF or obj models is recommended.
-- You can use the `visual_scale` multiplier to achieve the expected scale.
selection_box = {
-- see [Node boxes] for possibilities

View file

@ -1,4 +1,4 @@
Luanti Lua Mainmenu API Reference 5.12.0
Luanti Lua Mainmenu API Reference 5.13.0
========================================
Introduction
@ -23,8 +23,8 @@ Callbacks
* `core.button_handler(fields)`: called when a button is pressed.
* `fields` = `{name1 = value1, name2 = value2, ...}`
* `core.event_handler(event)`
* `event`: `"MenuQuit"`, `"KeyEnter"`, `"ExitButton"`, `"EditBoxEnter"` or
`"FullscreenChange"`
* `event`: `"MenuQuit"` (derived from `quit`) or `"FullscreenChange"`
The main menu may issue custom events, such as `"Refresh"` (server list).
* `core.on_before_close()`: called before the menu is closed, either to exit or
to join a game

View file

@ -13,3 +13,9 @@ Jordach (CC BY-SA 3.0):
Zeg9 (CC BY-SA 3.0):
testentities_lava_flan.x
testentities_lava_flan.png
"Cool Guy":
hecks (refer to irr/LICENSE):
testentities_cool_guy.x
testentities_cool_guy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

View file

@ -102,6 +102,19 @@ core.register_entity("testentities:lava_flan", {
end,
})
core.register_entity("testentities:cool_guy", {
initial_properties = {
visual = "mesh",
mesh = "testentities_cool_guy.x",
textures = {
"testentities_cool_guy.png"
},
},
on_activate = function(self)
self.object:set_animation({x = 0, y = 29}, 30, 0, true)
end,
})
-- Advanced visual tests
-- An entity for testing animated and yaw-modulated sprites

View file

@ -67,18 +67,6 @@ local function test_dynamic_media(cb, player)
end
unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true})
local function test_v3f_metatable(player)
assert(vector.check(player:get_pos()))
end
unittests.register("test_v3f_metatable", test_v3f_metatable, {player=true})
local function test_v3s16_metatable(player, pos)
local node = core.get_node(pos)
local found_pos = core.find_node_near(pos, 0, node.name, true)
assert(vector.check(found_pos))
end
unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true})
local function test_clear_meta(_, pos)
local ref = core.get_meta(pos)

View file

@ -13,8 +13,8 @@ namespace scene
//! Interface for an animated mesh.
/** There are already simple implementations of this interface available so
you don't have to implement this interface on your own if you need to:
You might want to use irr::scene::SAnimatedMesh, irr::scene::SMesh,
irr::scene::SMeshBuffer etc. */
You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc.
*/
class IAnimatedMesh : public IMesh
{
public:
@ -34,22 +34,8 @@ public:
scene node the mesh is instantiated in.*/
virtual void setAnimationSpeed(f32 fps) = 0;
//! Returns the IMesh interface for a frame.
/** \param frame: Frame number, >= 0, <= getMaxFrameNumber()
Linear interpolation is used if this is between two frames.
\return Returns the animated mesh for the given frame */
virtual IMesh *getMesh(f32 frame) = 0;
//! Returns the type of the animated mesh.
/** In most cases it is not necessary to use this method.
This is useful for making a safe downcast. For example,
if getMeshType() returns EAMT_MD2 it's safe to cast the
IAnimatedMesh to IAnimatedMeshMD2.
\returns Type of the mesh. */
E_ANIMATED_MESH_TYPE getMeshType() const override
{
return EAMT_UNKNOWN;
}
//! Returns the type of the animated mesh. Useful for safe downcasts.
E_ANIMATED_MESH_TYPE getMeshType() const = 0;
};
} // end namespace scene

View file

@ -12,35 +12,8 @@ namespace irr
{
namespace scene
{
enum E_JOINT_UPDATE_ON_RENDER
{
//! do nothing
EJUOR_NONE = 0,
//! get joints positions from the mesh (for attached nodes, etc)
EJUOR_READ,
//! control joint positions in the mesh (eg. ragdolls, or set the animation from animateJoints() )
EJUOR_CONTROL
};
class IAnimatedMeshSceneNode;
//! Callback interface for catching events of ended animations.
/** Implement this interface and use
IAnimatedMeshSceneNode::setAnimationEndCallback to be able to
be notified if an animation playback has ended.
**/
class IAnimationEndCallBack : public virtual IReferenceCounted
{
public:
//! Will be called when the animation playback has ended.
/** See IAnimatedMeshSceneNode::setAnimationEndCallback for
more information.
\param node: Node of which the animation has ended. */
virtual void OnAnimationEnd(IAnimatedMeshSceneNode *node) = 0;
};
//! Scene node capable of displaying an animated mesh.
class IAnimatedMeshSceneNode : public ISceneNode
{
@ -120,11 +93,10 @@ public:
/** When true the animations are played looped */
virtual bool getLoopMode() const = 0;
//! Sets a callback interface which will be called if an animation playback has ended.
/** Set this to 0 to disable the callback again.
Please note that this will only be called when in non looped
mode, see IAnimatedMeshSceneNode::setLoopMode(). */
virtual void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) = 0;
//! Will be called right after the joints have been animated,
//! but before the transforms have been propagated recursively to children.
virtual void setOnAnimateCallback(
const std::function<void(f32 dtime)> &cb) = 0;
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
/** In this way it is possible to change the materials a mesh
@ -139,20 +111,15 @@ public:
virtual void setMesh(IAnimatedMesh *mesh) = 0;
//! Returns the current mesh
virtual IAnimatedMesh *getMesh(void) = 0;
//! Set how the joints should be updated on render
virtual void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) = 0;
virtual IAnimatedMesh *getMesh() = 0;
//! Sets the transition time in seconds
/** Note: This needs to enable joints, and setJointmode set to
EJUOR_CONTROL. You must call animateJoints(), or the mesh will
not animate. */
/** Note: You must call animateJoints(), or the mesh will not animate. */
virtual void setTransitionTime(f32 Time) = 0;
//! animates the joints in the mesh based on the current frame.
/** Also takes in to account transitions. */
virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0;
virtual void animateJoints() = 0;
//! render mesh ignoring its transformation.
/** Culling is unaffected. */

View file

@ -11,85 +11,41 @@ namespace irr
namespace scene
{
//! Enumeration for different bone animation modes
enum E_BONE_ANIMATION_MODE
{
//! The bone is usually animated, unless it's parent is not animated
EBAM_AUTOMATIC = 0,
//! The bone is animated by the skin, if it's parent is not animated then animation will resume from this bone onward
EBAM_ANIMATED,
//! The bone is not animated by the skin
EBAM_UNANIMATED,
//! Not an animation mode, just here to count the available modes
EBAM_COUNT
};
enum E_BONE_SKINNING_SPACE
{
//! local skinning, standard
EBSS_LOCAL = 0,
//! global skinning
EBSS_GLOBAL,
EBSS_COUNT
};
//! Names for bone animation modes
const c8 *const BoneAnimationModeNames[] = {
"automatic",
"animated",
"unanimated",
0,
};
//! Interface for bones used for skeletal animation.
/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */
class IBoneSceneNode : public ISceneNode
{
public:
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) :
ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {}
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt)
:
ISceneNode(parent, mgr, id),
BoneIndex(boneIndex)
{
setName(boneName);
}
//! Get the index of the bone
virtual u32 getBoneIndex() const = 0;
//! Returns the index of the bone
u32 getBoneIndex() const
{
return BoneIndex;
}
//! Sets the animation mode of the bone.
/** \return True if successful. (Unused) */
virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0;
//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override
{
return Box;
}
//! Gets the current animation mode of the bone
virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0;
const u32 BoneIndex;
//! Get the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override = 0;
//! Returns the relative transformation of the scene node.
// virtual core::matrix4 getRelativeTransformation() const = 0;
//! The animation method.
void OnAnimate(u32 timeMs) override = 0;
// Bogus box; bone scene nodes are not rendered anyways.
static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};
//! The render method.
/** Does nothing as bones are not visible. */
void render() override {}
//! How the relative transformation of the bone is used
virtual void setSkinningSpace(E_BONE_SKINNING_SPACE space) = 0;
//! How the relative transformation of the bone is used
virtual E_BONE_SKINNING_SPACE getSkinningSpace() const = 0;
//! Updates the absolute position based on the relative and the parents position
virtual void updateAbsolutePositionOfAllChildren() = 0;
s32 positionHint;
s32 scaleHint;
s32 rotationHint;
};
} // end namespace scene

View file

@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE
//! Unknown animated mesh type.
EAMT_UNKNOWN = 0,
//! Quake 2 MD2 model file
EAMT_MD2,
//! Quake 3 MD3 model file
EAMT_MD3,
//! Maya .obj static model
EAMT_OBJ,
//! Quake 3 .bsp static Map
EAMT_BSP,
//! 3D Studio .3ds file
EAMT_3DS,
//! My3D Mesh, the file format by Zhuck Dimitry
EAMT_MY3D,
//! Pulsar LMTools .lmts file. This Irrlicht loader was written by Jonas Petersen
EAMT_LMTS,
//! Cartography Shop .csm file. This loader was created by Saurav Mohapatra.
EAMT_CSM,
//! .oct file for Paul Nette's FSRad or from Murphy McCauley's Blender .oct exporter.
/** The oct file format contains 3D geometry and lightmaps and
can be loaded directly by Irrlicht */
EAMT_OCT,
//! Halflife MDL model file
EAMT_MDL_HALFLIFE,
//! generic skinned mesh
EAMT_SKINNED,
@ -119,9 +87,7 @@ public:
virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
//! Returns the type of the meshes.
/** This is useful for making a safe downcast. For example,
if getMeshType() returns EAMT_MD2 it's safe to cast the
IMesh to IAnimatedMeshMD2.
/** This is useful for making a safe downcast.
Note: It's no longer just about animated meshes, that name has just historical reasons.
\returns Type of the mesh */
virtual E_ANIMATED_MESH_TYPE getMeshType() const

View file

@ -66,26 +66,6 @@ public:
IReferenceCounted::drop() for more information. */
virtual SMesh *createMeshCopy(IMesh *mesh) const = 0;
//! Get amount of polygons in mesh.
/** \param mesh Input mesh
\return Number of polygons in mesh. */
virtual s32 getPolyCount(IMesh *mesh) const = 0;
//! Get amount of polygons in mesh.
/** \param mesh Input mesh
\return Number of polygons in mesh. */
virtual s32 getPolyCount(IAnimatedMesh *mesh) const = 0;
//! Create a new AnimatedMesh and adds the mesh to it
/** \param mesh Input mesh
\param type The type of the animated mesh to create.
\return Newly created animated mesh with mesh as its only
content. When you don't need the animated mesh anymore, you
should call IAnimatedMesh::drop(). See
IReferenceCounted::drop() for more information. */
virtual IAnimatedMesh *createAnimatedMesh(IMesh *mesh,
scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) const = 0;
//! Apply a manipulator on the Meshbuffer
/** \param func A functor defining the mesh manipulation.
\param buffer The Meshbuffer to apply the manipulator to.

View file

@ -32,7 +32,7 @@ public:
//! Get the currently defined mesh for display.
/** \return Pointer to mesh which is displayed by this node. */
virtual IMesh *getMesh(void) = 0;
virtual IMesh *getMesh() = 0;
//! Sets if the scene node should not copy the materials of the mesh but use them directly.
/** In this way it is possible to change the materials of a mesh

View file

@ -94,16 +94,12 @@ public:
\param timeMs Current time in milliseconds. */
virtual void OnAnimate(u32 timeMs)
{
if (IsVisible) {
// update absolute position
if (!IsVisible && Children.empty())
return;
updateAbsolutePosition();
// perform the post render process on all children
ISceneNodeList::iterator it = Children.begin();
for (; it != Children.end(); ++it)
(*it)->OnAnimate(timeMs);
}
for (auto *child : Children)
child->OnAnimate(timeMs);
}
//! Renders the node.

View file

@ -172,7 +172,7 @@ public:
\return Pointer to the texture, or 0 if the texture
could not be loaded. This pointer should not be dropped. See
IReferenceCounted::drop() for more information. */
virtual ITexture *getTexture(const io::path &filename) = 0;
[[deprecated]] virtual ITexture *getTexture(const io::path &filename) = 0;
//! Get access to a named texture.
/** Loads the texture from disk if it is not
@ -184,7 +184,7 @@ public:
\return Pointer to the texture, or 0 if the texture
could not be loaded. This pointer should not be dropped. See
IReferenceCounted::drop() for more information. */
virtual ITexture *getTexture(io::IReadFile *file) = 0;
[[deprecated]] virtual ITexture *getTexture(io::IReadFile *file) = 0;
//! Returns amount of textures currently loaded
/** \return Amount of textures currently loaded */

View file

@ -1,167 +0,0 @@
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#pragma once
#include <vector>
#include "IAnimatedMesh.h"
#include "IMesh.h"
#include "aabbox3d.h"
namespace irr
{
namespace scene
{
//! Simple implementation of the IAnimatedMesh interface.
struct SAnimatedMesh final : public IAnimatedMesh
{
//! constructor
SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) :
IAnimatedMesh(), FramesPerSecond(25.f), Type(type)
{
addMesh(mesh);
recalculateBoundingBox();
}
//! destructor
virtual ~SAnimatedMesh()
{
// drop meshes
for (auto *mesh : Meshes)
mesh->drop();
}
f32 getMaxFrameNumber() const override
{
return static_cast<f32>(Meshes.size() - 1);
}
//! Gets the default animation speed of the animated mesh.
/** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
f32 getAnimationSpeed() const override
{
return FramesPerSecond;
}
//! Gets the frame count of the animated mesh.
/** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
The actual speed is set in the scene node the mesh is instantiated in.*/
void setAnimationSpeed(f32 fps) override
{
FramesPerSecond = fps;
}
//! Returns the IMesh interface for a frame.
/** \param frame: Frame number as zero based index.
\return The animated mesh based for the given frame */
IMesh *getMesh(f32 frame) override
{
if (Meshes.empty())
return nullptr;
return Meshes[static_cast<s32>(frame)];
}
//! adds a Mesh
void addMesh(IMesh *mesh)
{
if (mesh) {
mesh->grab();
Meshes.push_back(mesh);
}
}
//! Returns an axis aligned bounding box of the mesh.
/** \return A bounding box of this mesh is returned. */
const core::aabbox3d<f32> &getBoundingBox() const override
{
return Box;
}
//! set user axis aligned bounding box
void setBoundingBox(const core::aabbox3df &box) override
{
Box = box;
}
//! Recalculates the bounding box.
void recalculateBoundingBox()
{
Box.reset(0, 0, 0);
if (Meshes.empty())
return;
Box = Meshes[0]->getBoundingBox();
for (u32 i = 1; i < Meshes.size(); ++i)
Box.addInternalBox(Meshes[i]->getBoundingBox());
}
//! Returns the type of the animated mesh.
E_ANIMATED_MESH_TYPE getMeshType() const override
{
return Type;
}
//! returns amount of mesh buffers.
u32 getMeshBufferCount() const override
{
if (Meshes.empty())
return 0;
return Meshes[0]->getMeshBufferCount();
}
//! returns pointer to a mesh buffer
IMeshBuffer *getMeshBuffer(u32 nr) const override
{
if (Meshes.empty())
return 0;
return Meshes[0]->getMeshBuffer(nr);
}
//! Returns pointer to a mesh buffer which fits a material
/** \param material: material to search for
\return Returns the pointer to the mesh buffer or
NULL if there is no such mesh buffer. */
IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override
{
if (Meshes.empty())
return 0;
return Meshes[0]->getMeshBuffer(material);
}
//! set the hardware mapping hint, for driver
void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override
{
for (u32 i = 0; i < Meshes.size(); ++i)
Meshes[i]->setHardwareMappingHint(newMappingHint, buffer);
}
//! flags the meshbuffer as changed, reloads hardware buffers
void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override
{
for (u32 i = 0; i < Meshes.size(); ++i)
Meshes[i]->setDirty(buffer);
}
//! All meshes defining the animated mesh
std::vector<IMesh *> Meshes;
//! The bounding box of this mesh
core::aabbox3d<f32> Box{{0.0f, 0.0f, 0.0f}};
//! Default animation speed of this mesh.
f32 FramesPerSecond;
//! The type of the mesh.
E_ANIMATED_MESH_TYPE Type;
};
} // end namespace scene
} // end namespace irr

View file

@ -5,7 +5,7 @@
#pragma once
#include <vector>
#include "IMesh.h"
#include "IAnimatedMesh.h"
#include "IMeshBuffer.h"
#include "aabbox3d.h"
@ -14,7 +14,7 @@ namespace irr
namespace scene
{
//! Simple implementation of the IMesh interface.
struct SMesh final : public IMesh
struct SMesh final : public IAnimatedMesh
{
//! constructor
SMesh() {}
@ -134,6 +134,15 @@ struct SMesh final : public IMesh
//! The bounding box of this mesh
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
// Implement animated mesh interface as a static mesh.
// Slightly hacky: Eventually should be consolidated with SSkinnedMesh,
// with all the animation-related parts behind an optional.
virtual f32 getMaxFrameNumber() const override { return 0.0f; }
virtual f32 getAnimationSpeed() const override { return 0.0f; }
virtual void setAnimationSpeed(f32 fps) override {}
E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; }
};
} // end namespace scene

View file

@ -8,11 +8,18 @@
#include "ISceneManager.h"
#include "SMeshBuffer.h"
#include "SSkinMeshBuffer.h"
#include "aabbox3d.h"
#include "irrMath.h"
#include "irrTypes.h"
#include "matrix4.h"
#include "quaternion.h"
#include "vector3d.h"
#include "Transform.h"
#include <optional>
#include <string>
#include <variant>
#include <vector>
namespace irr
{
@ -26,12 +33,20 @@ class ISceneManager;
class SkinnedMesh : public IAnimatedMesh
{
public:
enum class SourceFormat {
B3D,
X,
GLTF,
OTHER,
};
//! constructor
SkinnedMesh() :
SkinnedMesh(SourceFormat src_format) :
EndFrame(0.f), FramesPerSecond(25.f),
LastAnimatedFrame(-1), SkinnedLastFrame(false),
HasAnimation(false), PreparedForSkinning(false),
AnimateNormals(true), HardwareSkinning(false)
AnimateNormals(true),
SrcFormat(src_format)
{
SkinningBuffers = &LocalBuffers;
}
@ -39,6 +54,10 @@ public:
//! destructor
virtual ~SkinnedMesh();
//! The source (file) format the mesh was loaded from.
//! Important for legacy reasons pertaining to different mesh loader behavior.
SourceFormat getSourceFormat() const { return SrcFormat; }
//! If the duration is 0, it is a static (=non animated) mesh.
f32 getMaxFrameNumber() const override;
@ -51,14 +70,12 @@ public:
The actual speed is set in the scene node the mesh is instantiated in.*/
void setAnimationSpeed(f32 fps) override;
//! returns the animated mesh for the given frame
IMesh *getMesh(f32) override;
//! Turns the given array of local matrices into an array of global matrices
//! by multiplying with respective parent matrices.
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const;
//! Animates joints based on frame input
void animateMesh(f32 frame);
//! Performs a software skin on this mesh based of joint positions
void skinMesh();
//! Performs a software skin on this mesh based on the given joint matrices
void skinMesh(const std::vector<core::matrix4> &animated_transforms);
//! returns amount of mesh buffers.
u32 getMeshBufferCount() const override;
@ -76,14 +93,15 @@ public:
void setTextureSlot(u32 meshbufNr, u32 textureSlot);
//! returns an axis aligned bounding box
//! Returns bounding box of the mesh *in static pose*.
const core::aabbox3d<f32> &getBoundingBox() const override {
return BoundingBox;
// TODO ideally we shouldn't be forced to implement this
return StaticPoseBox;
}
//! set user axis aligned bounding box
//! Set bounding box of the mesh *in static pose*.
void setBoundingBox(const core::aabbox3df &box) override {
BoundingBox = box;
StaticPoseBox = box;
}
//! set the hardware mapping hint, for driver
@ -127,28 +145,15 @@ public:
return !HasAnimation;
}
//! Allows to enable hardware skinning.
/* This feature is not implemented in Irrlicht yet */
bool setHardwareSkinning(bool on);
//! Refreshes vertex data cached in joints such as positions and normals
void refreshJointCache();
//! Moves the mesh into static position.
void resetAnimation();
void updateBoundingBox();
//! Recovers the joints from the mesh
void recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes);
//! Transfers the joint data to the mesh
void transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes);
//! Creates an array of joints from this mesh as children of node
void addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
IAnimatedMeshSceneNode *node,
ISceneManager *smgr);
std::vector<IBoneSceneNode *> addJoints(
IAnimatedMeshSceneNode *node, ISceneManager *smgr);
//! A vertex weight
struct SWeight
@ -223,7 +228,7 @@ public:
static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) {
core::quaternion result;
result.slerp(from, to, time, 0.001f);
result.slerp(from, to, time);
return result;
}
@ -275,15 +280,14 @@ public:
});
}
void updateTransform(f32 frame,
core::vector3df &t, core::quaternion &r, core::vector3df &s) const
void updateTransform(f32 frame, core::Transform &transform) const
{
if (auto pos = position.get(frame))
t = *pos;
transform.translation = *pos;
if (auto rot = rotation.get(frame))
r = *rot;
transform.rotation = *rot;
if (auto scl = scale.get(frame))
s = *scl;
transform.scale = *scl;
}
void cleanup() {
@ -296,16 +300,34 @@ public:
//! Joints
struct SJoint
{
SJoint() : GlobalSkinningSpace(false) {}
SJoint() {}
//! The name of this joint
std::optional<std::string> Name;
//! Local matrix of this joint
core::matrix4 LocalMatrix;
//! Local transformation to be set by loaders. Mutated by animation.
using VariantTransform = std::variant<core::Transform, core::matrix4>;
VariantTransform transform{core::Transform{}};
VariantTransform animate(f32 frame) const {
if (keys.empty())
return transform;
if (std::holds_alternative<core::matrix4>(transform)) {
// .x lets animations override matrix transforms entirely,
// which is what we implement here.
// .gltf does not allow animation of nodes using matrix transforms.
// Note that a decomposition into a TRS transform need not exist!
core::Transform trs;
keys.updateTransform(frame, trs);
return {trs};
}
auto trs = std::get<core::Transform>(transform);
keys.updateTransform(frame, trs);
return {trs};
}
//! List of child joints
std::vector<SJoint *> Children;
//! List of attached meshes
std::vector<u32> AttachedMeshes;
@ -316,42 +338,49 @@ public:
//! Skin weights
std::vector<SWeight> Weights;
//! Bounding box of all affected vertices, in local space
core::aabbox3df LocalBoundingBox{{0, 0, 0}};
//! Unnecessary for loaders, will be overwritten on finalize
core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data.
core::matrix4 GlobalAnimatedMatrix;
core::matrix4 LocalAnimatedMatrix;
//! These should be set by loaders.
core::vector3df Animatedposition;
core::vector3df Animatedscale;
core::quaternion Animatedrotation;
// The .x and .gltf formats pre-calculate this
std::optional<core::matrix4> GlobalInversedMatrix;
private:
//! Internal members used by SkinnedMesh
friend class SkinnedMesh;
bool GlobalSkinningSpace;
void setParent(SJoint *parent) {
ParentJointID = parent ? parent->JointID : std::optional<u16>{};
}
u16 JointID; // TODO refactor away: pointers -> IDs (problem: .x loader abuses SJoint)
std::optional<u16> ParentJointID;
};
//! Animates joints based on frame input
std::vector<SJoint::VariantTransform> animateMesh(f32 frame);
//! Calculates a bounding box given an animation in the form of global joint transforms.
core::aabbox3df calculateBoundingBox(
const std::vector<core::matrix4> &global_transforms);
void recalculateBaseBoundingBoxes();
const std::vector<SJoint *> &getAllJoints() const {
return AllJoints;
}
protected:
void checkForAnimation();
bool checkForAnimation() const;
void topoSortJoints();
void prepareForSkinning();
void calculateStaticBoundingBox();
void calculateJointBoundingBoxes();
void calculateBufferBoundingBoxes();
void normalizeWeights();
void buildAllLocalAnimatedMatrices();
void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0);
void calculateGlobalMatrices(SJoint *Joint, SJoint *ParentJoint);
void skinJoint(SJoint *Joint, SJoint *ParentJoint);
void calculateTangents(core::vector3df &normal,
core::vector3df &tangent, core::vector3df &binormal,
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
@ -363,31 +392,33 @@ protected:
//! Mapping from meshbuffer number to bindable texture slot
std::vector<u32> TextureSlots;
//! Joints, topologically sorted (parents come before their children).
std::vector<SJoint *> AllJoints;
std::vector<SJoint *> RootJoints;
// bool can't be used here because std::vector<bool>
// doesn't allow taking a reference to individual elements.
std::vector<std::vector<char>> Vertices_Moved;
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
//! Bounding box of just the static parts of the mesh
core::aabbox3df StaticPartsBox{{0, 0, 0}};
//! Bounding box of the mesh in static pose
core::aabbox3df StaticPoseBox{{0, 0, 0}};
f32 EndFrame;
f32 FramesPerSecond;
f32 LastAnimatedFrame;
bool SkinnedLastFrame;
bool HasAnimation;
bool PreparedForSkinning;
bool AnimateNormals;
bool HardwareSkinning;
SourceFormat SrcFormat;
};
// Interface for mesh loaders
class SkinnedMeshBuilder : public SkinnedMesh {
public:
SkinnedMeshBuilder() : SkinnedMesh() {}
SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {}
//! loaders should call this after populating the mesh
// returns *this, so do not try to drop the mesh builder instance

42
irr/include/Transform.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "irrMath.h"
#include <matrix4.h>
#include <vector3d.h>
#include <quaternion.h>
namespace irr
{
namespace core
{
struct Transform {
vector3df translation;
quaternion rotation;
vector3df scale{1};
Transform interpolate(Transform to, f32 time) const
{
core::quaternion interpolated_rotation;
interpolated_rotation.slerp(rotation, to.rotation, time);
return {
to.translation.getInterpolated(translation, time),
interpolated_rotation,
to.scale.getInterpolated(scale, time),
};
}
matrix4 buildMatrix() const
{
matrix4 T;
T.setTranslation(translation);
matrix4 R;
rotation.getMatrix_transposed(R);
matrix4 S;
S.setScale(scale);
return T * R * S;
}
};
} // end namespace core
} // end namespace irr

View file

@ -180,7 +180,7 @@ public:
linear interpolation.
*/
quaternion &slerp(quaternion q1, quaternion q2,
f32 time, f32 threshold = .05f);
f32 time, f32 threshold = .001f);
//! Set this quaternion to represent a rotation from angle and axis.
/** Axis must be unit length.

View file

@ -3,9 +3,13 @@
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CAnimatedMeshSceneNode.h"
#include "CBoneSceneNode.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "S3DVertex.h"
#include "Transform.h"
#include "irrTypes.h"
#include "matrix4.h"
#include "os.h"
#include "SkinnedMesh.h"
#include "IDummyTransformationSceneNode.h"
@ -17,6 +21,9 @@
#include "IFileSystem.h"
#include "quaternion.h"
#include <algorithm>
#include <cstddef>
#include <optional>
#include <cassert>
namespace irr
{
@ -30,13 +37,13 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
const core::vector3df &rotation,
const core::vector3df &scale) :
IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale),
Mesh(0),
Mesh(nullptr),
StartFrame(0), EndFrame(0), FramesPerSecond(0.025f),
CurrentFrameNr(0.f), LastTimeMs(0),
TransitionTime(0), Transiting(0.f), TransitingBlend(0.f),
JointMode(EJUOR_NONE), JointsUsed(false),
JointsUsed(false),
Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false),
LoopCallBack(0), PassCount(0)
PassCount(0)
{
setMesh(mesh);
}
@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
//! destructor
CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode()
{
if (LoopCallBack)
LoopCallBack->drop();
if (Mesh)
Mesh->drop();
}
@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
if (FramesPerSecond > 0.f) { // forwards...
if (CurrentFrameNr > EndFrame)
CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame);
} else // backwards...
{
} else { // backwards...
if (CurrentFrameNr < StartFrame)
CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame);
}
@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
CurrentFrameNr += timeMs * FramesPerSecond;
if (FramesPerSecond > 0.f) { // forwards...
if (CurrentFrameNr > EndFrame) {
CurrentFrameNr = EndFrame;
if (LoopCallBack)
LoopCallBack->OnAnimationEnd(this);
}
} else // backwards...
{
if (CurrentFrameNr < StartFrame) {
CurrentFrameNr = StartFrame;
if (LoopCallBack)
LoopCallBack->OnAnimationEnd(this);
}
CurrentFrameNr = std::min(CurrentFrameNr, EndFrame);
} else { // backwards...
CurrentFrameNr = std::max(CurrentFrameNr, StartFrame);
}
}
}
@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode()
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
{
if (Mesh->getMeshType() != EAMT_SKINNED) {
return Mesh->getMesh(getFrameNr());
} else {
return Mesh;
}
// As multiple scene nodes may be sharing the same skinned mesh, we have to
// re-animate it every frame to ensure that this node gets the mesh that it needs.
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
auto *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
if (JointMode == EJUOR_CONTROL) // write to mesh
skinnedMesh->transferJointsToMesh(JointChildSceneNodes);
else
skinnedMesh->animateMesh(getFrameNr());
// Update the skinned mesh for the current joint transforms.
skinnedMesh->skinMesh();
if (JointMode == EJUOR_READ) { // read from mesh
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
//---slow---
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n)
if (JointChildSceneNodes[n]->getParent() == this) {
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option
}
}
if (JointMode == EJUOR_CONTROL) {
// For meshes other than EJUOR_CONTROL, this is done by calling animateMesh()
skinnedMesh->updateBoundingBox();
}
// Matrices have already been calculated in OnAnimate
skinnedMesh->skinMesh(PerJoint.GlobalMatrices);
return skinnedMesh;
}
}
//! OnAnimate() is called just before rendering the whole scene.
@ -201,7 +176,28 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs)
buildFrameNr(timeMs - LastTimeMs);
LastTimeMs = timeMs;
// This needs to be done on animate, which is called recursively *before*
// anything is rendered so that the transformations of children are up to date
animateJoints();
// Copy old transforms *before* bone overrides have been applied.
// TODO if there are no bone overrides or no animation blending, this is unnecessary.
copyOldTransforms();
if (OnAnimateCallback)
OnAnimateCallback(timeMs / 1000.0f);
IAnimatedMeshSceneNode::OnAnimate(timeMs);
if (auto *skinnedMesh = dynamic_cast<SkinnedMesh*>(Mesh)) {
for (u16 i = 0; i < PerJoint.SceneNodes.size(); ++i)
PerJoint.GlobalMatrices[i] = PerJoint.SceneNodes[i]->getRelativeTransformation();
assert(PerJoint.GlobalMatrices.size() == skinnedMesh->getJointCount());
skinnedMesh->calculateGlobalMatrices(PerJoint.GlobalMatrices);
Box = skinnedMesh->calculateBoundingBox(PerJoint.GlobalMatrices);
} else {
Box = Mesh->getBoundingBox();
}
}
//! renders the node.
@ -218,15 +214,7 @@ void CAnimatedMeshSceneNode::render()
++PassCount;
scene::IMesh *m = getMeshForCurrentFrame();
if (m) {
Box = m->getBoundingBox();
} else {
#ifdef _DEBUG
os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING);
#endif
return;
}
assert(m);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render()
if (DebugDataVisible & scene::EDS_SKELETON) {
if (Mesh->getMeshType() == EAMT_SKINNED) {
// draw skeleton
for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) {
for (const auto *childJoint : joint->Children) {
driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(),
childJoint->GlobalAnimatedMatrix.getTranslation(),
const auto &joints = (static_cast<SkinnedMesh *>(Mesh))->getAllJoints();
for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) {
const auto translation = PerJoint.GlobalMatrices[i].getTranslation();
if (auto pjid = joints[i]->ParentJointID) {
const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation();
driver->draw3DLine(parent_translation, translation,
video::SColor(255, 51, 66, 255));
}
}
@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName)
return 0;
}
if (JointChildSceneNodes.size() <= *number) {
if (PerJoint.SceneNodes.size() <= *number) {
os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING);
return 0;
}
return JointChildSceneNodes[*number];
return PerJoint.SceneNodes[*number];
}
//! Returns a pointer to a child node, which has the same transformation as
@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID)
checkJoints();
if (JointChildSceneNodes.size() <= jointID) {
if (PerJoint.SceneNodes.size() <= jointID) {
os::Printer::log("Joint not loaded into node", ELL_WARNING);
return 0;
}
return JointChildSceneNodes[jointID];
return PerJoint.SceneNodes[jointID];
}
//! Gets joint count.
@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child)
{
if (ISceneNode::removeChild(child)) {
if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) {
if (JointChildSceneNodes[i] == child) {
JointChildSceneNodes[i] = 0; // remove link to child
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
if (PerJoint.SceneNodes[i] == child) {
PerJoint.SceneNodes[i] = 0; // remove link to child
break;
}
}
@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const
return Looping;
}
//! Sets a callback interface which will be called if an animation
//! playback has ended. Set this to 0 to disable the callback again.
void CAnimatedMeshSceneNode::setAnimationEndCallback(IAnimationEndCallBack *callback)
{
if (callback == LoopCallBack)
return;
if (LoopCallBack)
LoopCallBack->drop();
LoopCallBack = callback;
if (LoopCallBack)
LoopCallBack->grab();
}
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly)
{
@ -525,19 +498,16 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
// get materials and bounding box
Box = Mesh->getBoundingBox();
IMesh *m = Mesh->getMesh(0);
if (m) {
Materials.clear();
Materials.reallocate(m->getMeshBufferCount());
Materials.reallocate(Mesh->getMeshBufferCount());
for (u32 i = 0; i < m->getMeshBufferCount(); ++i) {
IMeshBuffer *mb = m->getMeshBuffer(i);
for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) {
IMeshBuffer *mb = Mesh->getMeshBuffer(i);
if (mb)
Materials.push_back(mb->getMaterial());
else
Materials.push_back(video::SMaterial());
}
}
// clean up joint nodes
if (JointsUsed) {
@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition()
IAnimatedMeshSceneNode::updateAbsolutePosition();
}
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
void CAnimatedMeshSceneNode::setJointMode(E_JOINT_UPDATE_ON_RENDER mode)
{
checkJoints();
JointMode = mode;
}
//! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2)
//! Sets the transition time in seconds (note: This needs to enable joints)
//! you must call animateJoints(), or the mesh will not animate
void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
{
@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
if (TransitionTime == ttime)
return;
TransitionTime = ttime;
if (ttime != 0)
setJointMode(EJUOR_CONTROL);
else
setJointMode(EJUOR_NONE);
}
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable)
RenderFromIdentity = enable;
}
//! updates the joint positions of this mesh
void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions)
void CAnimatedMeshSceneNode::addJoints()
{
if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) {
const auto &joints = static_cast<SkinnedMesh*>(Mesh)->getAllJoints();
PerJoint.setN(joints.size());
PerJoint.SceneNodes.clear();
PerJoint.SceneNodes.reserve(joints.size());
for (size_t i = 0; i < joints.size(); ++i) {
const auto *joint = joints[i];
ISceneNode *parent = this;
if (joint->ParentJointID)
parent = PerJoint.SceneNodes.at(*joint->ParentJointID); // exists because of topo. order
assert(parent);
const auto *matrix = std::get_if<core::matrix4>(&joint->transform);
PerJoint.SceneNodes.push_back(new CBoneSceneNode(
parent, SceneManager, 0, i, joint->Name,
matrix ? core::Transform{} : std::get<core::Transform>(joint->transform),
matrix ? *matrix : std::optional<core::matrix4>{}));
}
}
void CAnimatedMeshSceneNode::updateJointSceneNodes(
const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms)
{
for (size_t i = 0; i < transforms.size(); ++i) {
const auto &transform = transforms[i];
auto *node = static_cast<CBoneSceneNode*>(PerJoint.SceneNodes[i]);
if (const auto *trs = std::get_if<core::Transform>(&transform)) {
node->setTransform(*trs);
// .x lets animations override matrix transforms entirely.
node->Matrix = std::nullopt;
} else {
node->Matrix = std::get<core::matrix4>(transform);
}
}
}
//! updates the joint positions of this mesh
void CAnimatedMeshSceneNode::animateJoints()
{
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
return;
checkJoints();
const f32 frame = getFrameNr(); // old?
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
skinnedMesh->animateMesh(frame);
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
if (!skinnedMesh->isStatic())
updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr()));
//-----------------------------------------
// Transition
//-----------------------------------------
if (Transiting != 0.f) {
// Init additional matrices
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
PretransitingSave.push_back(core::matrix4());
}
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
//------Position------
JointChildSceneNodes[n]->setPosition(
core::lerp(
PretransitingSave[n].getTranslation(),
JointChildSceneNodes[n]->getPosition(),
TransitingBlend));
//------Rotation------
// Code is slow, needs to be fixed up
const core::quaternion RotationStart(PretransitingSave[n].getRotationRadians());
const core::quaternion RotationEnd(JointChildSceneNodes[n]->getRotation() * core::DEGTORAD);
core::quaternion QRotation;
QRotation.slerp(RotationStart, RotationEnd, TransitingBlend);
core::vector3df tmpVector;
QRotation.toEuler(tmpVector);
tmpVector *= core::RADTODEG; // convert from radians back to degrees
JointChildSceneNodes[n]->setRotation(tmpVector);
//------Scale------
// JointChildSceneNodes[n]->setScale(
// core::lerp(
// PretransitingSave[n].getScale(),
// JointChildSceneNodes[n]->getScale(),
// TransitingBlend));
}
}
if (CalculateAbsolutePositions) {
//---slow---
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
if (JointChildSceneNodes[n]->getParent() == this) {
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option
}
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
if (PerJoint.PreTransSaves[i]) {
PerJoint.SceneNodes[i]->setTransform(PerJoint.PreTransSaves[i]->interpolate(
PerJoint.SceneNodes[i]->getTransform(), TransitingBlend));
}
}
}
}
/*!
*/
void CAnimatedMeshSceneNode::checkJoints()
{
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
return;
if (!JointsUsed) {
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i)
removeChild(JointChildSceneNodes[i]);
JointChildSceneNodes.clear();
// Create joints for SkinnedMesh
((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager);
((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes);
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i)
removeChild(PerJoint.SceneNodes[i]);
addJoints();
JointsUsed = true;
JointMode = EJUOR_READ;
}
}
/*!
*/
void CAnimatedMeshSceneNode::copyOldTransforms()
{
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
if (!PerJoint.SceneNodes[i]->Matrix) {
PerJoint.PreTransSaves[i] = PerJoint.SceneNodes[i]->getTransform();
} else {
PerJoint.PreTransSaves[i] = std::nullopt;
}
}
}
void CAnimatedMeshSceneNode::beginTransition()
{
if (!JointsUsed)
return;
if (TransitionTime != 0) {
// Check the array is big enough
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
PretransitingSave.push_back(core::matrix4());
}
// Copy the position of joints
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n)
PretransitingSave[n] = JointChildSceneNodes[n]->getRelativeTransformation();
Transiting = core::reciprocal((f32)TransitionTime);
}
TransitingBlend = 0.f;
}
/*!
*/
ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager)
{
if (!newParent)
@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *
newNode->EndFrame = EndFrame;
newNode->FramesPerSecond = FramesPerSecond;
newNode->CurrentFrameNr = CurrentFrameNr;
newNode->JointMode = JointMode;
newNode->JointsUsed = JointsUsed;
newNode->TransitionTime = TransitionTime;
newNode->Transiting = Transiting;
newNode->TransitingBlend = TransitingBlend;
newNode->Looping = Looping;
newNode->ReadOnlyMaterials = ReadOnlyMaterials;
newNode->LoopCallBack = LoopCallBack;
if (newNode->LoopCallBack)
newNode->LoopCallBack->grab();
newNode->PassCount = PassCount;
newNode->JointChildSceneNodes = JointChildSceneNodes;
newNode->PretransitingSave = PretransitingSave;
newNode->PerJoint.SceneNodes = PerJoint.SceneNodes;
newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves;
newNode->RenderFromIdentity = RenderFromIdentity;
return newNode;

View file

@ -4,9 +4,12 @@
#pragma once
#include "CBoneSceneNode.h"
#include "IAnimatedMeshSceneNode.h"
#include "IAnimatedMesh.h"
#include "SkinnedMesh.h"
#include "Transform.h"
#include "matrix4.h"
namespace irr
@ -54,9 +57,11 @@ public:
//! returns the current loop mode
bool getLoopMode() const override;
//! Sets a callback interface which will be called if an animation
//! playback has ended. Set this to 0 to disable the callback again.
void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) override;
void setOnAnimateCallback(
const std::function<void(f32 dtime)> &cb) override
{
OnAnimateCallback = cb;
}
//! sets the speed with which the animation is played
//! NOTE: setMesh will also change this value and set it to the default speed of the mesh
@ -117,15 +122,16 @@ public:
//! updates the absolute position based on the relative and the parents position
void updateAbsolutePosition() override;
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) override;
//! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2)
//! Sets the transition time in seconds (note: This needs to enable joints)
//! you must call animateJoints(), or the mesh will not animate
void setTransitionTime(f32 Time) override;
void updateJointSceneNodes(const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms);
//! updates the joint positions of this mesh
void animateJoints(bool CalculateAbsolutePositions = true) override;
void animateJoints() override;
void addJoints();
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
void setRenderFromIdentity(bool On) override;
@ -142,6 +148,7 @@ private:
void buildFrameNr(u32 timeMs);
void checkJoints();
void copyOldTransforms();
void beginTransition();
core::array<video::SMaterial> Materials;
@ -158,19 +165,30 @@ private:
f32 Transiting; // is mesh transiting (plus cache of TransitionTime)
f32 TransitingBlend; // 0-1, calculated on buildFrameNr
// 0-unused, 1-get joints only, 2-set joints only, 3-move and set
E_JOINT_UPDATE_ON_RENDER JointMode;
bool JointsUsed;
bool Looping;
bool ReadOnlyMaterials;
bool RenderFromIdentity;
IAnimationEndCallBack *LoopCallBack;
s32 PassCount;
std::function<void(f32)> OnAnimateCallback;
std::vector<IBoneSceneNode *> JointChildSceneNodes;
core::array<core::matrix4> PretransitingSave;
struct PerJointData {
std::vector<CBoneSceneNode *> SceneNodes;
std::vector<core::matrix4> GlobalMatrices;
std::vector<std::optional<core::Transform>> PreTransSaves;
void setN(u16 n) {
SceneNodes.clear();
SceneNodes.resize(n);
GlobalMatrices.clear();
GlobalMatrices.resize(n);
PreTransSaves.clear();
PreTransSaves.resize(n);
}
};
PerJointData PerJoint;
};
} // end namespace scene

View file

@ -48,7 +48,7 @@ IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file)
return 0;
B3DFile = file;
AnimatedMesh = new scene::SkinnedMeshBuilder();
AnimatedMesh = new scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D);
ShowWarning = true; // If true a warning is issued if too many textures are used
VerticesStart = 0;
@ -143,31 +143,25 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint)
os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG);
#endif
f32 position[3], scale[3], rotation[4];
core::Transform transform;
{
f32 t[3], s[3], r[4];
readFloats(position, 3);
readFloats(scale, 3);
readFloats(rotation, 4);
readFloats(t, 3);
readFloats(s, 3);
readFloats(r, 4);
joint->Animatedposition = core::vector3df(position[0], position[1], position[2]);
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]);
joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]);
// Build LocalMatrix:
core::matrix4 positionMatrix;
positionMatrix.setTranslation(joint->Animatedposition);
core::matrix4 scaleMatrix;
scaleMatrix.setScale(joint->Animatedscale);
core::matrix4 rotationMatrix;
joint->Animatedrotation.getMatrix_transposed(rotationMatrix);
joint->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix;
joint->transform = transform = {
{t[0], t[1], t[2]},
{r[1], r[2], r[3], r[0]},
{s[0], s[1], s[2]},
};
}
if (inJoint)
joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix;
joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix();
else
joint->GlobalMatrix = joint->LocalMatrix;
joint->GlobalMatrix = transform.buildMatrix();
while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats
{

View file

@ -1,86 +0,0 @@
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CBoneSceneNode.h"
#include <optional>
namespace irr
{
namespace scene
{
//! constructor
CBoneSceneNode::CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id,
u32 boneIndex, const std::optional<std::string> &boneName) :
IBoneSceneNode(parent, mgr, id),
BoneIndex(boneIndex),
AnimationMode(EBAM_AUTOMATIC), SkinningSpace(EBSS_LOCAL)
{
setName(boneName);
}
//! Returns the index of the bone
u32 CBoneSceneNode::getBoneIndex() const
{
return BoneIndex;
}
//! Sets the animation mode of the bone. Returns true if successful.
bool CBoneSceneNode::setAnimationMode(E_BONE_ANIMATION_MODE mode)
{
AnimationMode = mode;
return true;
}
//! Gets the current animation mode of the bone
E_BONE_ANIMATION_MODE CBoneSceneNode::getAnimationMode() const
{
return AnimationMode;
}
//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32> &CBoneSceneNode::getBoundingBox() const
{
return Box;
}
/*
//! Returns the relative transformation of the scene node.
core::matrix4 CBoneSceneNode::getRelativeTransformation() const
{
return core::matrix4(); // RelativeTransformation;
}
*/
void CBoneSceneNode::OnAnimate(u32 timeMs)
{
if (IsVisible) {
// update absolute position
// updateAbsolutePosition();
// perform the post render process on all children
ISceneNodeList::iterator it = Children.begin();
for (; it != Children.end(); ++it)
(*it)->OnAnimate(timeMs);
}
}
void CBoneSceneNode::helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node)
{
Node->updateAbsolutePosition();
ISceneNodeList::const_iterator it = Node->getChildren().begin();
for (; it != Node->getChildren().end(); ++it) {
helper_updateAbsolutePositionOfAllChildren((*it));
}
}
void CBoneSceneNode::updateAbsolutePositionOfAllChildren()
{
helper_updateAbsolutePositionOfAllChildren(this);
}
} // namespace scene
} // namespace irr

View file

@ -7,6 +7,8 @@
// Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
#include "IBoneSceneNode.h"
#include "Transform.h"
#include "matrix4.h"
#include <optional>
@ -21,49 +23,48 @@ public:
//! constructor
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt);
//! Returns the index of the bone
u32 getBoneIndex() const override;
//! Sets the animation mode of the bone. Returns true if successful.
bool setAnimationMode(E_BONE_ANIMATION_MODE mode) override;
//! Gets the current animation mode of the bone
E_BONE_ANIMATION_MODE getAnimationMode() const override;
//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override;
/*
//! Returns the relative transformation of the scene node.
//core::matrix4 getRelativeTransformation() const override;
*/
void OnAnimate(u32 timeMs) override;
void updateAbsolutePositionOfAllChildren() override;
//! How the relative transformation of the bone is used
void setSkinningSpace(E_BONE_SKINNING_SPACE space) override
const std::optional<std::string> &boneName = std::nullopt,
const core::Transform &transform = {},
const std::optional<core::matrix4> &matrix = std::nullopt) :
IBoneSceneNode(parent, mgr, id, boneIndex, boneName),
Matrix(matrix)
{
SkinningSpace = space;
setTransform(transform);
}
E_BONE_SKINNING_SPACE getSkinningSpace() const override
void setTransform(const core::Transform &transform)
{
return SkinningSpace;
setPosition(transform.translation);
{
core::vector3df euler;
auto rot = transform.rotation;
// Invert to be consistent with setRotationDegrees
rot.makeInverse();
rot.toEuler(euler);
setRotation(euler * core::RADTODEG);
}
setScale(transform.scale);
}
private:
void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node);
core::Transform getTransform() const
{
return {
getPosition(),
core::quaternion(getRotation() * core::DEGTORAD).makeInverse(),
getScale()
};
}
u32 BoneIndex;
core::matrix4 getRelativeTransformation() const override
{
if (Matrix)
return *Matrix;
return IBoneSceneNode::getRelativeTransformation();
}
core::aabbox3d<f32> Box{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
E_BONE_ANIMATION_MODE AnimationMode;
E_BONE_SKINNING_SPACE SkinningSpace;
//! Some file formats alternatively let bones specify a transformation matrix.
//! If this is set, it overrides the TRS properties.
std::optional<core::matrix4> Matrix;
};
} // end namespace scene

View file

@ -347,7 +347,8 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file)
const char *filename = file->getFileName().c_str();
try {
tiniergltf::GlTF model = parseGLTF(file);
irr_ptr<SkinnedMeshBuilder> mesh(new SkinnedMeshBuilder());
irr_ptr<SkinnedMeshBuilder> mesh(new SkinnedMeshBuilder(
SkinnedMesh::SourceFormat::GLTF));
MeshExtractor extractor(std::move(model), mesh.get());
try {
extractor.load();
@ -538,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes
mat[i] = static_cast<f32>(m[i]);
mat = convertHandedness(mat);
// Decompose the matrix into translation, scale, and rotation.
joint->Animatedposition = mat.getTranslation();
auto scale = mat.getScale();
joint->Animatedscale = scale;
joint->Animatedrotation = mat.getRotationRadians(scale);
// Invert the rotation because it is applied using `getMatrix_transposed`,
// which again inverts.
joint->Animatedrotation.makeInverse();
// Note: "When a node is targeted for animation [...],
// only TRS properties MAY be present; matrix MUST NOT be present."
// Thus we MUST NOT do any decomposition, which in general need not exist.
joint->transform = mat;
return mat;
}
static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint)
{
const auto &trans = trs.translation;
const auto &rot = trs.rotation;
const auto &scale = trs.scale;
core::matrix4 transMat;
joint->Animatedposition = convertHandedness(core::vector3df(trans[0], trans[1], trans[2]));
transMat.setTranslation(joint->Animatedposition);
core::matrix4 rotMat;
joint->Animatedrotation = convertHandedness(core::quaternion(rot[0], rot[1], rot[2], rot[3]));
core::quaternion(joint->Animatedrotation).getMatrix_transposed(rotMat);
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]);
core::matrix4 scaleMat;
scaleMat.setScale(joint->Animatedscale);
return transMat * rotMat * scaleMat;
const auto &t = trs.translation;
const auto &r = trs.rotation;
const auto &s = trs.scale;
core::Transform transform{
convertHandedness(core::vector3df(t[0], t[1], t[2])),
convertHandedness(core::quaternion(r[0], r[1], r[2], r[3])),
core::vector3df(s[0], s[1], s[2]),
};
joint->transform = transform;
return transform.buildMatrix();
}
static core::matrix4 loadTransform(std::optional<std::variant<tiniergltf::Node::Matrix, tiniergltf::Node::TRS>> transform,
@ -583,8 +575,7 @@ void SelfType::MeshExtractor::loadNode(
const auto &node = m_gltf_model.nodes->at(nodeIdx);
auto *joint = m_irr_model->addJoint(parent);
const core::matrix4 transform = loadTransform(node.transform, joint);
joint->LocalMatrix = transform;
joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix;
joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform;
if (node.name.has_value()) {
joint->Name = node.name->c_str();
}
@ -641,7 +632,6 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
{
const auto &anim = m_gltf_model.animations->at(animIdx);
for (const auto &channel : anim.channels) {
const auto &sampler = anim.samplers.at(channel.sampler);
bool interpolate = ([&]() {
@ -662,6 +652,11 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
throw std::runtime_error("no animated node");
auto *joint = m_loaded_nodes.at(*channel.target.node);
if (std::holds_alternative<core::matrix4>(joint->transform)) {
warn("nodes using matrix transforms must not be animated");
continue;
}
switch (channel.target.path) {
case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: {
const auto outputAccessor = Accessor<core::vector3df>::make(m_gltf_model, sampler.output);

View file

@ -320,7 +320,6 @@ set(IRRMESHLOADER
add_library(IRRMESHOBJ OBJECT
SkinnedMesh.cpp
CBoneSceneNode.cpp
CMeshSceneNode.cpp
CAnimatedMeshSceneNode.cpp
${IRRMESHLOADER}

View file

@ -35,7 +35,7 @@ void CMeshCache::removeMesh(const IMesh *const mesh)
if (!mesh)
return;
for (u32 i = 0; i < Meshes.size(); ++i) {
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) {
if (Meshes[i].Mesh == mesh) {
Meshes[i].Mesh->drop();
Meshes.erase(i);
return;
@ -53,7 +53,7 @@ u32 CMeshCache::getMeshCount() const
s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const
{
for (u32 i = 0; i < Meshes.size(); ++i) {
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh))
if (Meshes[i].Mesh == mesh)
return (s32)i;
}
@ -93,7 +93,7 @@ const io::SNamedPath &CMeshCache::getMeshName(const IMesh *const mesh) const
return emptyNamedPath;
for (u32 i = 0; i < Meshes.size(); ++i) {
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh))
if (Meshes[i].Mesh == mesh)
return Meshes[i].NamedPath;
}
@ -115,7 +115,7 @@ bool CMeshCache::renameMesh(u32 index, const io::path &name)
bool CMeshCache::renameMesh(const IMesh *const mesh, const io::path &name)
{
for (u32 i = 0; i < Meshes.size(); ++i) {
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) {
if (Meshes[i].Mesh == mesh) {
Meshes[i].NamedPath.setPath(name);
Meshes.sort();
return true;

View file

@ -6,7 +6,6 @@
#include "SkinnedMesh.h"
#include "SMesh.h"
#include "CMeshBuffer.h"
#include "SAnimatedMesh.h"
#include "os.h"
#include <cassert>
@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const
return clone;
}
//! Returns amount of polygons in mesh.
s32 CMeshManipulator::getPolyCount(scene::IMesh *mesh) const
{
if (!mesh)
return 0;
s32 trianglecount = 0;
for (u32 g = 0; g < mesh->getMeshBufferCount(); ++g)
trianglecount += mesh->getMeshBuffer(g)->getIndexCount() / 3;
return trianglecount;
}
//! Returns amount of polygons in mesh.
s32 CMeshManipulator::getPolyCount(scene::IAnimatedMesh *mesh) const
{
if (mesh && mesh->getMaxFrameNumber() != 0)
return getPolyCount(mesh->getMesh(0));
return 0;
}
//! create a new AnimatedMesh and adds the mesh to it
IAnimatedMesh *CMeshManipulator::createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const
{
return new SAnimatedMesh(mesh, type);
}
} // end namespace scene
} // end namespace irr

View file

@ -31,15 +31,6 @@ public:
//! Clones a static IMesh into a modifiable SMesh.
SMesh *createMeshCopy(scene::IMesh *mesh) const override;
//! Returns amount of polygons in mesh.
s32 getPolyCount(scene::IMesh *mesh) const override;
//! Returns amount of polygons in mesh.
s32 getPolyCount(scene::IAnimatedMesh *mesh) const override;
//! create a new AnimatedMesh and adds the mesh to it
IAnimatedMesh *createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const override;
};
} // end namespace scene

View file

@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh
else if (node->getType() == scene::ESNT_MESH)
mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh();
else
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh()->getMesh(0);
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh();
if (!mesh)
return;
}

View file

@ -7,7 +7,6 @@
#include "IVideoDriver.h"
#include "SMesh.h"
#include "SMeshBuffer.h"
#include "SAnimatedMesh.h"
#include "IReadFile.h"
#include "fast_atof.h"
#include "coreutil.h"
@ -272,23 +271,19 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file)
}
}
// Create the Animated mesh if there's anything in the mesh
SAnimatedMesh *animMesh = 0;
if (0 != mesh->getMeshBufferCount()) {
mesh->recalculateBoundingBox();
animMesh = new SAnimatedMesh();
animMesh->Type = EAMT_OBJ;
animMesh->addMesh(mesh);
animMesh->recalculateBoundingBox();
}
// Clean up the allocate obj file contents
delete[] buf;
// more cleaning up
cleanUp();
mesh->drop();
return animMesh;
// Nothing in the mesh
if (mesh->getMeshBufferCount() == 0) {
mesh->drop();
return nullptr;
}
mesh->recalculateBoundingBox();
return mesh;
}
//! Read RGB color

View file

@ -8,7 +8,6 @@
#include "CSceneManager.h"
#include "IVideoDriver.h"
#include "IFileSystem.h"
#include "SAnimatedMesh.h"
#include "CMeshCache.h"
#include "IGUIEnvironment.h"
#include "IMaterialRenderer.h"
@ -762,7 +761,7 @@ ISceneManager *CSceneManager::createNewSceneManager(bool cloneContent)
//! Get a skinned mesh, which is not available as header-only code
SkinnedMesh *CSceneManager::createSkinnedMesh()
{
return new SkinnedMesh();
return new SkinnedMesh(SkinnedMesh::SourceFormat::OTHER);
}
// creates a scenemanager

View file

@ -4,6 +4,7 @@
#include "CXMeshFileLoader.h"
#include "SkinnedMesh.h"
#include "Transform.h"
#include "os.h"
#include "fast_atof.h"
@ -54,7 +55,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file)
u32 time = os::Timer::getRealTime();
#endif
AnimatedMesh = new SkinnedMeshBuilder();
AnimatedMesh = new SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X);
SkinnedMesh *res = nullptr;
if (load(file)) {
@ -513,6 +514,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
if (n.has_value()) {
JointID = *n;
joint = AnimatedMesh->getAllJoints()[JointID];
joint->setParent(Parent);
}
}
@ -527,8 +529,6 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
#ifdef _XREADER_DEBUG
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
#endif
if (Parent)
Parent->Children.push_back(joint);
}
// Now inside a frame.
@ -552,12 +552,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
if (!parseDataObjectFrame(joint))
return false;
} else if (objectName == "FrameTransformMatrix") {
if (!parseDataObjectTransformationMatrix(joint->LocalMatrix))
core::matrix4 matrix;
if (!parseDataObjectTransformationMatrix(matrix))
return false;
// joint->LocalAnimatedMatrix
// joint->LocalAnimatedMatrix.makeInverse();
// joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix;
joint->transform = matrix;
} else if (objectName == "Mesh") {
/*
frame.Meshes.push_back(SXMesh());

View file

@ -7,7 +7,15 @@
#include "CBoneSceneNode.h"
#include "IAnimatedMeshSceneNode.h"
#include "SSkinMeshBuffer.h"
#include "Transform.h"
#include "aabbox3d.h"
#include "irrMath.h"
#include "matrix4.h"
#include "os.h"
#include "vector3d.h"
#include <cassert>
#include <cstddef>
#include <variant>
#include <vector>
#include <cassert>
@ -48,154 +56,55 @@ void SkinnedMesh::setAnimationSpeed(f32 fps)
FramesPerSecond = fps;
}
//! returns the animated mesh based
IMesh *SkinnedMesh::getMesh(f32 frame)
{
// animate(frame,startFrameLoop, endFrameLoop);
if (frame == -1)
return this;
animateMesh(frame);
skinMesh();
return this;
}
//--------------------------------------------------------------------------
// Keyframe Animation
//--------------------------------------------------------------------------
//! Animates joints based on frame input
void SkinnedMesh::animateMesh(f32 frame)
using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
std::vector<VariantTransform> SkinnedMesh::animateMesh(f32 frame)
{
if (!HasAnimation || LastAnimatedFrame == frame)
return;
LastAnimatedFrame = frame;
SkinnedLastFrame = false;
for (auto *joint : AllJoints) {
// The joints can be animated here with no input from their
// parents, but for setAnimationMode extra checks are needed
// to their parents
joint->keys.updateTransform(frame,
joint->Animatedposition,
joint->Animatedrotation,
joint->Animatedscale);
}
// Note:
// LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for
// one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
// a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
//----------------
// Temp!
buildAllLocalAnimatedMatrices();
//-----------------
updateBoundingBox();
assert(HasAnimation);
std::vector<VariantTransform> result;
result.reserve(AllJoints.size());
for (auto *joint : AllJoints)
result.push_back(joint->animate(frame));
return result;
}
void SkinnedMesh::buildAllLocalAnimatedMatrices()
core::aabbox3df SkinnedMesh::calculateBoundingBox(
const std::vector<core::matrix4> &global_transforms)
{
for (auto *joint : AllJoints) {
// Could be faster:
if (!joint->keys.empty()) {
joint->GlobalSkinningSpace = false;
// IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility.
// Not tested so far if this was correct or wrong before quaternion fix!
// Note that using getMatrix_transposed inverts the rotation.
joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix);
// --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() ---
f32 *m1 = joint->LocalAnimatedMatrix.pointer();
core::vector3df &Pos = joint->Animatedposition;
m1[0] += Pos.X * m1[3];
m1[1] += Pos.Y * m1[3];
m1[2] += Pos.Z * m1[3];
m1[4] += Pos.X * m1[7];
m1[5] += Pos.Y * m1[7];
m1[6] += Pos.Z * m1[7];
m1[8] += Pos.X * m1[11];
m1[9] += Pos.Y * m1[11];
m1[10] += Pos.Z * m1[11];
m1[12] += Pos.X * m1[15];
m1[13] += Pos.Y * m1[15];
m1[14] += Pos.Z * m1[15];
// -----------------------------------
if (!joint->keys.scale.empty()) {
/*
core::matrix4 scaleMatrix;
scaleMatrix.setScale(joint->Animatedscale);
joint->LocalAnimatedMatrix *= scaleMatrix;
*/
// -------- joint->LocalAnimatedMatrix *= scaleMatrix -----------------
core::matrix4 &mat = joint->LocalAnimatedMatrix;
mat[0] *= joint->Animatedscale.X;
mat[1] *= joint->Animatedscale.X;
mat[2] *= joint->Animatedscale.X;
mat[3] *= joint->Animatedscale.X;
mat[4] *= joint->Animatedscale.Y;
mat[5] *= joint->Animatedscale.Y;
mat[6] *= joint->Animatedscale.Y;
mat[7] *= joint->Animatedscale.Y;
mat[8] *= joint->Animatedscale.Z;
mat[9] *= joint->Animatedscale.Z;
mat[10] *= joint->Animatedscale.Z;
mat[11] *= joint->Animatedscale.Z;
// -----------------------------------
assert(global_transforms.size() == AllJoints.size());
core::aabbox3df result = StaticPartsBox;
// skeletal animation
for (u16 i = 0; i < AllJoints.size(); ++i) {
auto box = AllJoints[i]->LocalBoundingBox;
global_transforms[i].transformBoxEx(box);
result.addInternalBox(box);
}
} else {
joint->LocalAnimatedMatrix = joint->LocalMatrix;
}
}
SkinnedLastFrame = false;
}
void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
{
if (!joint) {
for (auto *rootJoint : RootJoints)
buildAllGlobalAnimatedMatrices(rootJoint, 0);
return;
} else {
// Find global matrix...
if (!parentJoint || joint->GlobalSkinningSpace)
joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
else
joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
}
for (auto *childJoint : joint->Children)
buildAllGlobalAnimatedMatrices(childJoint, joint);
}
//--------------------------------------------------------------------------
// Software Skinning
//--------------------------------------------------------------------------
//! Preforms a software skin on this mesh based of joint positions
void SkinnedMesh::skinMesh()
{
if (!HasAnimation || SkinnedLastFrame)
return;
//----------------
// This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
buildAllGlobalAnimatedMatrices();
//-----------------
SkinnedLastFrame = true;
if (!HardwareSkinning) {
// rigid animation
for (auto *joint : AllJoints) {
for (u16 i = 0; i < AllJoints.size(); ++i) {
for (u32 j : AllJoints[i]->AttachedMeshes) {
auto box = (*SkinningBuffers)[j]->BoundingBox;
global_transforms[i].transformBoxEx(box);
result.addInternalBox(box);
}
}
return result;
}
// Software Skinning
void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
{
if (!HasAnimation)
return;
// rigid animation
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = joint->GlobalAnimatedMatrix;
Buffer->Transformation = global_matrices[i];
}
}
@ -204,27 +113,20 @@ void SkinnedMesh::skinMesh()
std::fill(buf.begin(), buf.end(), false);
// skin starting with the root joints
for (auto *rootJoint : RootJoints)
skinJoint(rootJoint, 0);
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
if (joint->Weights.empty())
continue;
for (auto *buffer : *SkinningBuffers)
buffer->setDirty(EBT_VERTEX);
}
updateBoundingBox();
}
void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
{
if (joint->Weights.size()) {
// Find this joints pull on vertices...
// Find this joints pull on vertices
// Note: It is assumed that the global inversed matrix has been calculated at this point.
core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value();
core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value();
core::vector3df thisVertexMove, thisNormalMove;
auto &buffersUsed = *SkinningBuffers;
// Skin Vertices Positions and Normals...
// Skin Vertices, Positions and Normals
for (const auto &weight : joint->Weights) {
// Pull this vertex...
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
//*(weight._Pos) += thisVertexMove * weight.strength;
}
buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
}
}
// Skin all children
for (auto *childJoint : joint->Children)
skinJoint(childJoint, joint);
for (auto *buffer : *SkinningBuffers)
buffer->setDirty(EBT_VERTEX);
}
//! Gets joint count.
@ -310,7 +209,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
if (LocalBuffers[i]->getMaterial() == material)
return LocalBuffers[i];
}
return 0;
return nullptr;
}
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
@ -337,29 +236,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
LocalBuffers[i]->setDirty(buffer);
}
//! (This feature is not implemented in irrlicht yet)
bool SkinnedMesh::setHardwareSkinning(bool on)
{
if (HardwareSkinning != on) {
if (on) {
// set mesh to static pose...
for (auto *joint : AllJoints) {
for (const auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos;
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
}
}
}
HardwareSkinning = on;
}
return HardwareSkinning;
}
void SkinnedMesh::refreshJointCache()
{
// copy cache from the mesh...
@ -384,72 +260,51 @@ void SkinnedMesh::resetAnimation()
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
}
}
SkinnedLastFrame = false;
LastAnimatedFrame = -1;
}
void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint)
//! Turns the given array of local matrices into an array of global matrices
//! by multiplying with respective parent matrices.
void SkinnedMesh::calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const
{
if (!joint && parentJoint) // bit of protection from endless loops
return;
// Go through the root bones
if (!joint) {
for (auto *rootJoint : RootJoints)
calculateGlobalMatrices(rootJoint, nullptr);
return;
// Note that the joints are topologically sorted.
for (u16 i = 0; i < AllJoints.size(); ++i) {
if (auto parent_id = AllJoints[i]->ParentJointID) {
matrices[i] = matrices[*parent_id] * matrices[i];
}
if (!parentJoint)
joint->GlobalMatrix = joint->LocalMatrix;
else
joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
joint->LocalAnimatedMatrix = joint->LocalMatrix;
joint->GlobalAnimatedMatrix = joint->GlobalMatrix;
if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated
joint->GlobalInversedMatrix = joint->GlobalMatrix;
joint->GlobalInversedMatrix->makeInverse(); // slow
}
for (auto *childJoint : joint->Children)
calculateGlobalMatrices(childJoint, joint);
SkinnedLastFrame = false;
}
void SkinnedMesh::checkForAnimation()
bool SkinnedMesh::checkForAnimation() const
{
// Check for animation...
HasAnimation = false;
for (auto *joint : AllJoints) {
if (!joint->keys.empty()) {
HasAnimation = true;
break;
return true;
}
}
// meshes with weights, are still counted as animated for ragdolls, etc
if (!HasAnimation) {
// meshes with weights are animatable
for (auto *joint : AllJoints) {
if (joint->Weights.size()) {
HasAnimation = true;
break;
}
if (!joint->Weights.empty()) {
return true;
}
}
if (HasAnimation) {
return false;
}
void SkinnedMesh::prepareForSkinning()
{
HasAnimation = checkForAnimation();
if (!HasAnimation || PreparedForSkinning)
return;
PreparedForSkinning = true;
EndFrame = 0.0f;
for (const auto *joint : AllJoints) {
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
}
}
if (HasAnimation && !PreparedForSkinning) {
PreparedForSkinning = true;
// check for bugs:
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
@ -466,14 +321,11 @@ void SkinnedMesh::checkForAnimation()
}
}
// An array used in skinning
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
Vertices_Moved[i][j] = false;
// For skinning: cache weight values for speed
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
@ -482,15 +334,118 @@ void SkinnedMesh::checkForAnimation()
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
// weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
}
}
// normalize weights
normalizeWeights();
for (auto *joint : AllJoints) {
joint->keys.cleanup();
}
}
void SkinnedMesh::calculateStaticBoundingBox()
{
std::vector<std::vector<bool>> animated(getMeshBufferCount());
for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
animated[mb] = std::vector<bool>(getMeshBuffer(mb)->getVertexCount());
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
animated[buffer_id][vertex_id] = true;
}
}
bool first = true;
for (u16 mb = 0; mb < getMeshBufferCount(); mb++) {
for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) {
if (!animated[mb][v]) {
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
if (!first) {
StaticPartsBox.addInternalPoint(pos);
} else {
StaticPartsBox.reset(pos);
first = false;
}
}
}
}
}
void SkinnedMesh::calculateJointBoundingBoxes()
{
for (auto *joint : AllJoints) {
bool first = true;
for (auto &weight : joint->Weights) {
if (weight.strength < 1e-6)
continue;
auto pos = weight.StaticPos;
joint->GlobalInversedMatrix.value().transformVect(pos);
if (!first) {
joint->LocalBoundingBox.addInternalPoint(pos);
} else {
joint->LocalBoundingBox.reset(pos);
first = false;
}
}
}
}
void SkinnedMesh::calculateBufferBoundingBoxes()
{
for (u32 j = 0; j < LocalBuffers.size(); ++j) {
// If we use skeletal animation, this will just be a bounding box of the static pose;
// if we use rigid animation, this will correctly transform the points first.
LocalBuffers[j]->recalculateBoundingBox();
}
}
void SkinnedMesh::recalculateBaseBoundingBoxes() {
calculateStaticBoundingBox();
calculateJointBoundingBoxes();
calculateBufferBoundingBoxes();
}
void SkinnedMesh::topoSortJoints()
{
size_t n = AllJoints.size();
std::vector<u16> new_to_old_id;
std::vector<std::vector<u16>> children(n);
for (u16 i = 0; i < n; ++i) {
if (auto parentId = AllJoints[i]->ParentJointID)
children[*parentId].push_back(i);
else
new_to_old_id.push_back(i);
}
// Levelorder
for (u16 i = 0; i < n; ++i) {
new_to_old_id.insert(new_to_old_id.end(),
children[new_to_old_id[i]].begin(),
children[new_to_old_id[i]].end());
}
std::vector<u16> old_to_new_id(n);
for (u16 i = 0; i < n; ++i)
old_to_new_id[new_to_old_id[i]] = i;
std::vector<SJoint *> joints(n);
for (u16 i = 0; i < n; ++i) {
joints[i] = AllJoints[new_to_old_id[i]];
joints[i]->JointID = i;
if (auto parentId = joints[i]->ParentJointID)
joints[i]->ParentJointID = old_to_new_id[*parentId];
}
AllJoints = std::move(joints);
for (u16 i = 0; i < n; ++i) {
if (auto pjid = AllJoints[i]->ParentJointID)
assert(*pjid < i);
}
SkinnedLastFrame = false;
}
//! called by loader after populating with mesh and bone data
@ -498,98 +453,44 @@ SkinnedMesh *SkinnedMeshBuilder::finalize()
{
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
// Make sure we recalc the next frame
LastAnimatedFrame = -1;
SkinnedLastFrame = false;
// calculate bounding box
for (auto *buffer : LocalBuffers) {
buffer->recalculateBoundingBox();
}
if (AllJoints.size() || RootJoints.size()) {
// populate AllJoints or RootJoints, depending on which is empty
if (RootJoints.empty()) {
for (auto *joint : AllJoints) {
bool foundParent = false;
for (const auto *parentJoint : AllJoints) {
for (const auto *childJoint : parentJoint->Children) {
if (childJoint == joint)
foundParent = true;
}
}
if (!foundParent)
RootJoints.push_back(joint);
}
} else {
AllJoints = RootJoints;
}
}
// Set array sizes...
topoSortJoints();
// Set array sizes
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
}
checkForAnimation();
prepareForSkinning();
if (HasAnimation) {
std::vector<core::matrix4> matrices;
matrices.reserve(AllJoints.size());
for (auto *joint : AllJoints) {
joint->keys.cleanup();
if (const auto *matrix = std::get_if<core::matrix4>(&joint->transform))
matrices.push_back(*matrix);
else
matrices.push_back(std::get<core::Transform>(joint->transform).buildMatrix());
}
calculateGlobalMatrices(matrices);
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
if (!joint->GlobalInversedMatrix) {
joint->GlobalInversedMatrix = matrices[i];
joint->GlobalInversedMatrix->makeInverse();
}
// Needed for animation and skinning...
calculateGlobalMatrices(0, 0);
// rigid animation for non animated meshes
for (auto *joint : AllJoints) {
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = joint->GlobalAnimatedMatrix;
Buffer->Transformation = matrices[i];
}
}
// calculate bounding box
if (LocalBuffers.empty())
BoundingBox.reset(0, 0, 0);
else {
irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
LocalBuffers[0]->Transformation.transformBoxEx(bb);
BoundingBox.reset(bb);
for (u32 j = 1; j < LocalBuffers.size(); ++j) {
bb = LocalBuffers[j]->BoundingBox;
LocalBuffers[j]->Transformation.transformBoxEx(bb);
BoundingBox.addInternalBox(bb);
}
}
recalculateBaseBoundingBoxes();
StaticPoseBox = calculateBoundingBox(matrices);
return this;
}
void SkinnedMesh::updateBoundingBox()
{
if (!SkinningBuffers)
return;
BoundingBox.reset(0, 0, 0);
for (auto *buffer : *SkinningBuffers) {
buffer->recalculateBoundingBox();
core::aabbox3df bb = buffer->BoundingBox;
buffer->Transformation.transformBoxEx(bb);
BoundingBox.addInternalBox(bb);
}
}
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
{
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
{
SJoint *joint = new SJoint;
joint->setParent(parent);
joint->JointID = AllJoints.size();
AllJoints.push_back(joint);
if (!parent) {
// Add root joints to array in finalize()
} else {
// Set parent (Be careful of the mesh loader also setting the parent)
parent->Children.push_back(joint);
}
return joint;
}
@ -684,73 +581,6 @@ void SkinnedMesh::normalizeWeights()
}
}
void SkinnedMesh::recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes)
{
for (u32 i = 0; i < AllJoints.size(); ++i) {
IBoneSceneNode *node = jointChildSceneNodes[i];
SJoint *joint = AllJoints[i];
node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
node->setScale(joint->LocalAnimatedMatrix.getScale());
node->updateAbsolutePosition();
}
}
void SkinnedMesh::transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes)
{
for (u32 i = 0; i < AllJoints.size(); ++i) {
const IBoneSceneNode *const node = jointChildSceneNodes[i];
SJoint *joint = AllJoints[i];
joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
joint->GlobalSkinningSpace = (node->getSkinningSpace() == EBSS_GLOBAL);
}
// Make sure we recalc the next frame
LastAnimatedFrame = -1;
SkinnedLastFrame = false;
}
void SkinnedMesh::addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
IAnimatedMeshSceneNode *node, ISceneManager *smgr)
{
// Create new joints
for (u32 i = 0; i < AllJoints.size(); ++i) {
jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name));
}
// Match up parents
for (u32 i = 0; i < jointChildSceneNodes.size(); ++i) {
const SJoint *const joint = AllJoints[i]; // should be fine
s32 parentID = -1;
for (u32 j = 0; (parentID == -1) && (j < AllJoints.size()); ++j) {
if (i != j) {
const SJoint *const parentTest = AllJoints[j];
for (u32 n = 0; n < parentTest->Children.size(); ++n) {
if (parentTest->Children[n] == joint) {
parentID = j;
break;
}
}
}
}
IBoneSceneNode *bone = jointChildSceneNodes[i];
if (parentID != -1)
bone->setParent(jointChildSceneNodes[parentID]);
else
bone->setParent(node);
bone->drop();
}
SkinnedLastFrame = false;
}
void SkinnedMesh::convertMeshToTangents()
{
// now calculate tangents

View file

@ -916,12 +916,7 @@ struct Node {
std::optional<std::size_t> skin;
std::optional<std::vector<double>> weights;
Node(const Json::Value &o)
: transform(Matrix {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
})
: transform(TRS{})
{
check(o.isObject());
if (o.isMember("camera")) {

View file

@ -95,366 +95,366 @@
### Keybindings
# Key for moving the player forward.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_forward = KEY_KEY_W
# keymap_forward = SYSTEM_SCANCODE_26
# Key for moving the player backward.
# Will also disable autoforward, when active.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_backward = KEY_KEY_S
# keymap_backward = SYSTEM_SCANCODE_22
# Key for moving the player left.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_left = KEY_KEY_A
# keymap_left = SYSTEM_SCANCODE_4
# Key for moving the player right.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_right = KEY_KEY_D
# keymap_right = SYSTEM_SCANCODE_7
# Key for jumping.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_jump = KEY_SPACE
# keymap_jump = SYSTEM_SCANCODE_44
# Key for sneaking.
# Also used for climbing down and descending in water if aux1_descends is disabled.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_sneak = KEY_LSHIFT
# keymap_sneak = SYSTEM_SCANCODE_225
# Key for digging, punching or using something.
# (Note: The actual meaning might vary on a per-game basis.)
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_dig = KEY_LBUTTON
# Key for placing an item/block or for using something.
# (Note: The actual meaning might vary on a per-game basis.)
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_place = KEY_RBUTTON
# Key for opening the inventory.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_inventory = KEY_KEY_I
# keymap_inventory = SYSTEM_SCANCODE_12
# Key for moving fast in fast mode.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_aux1 = KEY_KEY_E
# keymap_aux1 = SYSTEM_SCANCODE_8
# Key for opening the chat window.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_chat = KEY_KEY_T
# keymap_chat = SYSTEM_SCANCODE_23
# Key for opening the chat window to type commands.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_cmd = /
# keymap_cmd = SYSTEM_SCANCODE_56
# Key for opening the chat window to type local commands.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_cmd_local = .
# keymap_cmd_local = SYSTEM_SCANCODE_55
# Key for toggling unlimited view range.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_rangeselect =
# Key for toggling flying.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_freemove = KEY_KEY_K
# keymap_freemove = SYSTEM_SCANCODE_14
# Key for toggling pitch move mode.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_pitchmove =
# Key for toggling fast mode.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_fastmove = KEY_KEY_J
# keymap_fastmove = SYSTEM_SCANCODE_13
# Key for toggling noclip mode.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_noclip = KEY_KEY_H
# keymap_noclip = SYSTEM_SCANCODE_11
# Key for selecting the next item in the hotbar.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_hotbar_next = KEY_KEY_N
# keymap_hotbar_next = SYSTEM_SCANCODE_17
# Key for selecting the previous item in the hotbar.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_hotbar_previous = KEY_KEY_B
# keymap_hotbar_previous = SYSTEM_SCANCODE_5
# Key for muting the game.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_mute = KEY_KEY_M
# keymap_mute = SYSTEM_SCANCODE_16
# Key for increasing the volume.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_increase_volume =
# Key for decreasing the volume.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_decrease_volume =
# Key for toggling autoforward.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_autoforward =
# Key for toggling cinematic mode.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_cinematic =
# Key for toggling display of minimap.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_minimap = KEY_KEY_V
# keymap_minimap = SYSTEM_SCANCODE_25
# Key for taking screenshots.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_screenshot = KEY_F12
# keymap_screenshot = SYSTEM_SCANCODE_69
# Key for toggling fullscreen mode.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_fullscreen = KEY_F11
# keymap_fullscreen = SYSTEM_SCANCODE_68
# Key for dropping the currently selected item.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_drop = KEY_KEY_Q
# keymap_drop = SYSTEM_SCANCODE_20
# Key to use view zoom when possible.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_zoom = KEY_KEY_Z
# keymap_zoom = SYSTEM_SCANCODE_29
# Key for toggling the display of the HUD.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_hud = KEY_F1
# keymap_toggle_hud = SYSTEM_SCANCODE_58
# Key for toggling the display of chat.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_chat = KEY_F2
# keymap_toggle_chat = SYSTEM_SCANCODE_59
# Key for toggling the display of the large chat console.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_console = KEY_F10
# keymap_console = SYSTEM_SCANCODE_67
# Key for toggling the display of fog.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_fog = KEY_F3
# keymap_toggle_fog = SYSTEM_SCANCODE_60
# Key for toggling the display of debug info.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_debug = KEY_F5
# keymap_toggle_debug = SYSTEM_SCANCODE_62
# Key for toggling the display of the profiler. Used for development.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_profiler = KEY_F6
# keymap_toggle_profiler = SYSTEM_SCANCODE_63
# Key for toggling the display of mapblock boundaries.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_block_bounds =
# Key for switching between first- and third-person camera.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_camera_mode = KEY_KEY_C
# keymap_camera_mode = SYSTEM_SCANCODE_6
# Key for increasing the viewing range.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_increase_viewing_range_min = +
# keymap_increase_viewing_range_min = SYSTEM_SCANCODE_46
# Key for decreasing the viewing range.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_decrease_viewing_range_min = -
# keymap_decrease_viewing_range_min = SYSTEM_SCANCODE_45
# Key for selecting the first hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot1 = KEY_KEY_1
# keymap_slot1 = SYSTEM_SCANCODE_30
# Key for selecting the second hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot2 = KEY_KEY_2
# keymap_slot2 = SYSTEM_SCANCODE_31
# Key for selecting the third hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot3 = KEY_KEY_3
# keymap_slot3 = SYSTEM_SCANCODE_32
# Key for selecting the fourth hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot4 = KEY_KEY_4
# keymap_slot4 = SYSTEM_SCANCODE_33
# Key for selecting the fifth hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot5 = KEY_KEY_5
# keymap_slot5 = SYSTEM_SCANCODE_34
# Key for selecting the sixth hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot6 = KEY_KEY_6
# keymap_slot6 = SYSTEM_SCANCODE_35
# Key for selecting the seventh hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot7 = KEY_KEY_7
# keymap_slot7 = SYSTEM_SCANCODE_36
# Key for selecting the eighth hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot8 = KEY_KEY_8
# keymap_slot8 = SYSTEM_SCANCODE_37
# Key for selecting the ninth hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot9 = KEY_KEY_9
# keymap_slot9 = SYSTEM_SCANCODE_38
# Key for selecting the tenth hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot10 = KEY_KEY_0
# keymap_slot10 = SYSTEM_SCANCODE_39
# Key for selecting the 11th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot11 =
# Key for selecting the 12th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot12 =
# Key for selecting the 13th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot13 =
# Key for selecting the 14th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot14 =
# Key for selecting the 15th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot15 =
# Key for selecting the 16th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot16 =
# Key for selecting the 17th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot17 =
# Key for selecting the 18th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot18 =
# Key for selecting the 19th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot19 =
# Key for selecting the 20th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot20 =
# Key for selecting the 21st hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot21 =
# Key for selecting the 22nd hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot22 =
# Key for selecting the 23rd hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot23 =
# Key for selecting the 24th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot24 =
# Key for selecting the 25th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot25 =
# Key for selecting the 26th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot26 =
# Key for selecting the 27th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot27 =
# Key for selecting the 28th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot28 =
# Key for selecting the 29th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot29 =
# Key for selecting the 30th hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot30 =
# Key for selecting the 31st hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot31 =
# Key for selecting the 32nd hotbar slot.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_slot32 =
@ -925,10 +925,6 @@
# type: bool
# enable_translucent_foliage = false
# Apply specular shading to nodes.
# type: bool
# enable_node_specular = false
# When enabled, liquid reflections are simulated.
# type: bool
# enable_water_reflections = false
@ -3554,27 +3550,27 @@
### Client Debugging
# Key for toggling the camera update. Only usable with 'debug' privilege.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_toggle_update_camera =
# Key for switching to the previous entry in Quicktune.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_quicktune_prev =
# Key for switching to the next entry in Quicktune.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_quicktune_next =
# Key for decrementing the selected value in Quicktune.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_quicktune_dec =
# Key for incrementing the selected value in Quicktune.
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
# See https://docs.luanti.org/for-players/controls/
# type: key
# keymap_quicktune_inc =

View file

@ -56,5 +56,3 @@ script: |
# Is a backup icon location in case
mkdir -p AppDir/usr/share/luanti/misc
cp AppDir/usr/share/icons/hicolor/128x128/apps/luanti.png AppDir/usr/share/luanti/misc/luanti-xorg-icon-128.png
# Validation issues
sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/org.luanti.luanti.desktop

View file

@ -7,7 +7,7 @@ Comment[fr]=Plate-forme de jeu multijoueurs à base de blocs
Exec=luanti
Icon=luanti
Terminal=false
PrefersNonDefaultGPU=true
# Note: don't add PrefersNonDefaultGPU here, see #16095
Type=Application
Categories=Game;Simulation;
StartupNotify=false

View file

@ -174,6 +174,6 @@
<update_contact>celeron55@gmail.com</update_contact>
<releases>
<release date="2025-02-14" version="5.11.0"/>
<release date="2025-05-23" version="5.12.0"/>
</releases>
</component>

View file

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: German (Minetest)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2025-04-25 19:50+0000\n"
"Last-Translator: Wuzzy <Wuzzy@disroot.org>\n"
"PO-Revision-Date: 2025-05-16 10:31+0000\n"
"Last-Translator: sfan5 <sfan5@live.de>\n"
"Language-Team: German <https://hosted.weblate.org/projects/minetest/minetest/"
"de/>\n"
"Language: de\n"
@ -937,29 +937,27 @@ msgstr "Die Welt „$1“ löschen?"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "As a result, your keybindings may have been changed."
msgstr ""
msgstr "Aufgrund dessen könnte sich Ihre Tastenbelegung geändert haben."
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:"
msgstr ""
msgstr "Schauen Sie sich die Einstellungen oder die Dokumentation an:"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close"
msgstr ""
msgstr "Schließen"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed"
msgstr "Tastenbelegung"
msgstr "Geänderte Tastenbelegung"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings"
msgstr "Einstellungen"
msgstr "Einstellungen öffnen"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0."
msgstr ""
msgstr "Das Steuerungssystem wurde in Luanti 5.12.0 überarbeitet."
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
msgid "Confirm Password"
@ -4660,7 +4658,7 @@ msgid ""
"Instrument global callback functions on registration.\n"
"(anything you pass to a core.register_*() function)"
msgstr ""
"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren\n"
"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren.\n"
"(alles, was man einer Funktion wie core.register_*() übergibt)."
#: src/settings_translation_file.cpp

View file

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: minetest\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2022-04-29 20:12+0000\n"
"Last-Translator: JonAnder Oier <jonanderetaoier@gmail.com>\n"
"PO-Revision-Date: 2025-05-21 10:48+0000\n"
"Last-Translator: Josu Igoa <josuigoa@ni.eus>\n"
"Language-Team: Basque <https://hosted.weblate.org/projects/minetest/minetest/"
"eu/>\n"
"Language: eu\n"
@ -12,7 +12,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.12.1\n"
"X-Generator: Weblate 5.12-dev\n"
#: builtin/client/chatcommands.lua
msgid "Clear the out chat queue"
@ -23,9 +23,8 @@ msgid "Empty command."
msgstr "Agindu hutsa."
#: builtin/client/chatcommands.lua
#, fuzzy
msgid "Exit to main menu"
msgstr "Itzuli menu nagusira"
msgstr "Irten menu nagusira"
#: builtin/client/chatcommands.lua
msgid "Invalid command: "
@ -64,9 +63,8 @@ msgid "Command not available: "
msgstr "Komandoa ez dago eskuragarri: "
#: builtin/common/chatcommands.lua
#, fuzzy
msgid "Get help for commands (-t: output in chat)"
msgstr "Eskuratu laguntza komandoetarako"
msgstr "Eskuratu laguntza komandoetarako (-t: irteera txat-ean)"
#: builtin/common/chatcommands.lua
msgid ""
@ -76,9 +74,8 @@ msgstr ""
"zerrendatzeko."
#: builtin/common/chatcommands.lua
#, fuzzy
msgid "[all | <cmd>] [-t]"
msgstr "[guztia | <cmd>]"
msgstr "[all | <cmd>] [-t]"
#: builtin/common/settings/components.lua
msgid "Browse"

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: French (Minetest)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2025-04-28 18:58+0000\n"
"PO-Revision-Date: 2025-05-18 17:31+0000\n"
"Last-Translator: waxtatect <piero@live.ie>\n"
"Language-Team: French <https://hosted.weblate.org/projects/minetest/minetest/"
"fr/>\n"
@ -938,29 +938,27 @@ msgstr "Supprimer le monde « $1 » ?"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "As a result, your keybindings may have been changed."
msgstr ""
msgstr "Ainsi, il est possible que certaines touches aient été modifiées."
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:"
msgstr ""
msgstr "Vérifier les paramètres des touches ou consulter la documentation :"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close"
msgstr ""
msgstr "Fermer"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed"
msgstr "Raccourcis clavier"
msgstr "Modification des raccourcis clavier"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings"
msgstr "Paramètres"
msgstr "Ouvrir les paramètres"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0."
msgstr ""
msgstr "Le système d'affectation des touches a été remanié dans Luanti 5.12.0."
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
msgid "Confirm Password"

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: Indonesian (Minetest)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2025-04-25 10:52+0000\n"
"PO-Revision-Date: 2025-05-15 23:01+0000\n"
"Last-Translator: Linerly <linerly@proton.me>\n"
"Language-Team: Indonesian <https://hosted.weblate.org/projects/minetest/"
"minetest/id/>\n"
@ -12,7 +12,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.11.1-dev\n"
"X-Generator: Weblate 5.12-dev\n"
#: builtin/client/chatcommands.lua
msgid "Clear the out chat queue"
@ -930,29 +930,27 @@ msgstr "Hapus dunia \"$1\"?"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "As a result, your keybindings may have been changed."
msgstr ""
msgstr "Oleh sebab itu, pengikatan tombol kamu dapat berubah."
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:"
msgstr ""
msgstr "Periksa pengaturan tombol atau rujuk pada dokumentasi:"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close"
msgstr ""
msgstr "Tutup"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed"
msgstr "Pengikatan tombol"
msgstr "Pengikatan tombol berubah"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings"
msgstr "Pengaturan"
msgstr "Buka pengaturan"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0."
msgstr ""
msgstr "Sistem penanganan masukan telah dikerjakan ulang dalam Luanti 5.12.0."
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
msgid "Confirm Password"

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: Portuguese (Minetest)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2025-05-12 16:41+0000\n"
"Last-Translator: Felipe Amaral <contato.feamaral@gmail.com>\n"
"PO-Revision-Date: 2025-05-18 15:01+0000\n"
"Last-Translator: Ian Pedras <ian@pedras.org>\n"
"Language-Team: Portuguese <https://hosted.weblate.org/projects/minetest/"
"minetest/pt/>\n"
"Language: pt\n"
@ -934,30 +934,29 @@ msgid "Delete World \"$1\"?"
msgstr "Eliminar mundo \"$1\"?"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "As a result, your keybindings may have been changed."
msgstr ""
msgstr "Como resultado, os seus comandos foram mudados."
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:"
msgstr ""
msgstr "Veja as configurações ou refera a decumentação:"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close"
msgstr ""
msgstr "Fechar"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed"
msgstr "Combinações de teclas."
msgstr "Combinações de teclas mudadas"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings"
msgstr "Definições"
msgstr "Abrir Definições"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0."
msgstr ""
msgstr "O sistema de combinações de teclas foi remodelado no Luanti 5.12.0."
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
msgid "Confirm Password"
@ -2058,7 +2057,7 @@ msgstr "Scroll Lock"
#. ~ Key name
#: src/client/keycode.cpp
msgid "Select"
msgstr "Seleccionar"
msgstr "Selecionar"
#: src/client/keycode.cpp
msgid "Shift Key"
@ -2115,14 +2114,14 @@ msgid "Minimap in texture mode"
msgstr "Minimapa em modo de textura"
#: src/client/shader.cpp
#, fuzzy, c-format
#, c-format
msgid "Failed to compile the \"%s\" shader."
msgstr "Falha ao abrir página da web"
msgstr "Falha ao compilar o \"%s\" shader."
#: src/client/shader.cpp
#, fuzzy
msgid "GLSL is not supported by the driver"
msgstr "Som do sistema não é suportado nesta versão"
msgstr "GLSL não é suportado pelo seu sistema"
#. ~ Error when a mod is missing dependencies. Ex: "Mod Title is missing: mod1, mod2, mod3"
#: src/content/mod_configuration.cpp
@ -2168,11 +2167,11 @@ msgstr "Continuar"
#: src/gui/guiOpenURL.cpp
msgid "Open"
msgstr ""
msgstr "Abrir"
#: src/gui/guiOpenURL.cpp
msgid "Open URL?"
msgstr ""
msgstr "Abrir URL?"
#: src/gui/guiOpenURL.cpp
#, fuzzy
@ -2207,33 +2206,34 @@ msgstr "Volume do som: %d%%"
#: src/gui/touchscreeneditor.cpp
#, fuzzy
msgid "Add button"
msgstr "Roda do Rato"
msgstr "Adicionar botão"
#: src/gui/touchscreeneditor.cpp
#, fuzzy
msgid "Done"
msgstr "Feito!"
msgstr "Feito"
#: src/gui/touchscreeneditor.cpp
#, fuzzy
msgid "Remove"
msgstr "Servidor remoto"
msgstr "Remover"
#: src/gui/touchscreeneditor.cpp
#, fuzzy
msgid "Reset"
msgstr ""
msgstr "Refazer"
#: src/gui/touchscreeneditor.cpp
#, fuzzy
msgid "Start dragging a button to add. Tap outside to cancel."
msgstr ""
msgstr "Começe a arrastar um botão para adicionar. Clique fora para cancelar."
#: src/gui/touchscreeneditor.cpp
msgid "Tap a button to select it. Drag a button to move it."
msgstr ""
msgstr "Clique num botão para selecioná-lo. Arraste o botão para movê-lo."
#: src/gui/touchscreeneditor.cpp
msgid "Tap outside to deselect."
msgstr ""
msgstr "Click fora para desselecionar."
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
msgid "Aux1"
@ -2245,7 +2245,7 @@ msgstr "Mudar camera"
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
msgid "Dig/punch/use"
msgstr ""
msgstr "Escavar/Attacar/Usar"
#: src/gui/touchscreenlayout.cpp
msgid "Drop"
@ -2370,27 +2370,32 @@ msgstr ""
#: src/network/clientpackethandler.cpp
msgid "The server is running in singleplayer mode. You cannot connect."
msgstr ""
msgstr "Este servidor está a correr no modo uni jogador. Não se pode connectar."
#: src/network/clientpackethandler.cpp
msgid "Too many users"
msgstr ""
msgstr "Demasiados utilizadores"
#: src/network/clientpackethandler.cpp
msgid "Unknown disconnect reason."
msgstr ""
msgstr "Razão de desconecto desconhecido."
#: src/network/clientpackethandler.cpp
#, fuzzy
msgid ""
"Your client sent something the server didn't expect. Try reconnecting or "
"updating your client."
msgstr ""
"O seu cliente mandou algo que o servidor não esperava. Tente reconectar ou "
"atualizar o seu cliente."
#: src/network/clientpackethandler.cpp
msgid ""
"Your client's version is not supported.\n"
"Please contact the server administrator."
msgstr ""
"A versão do seu cliente não é supportado.\n"
"Por favor contacte o administrador do servidor."
#: src/server.cpp
#, c-format
@ -2465,7 +2470,7 @@ msgstr "Ruído 2D que localiza os vales e canais dos rios."
#: src/settings_translation_file.cpp
msgid "3D"
msgstr ""
msgstr "3D"
#: src/settings_translation_file.cpp
msgid "3D clouds"
@ -2638,13 +2643,13 @@ msgid ""
msgstr ""
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Allow clouds to look 3D instead of flat."
msgstr "Usar nuvens 3D em vez de planas."
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Allows liquids to be translucent."
msgstr ""
msgstr "Permitir liquidos semi transparentes."
#: src/settings_translation_file.cpp
msgid ""
@ -2697,8 +2702,9 @@ msgid "Anticheat flags"
msgstr ""
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Anticheat movement tolerance"
msgstr ""
msgstr "Tolerância de movimentos anti-batota"
#: src/settings_translation_file.cpp
msgid "Append item name"
@ -2724,8 +2730,9 @@ msgid ""
msgstr ""
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Apply specular shading to nodes."
msgstr ""
msgstr "Aplicar shading especular aos nodes."
#: src/settings_translation_file.cpp
msgid "Arm inertia"
@ -2824,9 +2831,8 @@ msgid "Base terrain height."
msgstr "Altura base do terreno."
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Base texture size"
msgstr "Tamanho mínimo da textura"
msgstr "Tamanho base da textura"
#: src/settings_translation_file.cpp
msgid "Basic privileges"
@ -2849,9 +2855,8 @@ msgid "Bind address"
msgstr "Endereço de bind"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Biome API"
msgstr "Biomas"
msgstr "API de Biomas"
#: src/settings_translation_file.cpp
msgid "Biome noise"
@ -3016,13 +3021,12 @@ msgid "Client"
msgstr "Cliente"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Client Debugging"
msgstr "Debugging"
msgstr "Debugging de cliente"
#: src/settings_translation_file.cpp
msgid "Client Mesh Chunksize"
msgstr ""
msgstr "Tamhãnho do Client Mesh Chunksize"
#: src/settings_translation_file.cpp
msgid "Client and Server"
@ -3063,7 +3067,7 @@ msgstr "Nuvens no menu"
#: src/settings_translation_file.cpp
msgid "Color depth for post-processing texture"
msgstr ""
msgstr "Profundidade da cor para a textura post-processing"
#: src/settings_translation_file.cpp
msgid "Colored fog"

View file

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: Russian (Minetest)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2025-05-03 05:01+0000\n"
"Last-Translator: Nana_M <sab.pyrope@gmail.com>\n"
"PO-Revision-Date: 2025-05-16 16:16+0000\n"
"Last-Translator: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/minetest/"
"minetest/ru/>\n"
"Language: ru\n"
@ -934,29 +934,27 @@ msgstr "Удалить мир «$1»?"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "As a result, your keybindings may have been changed."
msgstr ""
msgstr "В результате ваши привязки клавиш, возможно, были изменены."
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:"
msgstr ""
msgstr "Ознакомьтесь с настройками клавиш или обратитесь к документации:"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close"
msgstr ""
msgstr "Закрыть"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed"
msgstr "Привязки клавиш"
msgstr "Изменены привязки клавиш"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings"
msgstr "Настройки"
msgstr "Открыть настройки"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0."
msgstr ""
msgstr "Система обработки входных данных была переработана в Luanti 5.12.0."
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
msgid "Confirm Password"
@ -2250,7 +2248,7 @@ msgstr "Бросить"
#: src/gui/touchscreenlayout.cpp
msgid "Exit"
msgstr "Закрыть"
msgstr "Выйти"
#: src/gui/touchscreenlayout.cpp
msgid "Inventory"

View file

@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: Chinese (Simplified) (Minetest)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
"PO-Revision-Date: 2025-02-24 16:36+0000\n"
"Last-Translator: BX Zhang <zbx1425@outlook.com>\n"
"PO-Revision-Date: 2025-05-15 23:02+0000\n"
"Last-Translator: y5nw <y5nw@users.noreply.hosted.weblate.org>\n"
"Language-Team: Chinese (Simplified Han script) <https://hosted.weblate.org/"
"projects/minetest/minetest/zh_Hans/>\n"
"Language: zh_CN\n"
@ -12,7 +12,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.10.1-dev\n"
"X-Generator: Weblate 5.12-dev\n"
#: builtin/client/chatcommands.lua
msgid "Clear the out chat queue"
@ -82,16 +82,15 @@ msgstr "浏览"
#: builtin/common/settings/components.lua
msgid "Conflicts with \"$1\""
msgstr ""
msgstr "与“$1”冲突"
#: builtin/common/settings/components.lua
msgid "Edit"
msgstr "编辑"
#: builtin/common/settings/components.lua
#, fuzzy
msgid "Remove keybinding"
msgstr "按键绑定"
msgstr "移除按键绑定"
#: builtin/common/settings/components.lua
msgid "Select directory"
@ -253,7 +252,7 @@ msgstr "通用"
#: builtin/common/settings/dlg_settings.lua
msgid "Long tap"
msgstr ""
msgstr "长按"
#: builtin/common/settings/dlg_settings.lua
msgid "Movement"
@ -558,12 +557,11 @@ msgstr "捐赠"
#: builtin/mainmenu/content/dlg_package.lua
msgid "Error loading package information"
msgstr ""
msgstr "无法获取软件包信息"
#: builtin/mainmenu/content/dlg_package.lua
#, fuzzy
msgid "Error loading reviews"
msgstr "创建客户端出错:%s"
msgstr "无法加载评价"
#: builtin/mainmenu/content/dlg_package.lua
msgid "Forum Topic"
@ -583,7 +581,7 @@ msgstr "问题跟踪器"
#: builtin/mainmenu/content/dlg_package.lua
msgid "Reviews"
msgstr ""
msgstr "评价"
#: builtin/mainmenu/content/dlg_package.lua
msgid "Source"
@ -638,10 +636,11 @@ msgid "Unable to install a $1 as a texture pack"
msgstr "无法将$1安装为材质包"
#: builtin/mainmenu/dlg_clients_list.lua
#, fuzzy
msgid ""
"Players connected to\n"
"$1"
msgstr ""
msgstr "连接到$1的玩家"
#: builtin/mainmenu/dlg_config_world.lua
msgid "(Enabled, has error)"
@ -924,25 +923,23 @@ msgstr "删除世界“$1”"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "As a result, your keybindings may have been changed."
msgstr ""
msgstr "您的一些按键绑定有可能已被更改。"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:"
msgstr ""
msgstr "请查看按键绑定设置或参考文档:"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close"
msgstr ""
msgstr "关闭"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed"
msgstr "按键绑定"
msgstr "按键绑定已被更改"
#: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings"
msgstr "设置"
msgstr "打开设置"
#: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0."
@ -1292,12 +1289,11 @@ msgid "Ping"
msgstr "ping值"
#: builtin/mainmenu/tab_online.lua
#, fuzzy
msgid ""
"Players:\n"
"$1"
msgstr ""
"客户端:\n"
"玩家::\n"
"$1"
#: builtin/mainmenu/tab_online.lua
@ -2247,7 +2243,7 @@ msgstr "溢出菜单"
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
#, fuzzy
msgid "Place/use"
msgstr "放置"
msgstr "放置或使用"
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
msgid "Range select"
@ -2262,9 +2258,8 @@ msgid "Toggle chat log"
msgstr "启用/禁用聊天记录"
#: src/gui/touchscreenlayout.cpp
#, fuzzy
msgid "Toggle debug"
msgstr "启用/禁用雾"
msgstr "显示/隐藏调试信息"
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
msgid "Toggle fast"
@ -2301,9 +2296,8 @@ msgid "Internal server error"
msgstr "内部服务器错误"
#: src/network/clientpackethandler.cpp
#, fuzzy
msgid "Invalid password"
msgstr "密码"
msgstr "密码错误"
#. ~ DO NOT TRANSLATE THIS LITERALLY!
#. This is a special string which needs to contain the translation's
@ -2977,9 +2971,8 @@ msgid "Client"
msgstr "客户端"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Client Debugging"
msgstr "调试"
msgstr "客户端调试"
#: src/settings_translation_file.cpp
msgid "Client Mesh Chunksize"
@ -3233,7 +3226,6 @@ msgid "Decrease view range"
msgstr "减少可视范围"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Decrease volume"
msgstr "减小音量"
@ -3446,9 +3438,8 @@ msgid ""
msgstr ""
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Drop item"
msgstr "丢弃物品"
msgstr "丢弃物品"
#: src/settings_translation_file.cpp
msgid "Dump the mapgen debug information."
@ -4133,164 +4124,132 @@ msgstr ""
"单位为方块每二次方秒。"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 1"
msgstr "快捷栏1键"
msgstr "快捷栏第1项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 10"
msgstr "快捷栏10键"
msgstr "快捷栏第10项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 11"
msgstr "快捷栏11键"
msgstr "快捷栏第11项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 12"
msgstr "快捷栏12键"
msgstr "快捷栏第12项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 13"
msgstr "快捷栏13键"
msgstr "快捷栏第13项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 14"
msgstr "快捷栏14键"
msgstr "快捷栏第14项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 15"
msgstr "快捷栏15键"
msgstr "快捷栏第15项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 16"
msgstr "快捷栏16键"
msgstr "快捷栏第16项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 17"
msgstr "快捷栏17键"
msgstr "快捷栏第17项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 18"
msgstr "快捷栏18键"
msgstr "快捷栏第18项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 19"
msgstr "快捷栏19键"
msgstr "快捷栏第19项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 2"
msgstr "快捷栏2键"
msgstr "快捷栏第2项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 20"
msgstr "快捷栏20键"
msgstr "快捷栏第20项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 21"
msgstr "快捷栏21键"
msgstr "快捷栏第21项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 22"
msgstr "快捷栏22键"
msgstr "快捷栏第22项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 23"
msgstr "快捷栏23键"
msgstr "快捷栏第23项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 24"
msgstr "快捷栏24键"
msgstr "快捷栏第24项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 25"
msgstr "快捷栏25键"
msgstr "快捷栏第25项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 26"
msgstr "快捷栏26键"
msgstr "快捷栏第26项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 27"
msgstr "快捷栏27键"
msgstr "快捷栏第27项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 28"
msgstr "快捷栏28键"
msgstr "快捷栏第28项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 29"
msgstr "快捷栏29键"
msgstr "快捷栏第29项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 3"
msgstr "快捷栏3键"
msgstr "快捷栏第3项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 30"
msgstr "快捷栏30键"
msgstr "快捷栏第30项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 31"
msgstr "快捷栏31键"
msgstr "快捷栏第31项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 32"
msgstr "快捷栏32键"
msgstr "快捷栏第32项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 4"
msgstr "快捷栏4键"
msgstr "快捷栏第4项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 5"
msgstr "快捷栏5键"
msgstr "快捷栏第5项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 6"
msgstr "快捷栏6键"
msgstr "快捷栏第6项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 7"
msgstr "快捷栏7键"
msgstr "快捷栏第7项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 8"
msgstr "快捷栏8键"
msgstr "快捷栏第8项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar slot 9"
msgstr "快捷栏9键"
msgstr "快捷栏第9项"
#: src/settings_translation_file.cpp
msgid "Hotbar: Enable mouse wheel for selection"
@ -4301,14 +4260,12 @@ msgid "Hotbar: Invert mouse wheel direction"
msgstr "快捷栏:反转鼠标滚轮方向"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar: select next item"
msgstr "快捷栏下一个键"
msgstr "选择快捷栏下一项"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Hotbar: select previous item"
msgstr "快捷栏上一个键"
msgstr "选择快捷栏上一项"
#: src/settings_translation_file.cpp
msgid "How deep to make rivers."
@ -4513,7 +4470,6 @@ msgid "Increase view range"
msgstr "增加可视范围"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Increase volume"
msgstr "增大音量"
@ -5031,9 +4987,8 @@ msgid "Key to use view zoom when possible."
msgstr ""
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Keybindings"
msgstr "按键绑定"
msgstr "按键绑定"
#: src/settings_translation_file.cpp
msgid "Keyboard and Mouse"
@ -5072,9 +5027,8 @@ msgid "Large cave proportion flooded"
msgstr "大型洞穴淹没比"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Large chat console"
msgstr "大型聊天控制台"
msgstr "大型聊天控制台"
#: src/settings_translation_file.cpp
msgid "Leaves style"
@ -5714,23 +5668,20 @@ msgid "Mouse sensitivity multiplier."
msgstr "鼠标灵敏度倍数。"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Move backward"
msgstr "后"
msgstr "后退"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Move forward"
msgstr "自动向前"
msgstr "前"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Move left"
msgstr "移动"
msgstr "往左移动"
#: src/settings_translation_file.cpp
msgid "Move right"
msgstr ""
msgstr "往右移动"
#: src/settings_translation_file.cpp
#, fuzzy
@ -5879,14 +5830,12 @@ msgid ""
msgstr "默认字体后阴影的透明度alpha取值范围0255。"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Open chat"
msgstr "打开"
msgstr "打开聊天室"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Open inventory"
msgstr "物品栏"
msgstr "打开物品栏"
#: src/settings_translation_file.cpp
msgid ""
@ -7174,14 +7123,12 @@ msgid "Toggle Sneak key"
msgstr "启用/禁用拍照模式键"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Toggle automatic forward"
msgstr "自动前进"
msgstr "启用/禁用自动前进"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Toggle block bounds"
msgstr "地图块边界"
msgstr "显示/隐藏地图块边界"
#: src/settings_translation_file.cpp
#, fuzzy
@ -7199,18 +7146,16 @@ msgid "Toggle cinematic mode"
msgstr "切换电影模式"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Toggle debug info"
msgstr "启用/禁用雾"
msgstr "显示/隐藏调试信息"
#: src/settings_translation_file.cpp
msgid "Toggle fog"
msgstr "启用/禁用雾"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Toggle fullscreen"
msgstr "启用/禁用飞行模式"
msgstr "切换全屏模式"
#: src/settings_translation_file.cpp
msgid "Toggle pitchmove"
@ -7238,9 +7183,8 @@ msgid "Touchscreen"
msgstr "触摸屏"
#: src/settings_translation_file.cpp
#, fuzzy
msgid "Touchscreen controls"
msgstr "触屏阈值"
msgstr "触屏控制"
#: src/settings_translation_file.cpp
msgid "Touchscreen sensitivity"

View file

@ -148,7 +148,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
Menu-game loop
*/
bool retval = true;
bool *kill = porting::signal_handler_killstatus();
volatile auto *kill = porting::signal_handler_killstatus();
while (m_rendering_engine->run() && !*kill &&
!g_gamecallback->shutdown_requested) {
@ -326,6 +326,18 @@ void ClientLauncher::setting_changed_callback(const std::string &name, void *dat
static_cast<ClientLauncher*>(data)->config_guienv();
}
static video::ITexture *loadTexture(video::IVideoDriver *driver, const char *path)
{
// FIXME?: it would be cleaner to do this through a ITextureSource, but we don't have one
video::ITexture *texture = nullptr;
verbosestream << "Loading texture " << path << std::endl;
if (auto *image = driver->createImageFromFile(path); image) {
texture = driver->addTexture(fs::GetFilenameFromPath(path), image);
image->drop();
}
return texture;
}
void ClientLauncher::config_guienv()
{
gui::IGUISkin *skin = guienv->getSkin();
@ -364,10 +376,9 @@ void ClientLauncher::config_guienv()
if (cached_id != sprite_ids.end()) {
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, cached_id->second);
} else {
gui::IGUISpriteBank *sprites = skin->getSpriteBank();
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
video::ITexture *texture = driver->getTexture(path.c_str());
s32 id = sprites->addTextureAsSprite(texture);
auto *driver = m_rendering_engine->get_video_driver();
auto *texture = loadTexture(driver, path.c_str());
s32 id = skin->getSpriteBank()->addTextureAsSprite(texture);
if (id != -1) {
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, id);
sprite_ids.emplace(path, id);
@ -529,7 +540,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
void ClientLauncher::main_menu(MainMenuData *menudata)
{
bool *kill = porting::signal_handler_killstatus();
volatile auto *kill = porting::signal_handler_killstatus();
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
auto *device = m_rendering_engine->get_raw_device();

View file

@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
matrix.setTextureScale(txs, tys);
}
// Evaluate transform chain recursively; irrlicht does not do this for us
static void updatePositionRecursive(scene::ISceneNode *node)
{
scene::ISceneNode *parent = node->getParent();
if (parent)
updatePositionRecursive(parent);
node->updateAbsolutePosition();
}
static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
{
thread_local std::vector<u64> logged;
@ -682,7 +673,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
m_animated_meshnode->grab();
mesh->drop(); // The scene node took hold of it
m_animated_meshnode->animateJoints(); // Needed for some animations
m_animated_meshnode->setScale(m_prop.visual_size);
// set vertex colors to ensure alpha is set
@ -693,6 +683,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
m_animated_meshnode->forEachMaterial([this] (auto &mat) {
mat.BackfaceCulling = m_prop.backface_culling;
});
m_animated_meshnode->setOnAnimateCallback([&](f32 dtime) {
for (auto &it : m_bone_override) {
auto* bone = m_animated_meshnode->getJointNode(it.first.c_str());
if (!bone)
continue;
BoneOverride &props = it.second;
props.dtime_passed += dtime;
bone->setPosition(props.getPosition(bone->getPosition()));
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
bone->setScale(props.getScale(bone->getScale()));
}
});
} else
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
break;
@ -783,7 +788,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
updateMarker();
updateNodePos();
updateAnimation();
updateBones(.0f);
updateAttachments();
setNodeLight(m_last_light);
updateMeshCulling();
@ -1174,18 +1178,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
rot_translator.val_current = m_rotation;
updateNodePos();
}
if (m_animated_meshnode) {
// Everything must be updated; the whole transform
// chain as well as the animated mesh node.
// Otherwise, bone attachments would be relative to
// a position that's one frame old.
if (m_matrixnode)
updatePositionRecursive(m_matrixnode);
m_animated_meshnode->updateAbsolutePosition();
m_animated_meshnode->animateJoints();
updateBones(dtime);
}
}
static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count)
@ -1394,44 +1386,6 @@ void GenericCAO::updateAnimationSpeed()
m_animated_meshnode->setAnimationSpeed(m_animation_speed);
}
void GenericCAO::updateBones(f32 dtime)
{
if (!m_animated_meshnode)
return;
if (m_bone_override.empty()) {
m_animated_meshnode->setJointMode(scene::EJUOR_NONE);
return;
}
m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
for (auto &it : m_bone_override) {
std::string bone_name = it.first;
scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
if (!bone)
continue;
BoneOverride &props = it.second;
props.dtime_passed += dtime;
bone->setPosition(props.getPosition(bone->getPosition()));
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
bone->setScale(props.getScale(bone->getScale()));
}
// The following is needed for set_bone_pos to propagate to
// attached objects correctly.
// Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL.
for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
auto bone = m_animated_meshnode->getJointNode(i);
// Look for the root bone.
if (bone && bone->getParent() == m_animated_meshnode) {
// Update entire skeleton.
bone->updateAbsolutePositionOfAllChildren();
break;
}
}
}
void GenericCAO::updateAttachments()
{
ClientActiveObject *parent = getParent();
@ -1747,7 +1701,6 @@ void GenericCAO::processMessage(const std::string &data)
} else {
m_bone_override[bone] = props;
}
// updateBones(); now called every step
} else if (cmd == AO_CMD_ATTACH_TO) {
u16 parent_id = readS16(is);
std::string bone = deSerializeString16(is);

View file

@ -286,8 +286,6 @@ public:
void updateAnimationSpeed();
void updateBones(f32 dtime);
void processMessage(const std::string &data) override;
bool directReportPunch(v3f dir, const ItemStack *punchitem,

View file

@ -137,11 +137,20 @@ void MapblockMeshGenerator::drawQuad(const TileSpec &tile, v3f *coords, const v3
}
static std::array<video::S3DVertex, 24> setupCuboidVertices(const aabb3f &box,
const f32 *txc, const TileSpec *tiles, int tilecount)
const f32 *txc, const TileSpec *tiles, int tilecount, v3s16 alignment)
{
v3f min = box.MinEdge;
v3f max = box.MaxEdge;
// Texture coords are [0,1] if not specified otherwise
f32 uniform_txc[24];
if (!txc) {
for (int i = 0; i != 24; ++i) {
uniform_txc[i] = (i % 4 < 2) ? 0.0f : 1.0f;
}
txc = uniform_txc;
}
std::array<video::S3DVertex, 24> vertices = {{
// top
video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, {}, txc[0], txc[1]),
@ -185,15 +194,47 @@ static std::array<video::S3DVertex, 24> setupCuboidVertices(const aabb3f &box,
case TileRotation::None:
break;
case TileRotation::R90:
tcoords.set(-tcoords.Y, tcoords.X);
tcoords.set(1 - tcoords.Y, tcoords.X);
break;
case TileRotation::R180:
tcoords.set(-tcoords.X, -tcoords.Y);
tcoords.set(1 - tcoords.X, 1 - tcoords.Y);
break;
case TileRotation::R270:
tcoords.set(tcoords.Y, -tcoords.X);
tcoords.set(tcoords.Y, 1 - tcoords.X);
break;
}
if (tile.world_aligned) {
// Maps uv dimension of every face to world dimension xyz
constexpr int coord_dim[12] = {
0, 2, // up
0, 2, // down
2, 1, // right
2, 1, // left
0, 1, // back
0, 1, // front
};
auto scale = tile.layers[0].scale;
f32 scale_factor = 1.0f / scale;
float x = alignment[coord_dim[face*2]] % scale;
float y = alignment[coord_dim[face*2 + 1]] % scale;
// Faces grow in different directions
if (face != 1) {
y = tcoords.Y + ((scale-1)-y);
} else {
y = tcoords.Y + y;
}
if (face == 3 || face == 4) {
x = tcoords.X + ((scale-1)-x);
} else {
x = tcoords.X + x;
}
tcoords.set(x * scale_factor, y * scale_factor);
}
}
}
@ -212,6 +253,7 @@ enum class QuadDiagonal {
// for the opposite corners of each face - therefore, there
// should be (2+2)*6=24 values in the list. The order of
// the faces in the list is up-down-right-left-back-front
// if nullptr use standard [0,1] coords
// (compatible with ContentFeatures).
// mask - a bit mask that suppresses drawing of tiles.
// tile i will not be drawn if mask & (1 << i) is 1
@ -224,7 +266,7 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
{
assert(tilecount >= 1 && tilecount <= 6); // pre-condition
auto vertices = setupCuboidVertices(box, txc, tiles, tilecount);
auto vertices = setupCuboidVertices(box, txc, tiles, tilecount, cur_node.p);
for (int k = 0; k < 6; ++k) {
if (mask & (1 << k))
@ -301,12 +343,13 @@ video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
{
f32 tx1 = (box.MinEdge.X / BS) + 0.5;
f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
// Generate texture coords which are aligned to coords of a solid nodes
f32 tx1 = (box.MinEdge.X / BS) + 0.5f;
f32 ty1 = (box.MinEdge.Y / BS) + 0.5f;
f32 tz1 = (box.MinEdge.Z / BS) + 0.5f;
f32 tx2 = (box.MaxEdge.X / BS) + 0.5f;
f32 ty2 = (box.MaxEdge.Y / BS) + 0.5f;
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5f;
f32 txc[24] = {
tx1, 1 - tz2, tx2, 1 - tz1, // up
tx1, tz1, tx2, tz2, // down
@ -334,7 +377,6 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box,
const TileSpec *tiles, int tile_count, const f32 *txc, u8 mask)
{
bool scale = std::fabs(cur_node.f->visual_scale - 1.0f) > 1e-3f;
f32 texture_coord_buf[24];
f32 dx1 = box.MinEdge.X;
f32 dy1 = box.MinEdge.Y;
f32 dz1 = box.MinEdge.Z;
@ -342,19 +384,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box,
f32 dy2 = box.MaxEdge.Y;
f32 dz2 = box.MaxEdge.Z;
if (scale) {
if (!txc) { // generate texture coords before scaling
generateCuboidTextureCoords(box, texture_coord_buf);
txc = texture_coord_buf;
}
box.MinEdge *= cur_node.f->visual_scale;
box.MaxEdge *= cur_node.f->visual_scale;
}
box.MinEdge += cur_node.origin;
box.MaxEdge += cur_node.origin;
if (!txc) {
generateCuboidTextureCoords(box, texture_coord_buf);
txc = texture_coord_buf;
}
if (data->m_smooth_lighting) {
LightInfo lights[8];
for (int j = 0; j < 8; ++j) {
@ -442,10 +476,8 @@ void MapblockMeshGenerator::drawSolidNode()
u8 mask = faces ^ 0b0011'1111; // k-th bit is set if k-th face is to be *omitted*, as expected by cuboid drawing functions.
cur_node.origin = intToFloat(cur_node.p, BS);
auto box = aabb3f(v3f(-0.5 * BS), v3f(0.5 * BS));
f32 texture_coord_buf[24];
box.MinEdge += cur_node.origin;
box.MaxEdge += cur_node.origin;
generateCuboidTextureCoords(box, texture_coord_buf);
if (data->m_smooth_lighting) {
LightPair lights[6][4];
for (int face = 0; face < 6; ++face) {
@ -458,7 +490,7 @@ void MapblockMeshGenerator::drawSolidNode()
}
}
drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) {
drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) {
auto final_lights = lights[face];
for (int j = 0; j < 4; j++) {
video::S3DVertex &vertex = vertices[j];
@ -471,7 +503,7 @@ void MapblockMeshGenerator::drawSolidNode()
return QuadDiagonal::Diag02;
});
} else {
drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) {
drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) {
video::SColor color = encode_light(lights[face], cur_node.f->light_source);
if (!cur_node.f->light_source)
applyFacesShading(color, vertices[0].Normal);
@ -952,7 +984,10 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
if (edge_invisible)
continue;
drawAutoLightedCuboid(frame_edges[edge], tiles[1]);
f32 txc[24];
generateCuboidTextureCoords(frame_edges[edge], txc);
drawAutoLightedCuboid(frame_edges[edge], tiles[1], txc);
}
for (int face = 0; face < 6; face++) {
@ -996,16 +1031,17 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
TileSpec tile;
getSpecialTile(0, &tile);
drawAutoLightedCuboid(
aabb3f(
aabb3f box(
-(nb[5] ? g : b),
-(nb[4] ? g : b),
-(nb[3] ? g : b),
(nb[2] ? g : b),
(nb[1] ? g : b) * vlev,
(nb[0] ? g : b)
),
tile);
);
f32 txc[24];
generateCuboidTextureCoords(box, txc);
drawAutoLightedCuboid(box, tile, txc);
}
}
@ -1649,7 +1685,10 @@ void MapblockMeshGenerator::drawNodeboxNode()
for (auto &box : boxes) {
u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors);
drawAutoLightedCuboid(box, tiles, 6, nullptr, mask);
f32 txc[24];
generateCuboidTextureCoords(box, txc);
drawAutoLightedCuboid(box, tiles, 6, txc, mask);
}
}
@ -1676,7 +1715,7 @@ void MapblockMeshGenerator::drawMeshNode()
if (cur_node.f->mesh_ptr) {
// clone and rotate mesh
mesh = cloneMesh(cur_node.f->mesh_ptr);
mesh = cloneStaticMesh(cur_node.f->mesh_ptr);
bool modified = true;
if (facedir)
rotateMeshBy6dFacedir(mesh, facedir);

View file

@ -86,7 +86,7 @@ private:
template <typename Fn>
void drawCuboid(const aabb3f &box, const TileSpec *tiles, int tilecount,
const f32 *txc, u8 mask, Fn &&face_lighter);
void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
static void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
void drawAutoLightedCuboid(aabb3f box, const TileSpec &tile, f32 const *txc = nullptr, u8 mask = 0);
void drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, f32 const *txc = nullptr, u8 mask = 0);
u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const;

View file

@ -65,6 +65,8 @@
#include "client/sound/sound_openal.h"
#endif
#include <csignal>
class NodeDugEvent : public MtEvent
{
public:
@ -561,7 +563,7 @@ public:
Game();
~Game();
bool startup(bool *kill,
bool startup(volatile std::sig_atomic_t *kill,
InputHandler *input,
RenderingEngine *rendering_engine,
const GameStartData &game_params,
@ -797,7 +799,7 @@ private:
RenderingEngine *m_rendering_engine;
video::IVideoDriver *driver;
scene::ISceneManager *smgr;
bool *kill;
volatile std::sig_atomic_t *kill;
std::string *error_message;
bool *reconnect_requested;
PausedNodesList paused_animated_nodes;
@ -932,7 +934,7 @@ Game::~Game()
m_rendering_engine->finalize();
}
bool Game::startup(bool *kill,
bool Game::startup(volatile std::sig_atomic_t *kill,
InputHandler *input,
RenderingEngine *rendering_engine,
const GameStartData &start_data,
@ -1114,8 +1116,12 @@ void Game::run()
void Game::shutdown()
{
// Clear text when exiting.
// Delete text and menus first
m_game_ui->clearText();
m_game_formspec.reset();
while (g_menumgr.menuCount() > 0) {
g_menumgr.deleteFront();
}
if (g_touchcontrols)
g_touchcontrols->hide();
@ -1126,11 +1132,6 @@ void Game::shutdown()
sky.reset();
/* cleanup menus */
while (g_menumgr.menuCount() > 0) {
g_menumgr.deleteFront();
}
// only if the shutdown progress bar isn't shown yet
if (m_shutdown_progress == 0.0f)
showOverlayMessage(N_("Shutting down..."), 0, 0);
@ -4236,7 +4237,7 @@ void Game::readSettings()
****************************************************************************/
/****************************************************************************/
void the_game(bool *kill,
void the_game(volatile std::sig_atomic_t *kill,
InputHandler *input,
RenderingEngine *rendering_engine,
const GameStartData &start_data,

View file

@ -6,6 +6,7 @@
#include "irrlichttypes.h"
#include "config.h"
#include <csignal>
#include <string>
#if !IS_CLIENT_BUILD
@ -36,7 +37,7 @@ struct CameraOrientation {
#define GAME_FALLBACK_TIMEOUT 1.8f
#define GAME_CONNECTION_TIMEOUT 10.0f
void the_game(bool *kill,
void the_game(volatile std::sig_atomic_t *kill,
InputHandler *input,
RenderingEngine *rendering_engine,
const GameStartData &start_data,

View file

@ -31,16 +31,6 @@ struct TextDestNodeMetadata : public TextDest
m_p = p;
m_client = client;
}
// This is deprecated I guess? -celeron55
void gotText(const std::wstring &text)
{
std::string ntext = wide_to_utf8(text);
infostream << "Submitting 'text' field of node at (" << m_p.X << ","
<< m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
StringMap fields;
fields["text"] = ntext;
m_client->sendNodemetaFields(m_p, "", fields);
}
void gotText(const StringMap &fields)
{
m_client->sendNodemetaFields(m_p, "", fields);
@ -217,10 +207,11 @@ void GameFormSpec::deleteFormspec()
}
}
GameFormSpec::~GameFormSpec() {
void GameFormSpec::reset()
{
if (m_formspec)
m_formspec->quitMenu();
this->deleteFormspec();
deleteFormspec();
}
bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname)

View file

@ -26,7 +26,7 @@ struct GameFormSpec
{
void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input);
~GameFormSpec();
~GameFormSpec() { reset(); }
void showFormSpec(const std::string &formspec, const std::string &formname);
void showCSMFormSpec(const std::string &formspec, const std::string &formname);
@ -43,6 +43,7 @@ struct GameFormSpec
void disableDebugView();
bool handleCallbacks();
void reset();
#ifdef __ANDROID__
// Returns false if no formspec open

View file

@ -3,15 +3,16 @@
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "mesh.h"
#include "IMeshBuffer.h"
#include "SSkinMeshBuffer.h"
#include "debug.h"
#include "log.h"
#include <cmath>
#include <iostream>
#include <IAnimatedMesh.h>
#include <SAnimatedMesh.h>
#include <IAnimatedMeshSceneNode.h>
#include "S3DVertex.h"
#include "SMesh.h"
#include <SMesh.h>
#include "SMeshBuffer.h"
inline static void applyShadeFactor(video::SColor& color, float factor)
@ -95,11 +96,23 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
mesh->addMeshBuffer(buf);
buf->drop();
}
scaleMesh(mesh, scale); // also recalculates bounding box
return mesh;
}
scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
mesh->drop();
scaleMesh(anim_mesh, scale); // also recalculates bounding box
return anim_mesh;
template<typename F>
inline static void transformMeshBuffer(scene::IMeshBuffer *buf,
const F &transform_vertex)
{
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++) {
auto *vertex = (video::S3DVertex *)(vertices + i * stride);
transform_vertex(vertex);
}
buf->setDirty(scene::EBT_VERTEX);
buf->recalculateBoundingBox();
}
void scaleMesh(scene::IMesh *mesh, v3f scale)
@ -112,14 +125,9 @@ void scaleMesh(scene::IMesh *mesh, v3f scale)
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
buf->setDirty(scene::EBT_VERTEX);
buf->recalculateBoundingBox();
transformMeshBuffer(buf, [scale](video::S3DVertex *vertex) {
vertex->Pos *= scale;
});
// calculate total bounding box
if (j == 0)
@ -140,14 +148,9 @@ void translateMesh(scene::IMesh *mesh, v3f vec)
u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType());
u32 vertex_count = buf->getVertexCount();
u8 *vertices = (u8 *)buf->getVertices();
for (u32 i = 0; i < vertex_count; i++)
((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
buf->setDirty(scene::EBT_VERTEX);
buf->recalculateBoundingBox();
transformMeshBuffer(buf, [vec](video::S3DVertex *vertex) {
vertex->Pos += vec;
});
// calculate total bounding box
if (j == 0)
@ -330,44 +333,40 @@ bool checkMeshNormals(scene::IMesh *mesh)
return true;
}
template<class VertexType, class SMeshBufferType>
static scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{
auto *v = static_cast<VertexType *>(mesh_buffer->getVertices());
u16 *indices = mesh_buffer->getIndices();
auto *cloned_buffer = new SMeshBufferType();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
// Rigidly animated meshes may have transformation matrices that need to be applied
if (auto *sbuf = dynamic_cast<scene::SSkinMeshBuffer *>(mesh_buffer)) {
transformMeshBuffer(cloned_buffer, [sbuf](video::S3DVertex *vertex) {
sbuf->Transformation.transformVect(vertex->Pos);
vertex->Normal = sbuf->Transformation.rotateAndScaleVect(vertex->Normal);
vertex->Normal.normalize();
});
}
return cloned_buffer;
}
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{
switch (mesh_buffer->getVertexType()) {
case video::EVT_STANDARD: {
video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
case video::EVT_STANDARD:
return cloneMeshBuffer<video::S3DVertex, scene::SMeshBuffer>(mesh_buffer);
case video::EVT_2TCOORDS:
return cloneMeshBuffer<video::S3DVertex2TCoords, scene::SMeshBufferLightMap>(mesh_buffer);
case video::EVT_TANGENTS:
return cloneMeshBuffer<video::S3DVertexTangents, scene::SMeshBufferTangents>(mesh_buffer);
}
case video::EVT_2TCOORDS: {
video::S3DVertex2TCoords *v =
(video::S3DVertex2TCoords *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBufferLightMap *cloned_buffer =
new scene::SMeshBufferLightMap();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
case video::EVT_TANGENTS: {
video::S3DVertexTangents *v =
(video::S3DVertexTangents *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
scene::SMeshBufferTangents *cloned_buffer =
new scene::SMeshBufferTangents();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
}
}
// This should not happen.
sanity_check(false);
return NULL;
}
scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh)
{
scene::SMesh* dst_mesh = new scene::SMesh();
for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {

View file

@ -93,10 +93,8 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees);
*/
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer);
/*
Clone the mesh.
*/
scene::SMesh* cloneMesh(scene::IMesh *src_mesh);
/// Clone a mesh. For an animated mesh, this will clone the static pose.
scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh);
/*
Convert nodeboxes to mesh. Each tile goes into a different buffer.

View file

@ -14,25 +14,19 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertice
const TileLayer *layer = &tile.layers[layernum];
if (layer->empty())
continue;
append(*layer, vertices, numVertices, indices, numIndices, layernum,
tile.world_aligned);
append(*layer, vertices, numVertices, indices, numIndices, layernum);
}
}
void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices,
u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum,
bool use_scale)
u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum)
{
PreMeshBuffer &p = findBuffer(layer, layernum, numVertices);
f32 scale = 1.0f;
if (use_scale)
scale = 1.0f / layer.scale;
u32 vertex_count = p.vertices.size();
for (u32 i = 0; i < numVertices; i++) {
p.vertices.emplace_back(vertices[i].Pos + offset, vertices[i].Normal,
vertices[i].Color, scale * vertices[i].TCoords);
vertices[i].Color, vertices[i].TCoords);
m_bounding_radius_sq = std::max(m_bounding_radius_sq,
(vertices[i].Pos - m_center_pos).getLengthSQ());
}

View file

@ -55,7 +55,7 @@ private:
void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices,
u8 layernum, bool use_scale = false);
u8 layernum);
PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices);
};

View file

@ -410,7 +410,7 @@ video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name
shadow_map_name.c_str(), texture_format);
}
return m_driver->getTexture(shadow_map_name.c_str());
return m_driver->findTexture(shadow_map_name.c_str());
}
void ShadowRenderer::renderShadowMap(video::ITexture *target,

View file

@ -191,8 +191,6 @@ struct TileSpec
bool world_aligned = false;
//! Tile rotation.
TileRotation rotation = TileRotation::None;
//! This much light does the tile emit.
u8 emissive_light = 0;
//! The first is base texture, the second is overlay.
TileLayer layers[MAX_TILE_LAYERS];
};

View file

@ -255,7 +255,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
dim = core::dimension2d<u32>(dim.Width, frame_height);
}
scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
scene::SMesh *mesh = cloneMesh(original);
scene::SMesh *mesh = cloneStaticMesh(original);
original->drop();
//set texture
mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
@ -639,7 +639,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
// get mesh
core::dimension2d<u32> dim = texture->getSize();
scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
scene::SMesh *mesh = cloneMesh(original);
scene::SMesh *mesh = cloneStaticMesh(original);
original->drop();
//set texture

View file

@ -27,12 +27,13 @@ public:
void fill(v3s16 bpmin, v3s16 bpmax, MapNode n)
{
for (s16 z = bpmin.Z; z <= bpmax.Z; z++)
for (s16 y = bpmin.Y; y <= bpmax.Y; y++)
for (s16 x = bpmin.X; x <= bpmax.X; x++) {
for (s16 x = bpmin.X; x <= bpmax.X; x++)
for (s16 y = bpmin.Y; y <= bpmax.Y; y++) {
MapBlock *block = getBlockNoCreateNoEx({x, y, z});
if (block) {
auto *data = block->getData();
for (size_t i = 0; i < MapBlock::nodecount; i++)
block->getData()[i] = n;
data[i] = n;
block->expireIsAirCache();
}
}

View file

@ -718,19 +718,24 @@ void GUIButton::setFromStyle(const StyleSpec& style)
setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
setOverrideFont(style.getFont());
BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
if (style.isNotDefault(StyleSpec::BGIMG)) {
video::ITexture *texture = style.getTexture(StyleSpec::BGIMG,
getTextureSource());
if (BgMiddle.getArea() == 0) {
setImage(guiScalingImageButton(
Environment->getVideoDriver(), texture,
AbsoluteRect.getWidth(), AbsoluteRect.getHeight()));
} else {
// Scaling happens in `draw2DImage9Slice`
setImage(texture);
}
setScaleImage(true);
} else {
setImage(nullptr);
}
BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
// Child padding and offset
Padding = style.getRect(StyleSpec::PADDING, core::rect<s32>());
Padding = core::rect<s32>(

View file

@ -33,6 +33,8 @@
#include "client/sound/sound_openal.h"
#endif
#include <csignal>
/******************************************************************************/
void TextDestGuiEngine::gotText(const StringMap &fields)
@ -40,12 +42,6 @@ void TextDestGuiEngine::gotText(const StringMap &fields)
m_engine->getScriptIface()->handleMainMenuButtons(fields);
}
/******************************************************************************/
void TextDestGuiEngine::gotText(const std::wstring &text)
{
m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
}
/******************************************************************************/
MenuTextureSource::~MenuTextureSource()
{
@ -74,6 +70,7 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
if (retval)
return retval;
verbosestream << "MenuTextureSource: loading " << name << std::endl;
video::IImage *image = m_driver->createImageFromFile(name.c_str());
if (!image)
return NULL;
@ -109,7 +106,7 @@ GUIEngine::GUIEngine(JoystickController *joystick,
RenderingEngine *rendering_engine,
IMenuManager *menumgr,
MainMenuData *data,
bool &kill) :
volatile std::sig_atomic_t &kill) :
m_rendering_engine(rendering_engine),
m_parent(parent),
m_menumanager(menumgr),
@ -404,12 +401,6 @@ GUIEngine::~GUIEngine()
m_sound_manager.reset();
m_irr_toplefttext->remove();
// delete textures
for (image_definition &texture : m_textures) {
if (texture.texture)
m_rendering_engine->get_video_driver()->removeTexture(texture.texture);
}
}
/******************************************************************************/
@ -597,26 +588,16 @@ void GUIEngine::drawFooter(video::IVideoDriver *driver)
bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath,
bool tile_image, unsigned int minsize)
{
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
m_textures[layer].texture = nullptr;
if (m_textures[layer].texture) {
driver->removeTexture(m_textures[layer].texture);
m_textures[layer].texture = NULL;
}
if (texturepath.empty() || !fs::PathExists(texturepath)) {
if (texturepath.empty() || !fs::PathExists(texturepath))
return false;
}
m_textures[layer].texture = driver->getTexture(texturepath.c_str());
m_textures[layer].texture = m_texture_source->getTexture(texturepath);
m_textures[layer].tile = tile_image;
m_textures[layer].minsize = minsize;
if (!m_textures[layer].texture) {
return false;
}
return true;
return m_textures[layer].texture != nullptr;
}
/******************************************************************************/

View file

@ -14,6 +14,8 @@
#include "util/enriched_string.h"
#include "translation.h"
#include <csignal>
/******************************************************************************/
/* Structs and macros */
/******************************************************************************/
@ -61,12 +63,6 @@ public:
*/
void gotText(const StringMap &fields);
/**
* receive text/events transmitted by guiFormSpecMenu
* @param text textual representation of event
*/
void gotText(const std::wstring &text);
private:
/** target to transmit data to */
GUIEngine *m_engine = nullptr;
@ -130,7 +126,7 @@ public:
RenderingEngine *rendering_engine,
IMenuManager *menumgr,
MainMenuData *data,
bool &kill);
volatile std::sig_atomic_t &kill);
/** default destructor */
virtual ~GUIEngine();
@ -199,7 +195,7 @@ private:
irr_ptr<GUIFormSpecMenu> m_menu;
/** reference to kill variable managed by SIGINT handler */
bool &m_kill;
volatile std::sig_atomic_t &m_kill;
/** variable used to abort menu and return back to main game handling */
bool m_startgame = false;

View file

@ -77,6 +77,9 @@
return; \
}
// Element ID of the "Proceed" button shown for sizeless formspecs
constexpr s32 ID_PROCEED_BTN = 257;
/*
GUIFormSpecMenu
*/
@ -1235,10 +1238,14 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
}
//now really show table
GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
rect, m_tsrc);
// Apply styling before calculating the cell sizes
auto style = getDefaultStyleForElement("table", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideFont(style.getFont());
if (spec.fname == m_focused_element) {
Environment->setFocus(e);
}
@ -1252,10 +1259,6 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
if (!str_initial_selection.empty() && str_initial_selection != "0")
e->setSelected(stoi(str_initial_selection));
auto style = getDefaultStyleForElement("table", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideFont(style.getFont());
m_tables.emplace_back(spec, e);
m_fields.push_back(spec);
}
@ -2998,7 +3001,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
gui::IGUIElement *focused_element = Environment->getFocus();
if (focused_element && focused_element->getParent() == this) {
s32 focused_id = focused_element->getID();
if (focused_id > 257) {
if (focused_id > ID_PROCEED_BTN) {
for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
if (field.fid == focused_id) {
m_focused_element = field.fname;
@ -3308,7 +3311,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
size.X / 2 - 70, pos.Y,
size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
);
GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257,
GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, ID_PROCEED_BTN,
wstrgettext("Proceed").c_str());
}
}
@ -4031,12 +4034,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
if (m_joystick->wasKeyDown(KeyType::ESC)) {
tryClose();
} else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
trySubmitClose();
}
}
return handled;
@ -4053,7 +4051,16 @@ void GUIFormSpecMenu::tryClose()
quitMenu();
} else {
acceptInput(quit_mode_try);
m_text_dst->gotText(L"MenuQuit");
}
}
void GUIFormSpecMenu::trySubmitClose()
{
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
}
@ -4100,12 +4107,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
break;
}
if (current_keys_pending.key_enter) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
trySubmitClose();
} else {
acceptInput();
}
@ -4819,12 +4821,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
&& isVisible()) {
const s32 caller_id = event.GUIEvent.Caller->getID();
bool close_on_enter;
switch (event.GUIEvent.EventType) {
case gui::EGET_TAB_CHANGED:
if (!isVisible())
break;
// find the element that was clicked
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if ((s.ftype == f_TabHeader) &&
(s.fid == event.GUIEvent.Caller->getID())) {
if (s.ftype == f_TabHeader &&
s.fid == caller_id) {
if (!s.sound.empty() && m_sound_manager)
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
s.send = true;
@ -4833,26 +4841,26 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
return true;
}
}
}
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
&& isVisible()) {
break;
case gui::EGET_ELEMENT_FOCUS_LOST:
if (!isVisible())
break;
if (!canTakeFocus(event.GUIEvent.Element)) {
infostream<<"GUIFormSpecMenu: Not allowing focus change."
<<std::endl;
// Returning true disables focus change
return true;
}
}
if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
s32 caller_id = event.GUIEvent.Caller->getID();
break;
if (caller_id == 257) {
acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu();
case gui::EGET_BUTTON_CLICKED:
case gui::EGET_CHECKBOX_CHANGED:
case gui::EGET_COMBO_BOX_CHANGED:
case gui::EGET_SCROLL_BAR_CHANGED:
if (caller_id == ID_PROCEED_BTN) {
trySubmitClose();
return true;
}
@ -4882,7 +4890,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
if (s.is_exit) {
acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu();
return true;
}
@ -4923,20 +4930,22 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s.send = false;
}
}
}
if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
// move scroll_containers
for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
c.second->onScrollEvent(event.GUIEvent.Caller);
}
break;
if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
if (event.GUIEvent.Caller->getID() > 257) {
bool close_on_enter = true;
case gui::EGET_EDITBOX_ENTER:
if (caller_id <= ID_PROCEED_BTN)
break;
close_on_enter = true;
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if (s.ftype == f_Unknown &&
s.fid == event.GUIEvent.Caller->getID()) {
s.fid == caller_id) {
current_field_enter_pending = s.fname;
auto it = field_close_on_enter.find(s.fname);
if (it != field_close_on_enter.end())
@ -4948,35 +4957,30 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
current_keys_pending.key_enter = true;
if (close_on_enter) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
} else {
if (close_on_enter)
trySubmitClose();
else
acceptInput();
}
return true;
}
}
if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
int current_id = event.GUIEvent.Caller->getID();
if (current_id > 257) {
case gui::EGET_TABLE_CHANGED:
if (caller_id <= ID_PROCEED_BTN)
break;
// find the element that was clicked
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
// if it's a table, set the send field
// so lua knows which table was changed
if ((s.ftype == f_Table) && (s.fid == current_id)) {
if (s.ftype == f_Table && s.fid == caller_id) {
s.send = true;
acceptInput();
s.send=false;
s.send = false;
}
}
return true;
}
default:
break;
}
}

View file

@ -68,8 +68,6 @@ struct TextDest
{
virtual ~TextDest() = default;
// This is deprecated I guess? -celeron55
virtual void gotText(const std::wstring &text) {}
virtual void gotText(const StringMap &fields) = 0;
std::string m_formname;
@ -493,6 +491,7 @@ private:
bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect);
void tryClose();
void trySubmitClose();
void showTooltip(const std::wstring &text, const irr::video::SColor &color,
const irr::video::SColor &bgcolor);

View file

@ -85,7 +85,7 @@ public:
void setTextList(const std::vector<std::string> &content,
bool transparent);
/* Set generic table options, columns and content */
/** Set generic table options, columns and content, calculate cell sizes */
// Adds empty strings to end of content if there is an incomplete row
void setTable(const TableOptions &options,
const TableColumns &columns,

View file

@ -59,6 +59,8 @@ public:
if(!m_stack.empty()) {
m_stack.back()->setVisible(true);
guienv->setFocus(m_stack.back());
} else {
guienv->removeFocus(menu);
}
}

View file

@ -1138,7 +1138,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
return false;
}
ChatInterface iface;
bool &kill = *porting::signal_handler_killstatus();
volatile auto &kill = *porting::signal_handler_killstatus();
try {
// Create server
@ -1181,7 +1181,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
server.start();
// Run server
bool &kill = *porting::signal_handler_killstatus();
volatile auto &kill = *porting::signal_handler_killstatus();
dedicated_server_loop(server, kill);
} catch (const ModError &e) {
@ -1226,7 +1226,7 @@ static bool migrate_map_database(const GameParams &game_params, const Settings &
u32 count = 0;
u64 last_update_time = 0;
bool &kill = *porting::signal_handler_killstatus();
volatile auto &kill = *porting::signal_handler_killstatus();
std::vector<v3s16> blocks;
old_db->listAllLoadableBlocks(blocks);
@ -1280,7 +1280,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting
u32 count = 0;
u64 last_update_time = 0;
bool &kill = *porting::signal_handler_killstatus();
volatile auto &kill = *porting::signal_handler_killstatus();
const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE;
const s16 map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9);

View file

@ -772,52 +772,79 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent)
infostream<<std::endl;
}
const bool all_new = m_area.hasEmptyExtent() || block_area_nodes.contains(m_area);
std::map<v3s16, bool> had_blocks;
// we can skip this calculation if the areas are disjoint
if (!m_area.intersect(block_area_nodes).hasEmptyExtent())
had_blocks = getCoveredBlocks();
const bool all_new = m_area.hasEmptyExtent();
addArea(block_area_nodes);
for(s32 z=p_min.Z; z<=p_max.Z; z++)
for(s32 y=p_min.Y; y<=p_max.Y; y++)
for(s32 x=p_min.X; x<=p_max.X; x++)
{
u8 flags = 0;
MapBlock *block;
v3s16 p(x,y,z);
if (m_loaded_blocks.count(p) > 0)
// if this block was already in the vmanip and it has data, skip
if (auto it = had_blocks.find(p); it != had_blocks.end() && it->second)
continue;
bool block_data_inexistent = false;
{
TimeTaker timer2("emerge load", &emerge_load_time);
block = m_map->getBlockNoCreateNoEx(p);
if (!block)
block_data_inexistent = true;
else
block->copyTo(*this);
}
if(block_data_inexistent)
{
if (load_if_inexistent && !blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true);
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
if (block) {
block->copyTo(*this);
} else {
if (load_if_inexistent && !blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true);
assert(block);
block->copyTo(*this);
} else {
flags |= VMANIP_BLOCK_DATA_INEXIST;
// Mark area inexistent
VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
setFlags(a, VOXELFLAG_NO_DATA);
}
}
m_loaded_blocks[p] = flags;
}
if (all_new)
m_is_dirty = false;
}
std::map<v3s16, bool> MMVManip::getCoveredBlocks() const
{
std::map<v3s16, bool> ret;
if (m_area.hasEmptyExtent())
return ret;
// Figure out if *any* node in this block has data according to m_flags
const auto &check_block = [this] (v3s16 bp) -> bool {
v3s16 pmin = bp * MAP_BLOCKSIZE;
v3s16 pmax = pmin + v3s16(MAP_BLOCKSIZE-1);
for(s16 z=pmin.Z; z<=pmax.Z; z++)
for(s16 y=pmin.Y; y<=pmax.Y; y++)
for(s16 x=pmin.X; x<=pmax.X; x++) {
if (!(m_flags[m_area.index(x,y,z)] & VOXELFLAG_NO_DATA))
return true;
}
return false;
};
v3s16 bpmin = getNodeBlockPos(m_area.MinEdge);
v3s16 bpmax = getNodeBlockPos(m_area.MaxEdge);
if (bpmin * MAP_BLOCKSIZE != m_area.MinEdge)
throw BaseException("MMVManip not block-aligned");
if ((bpmax+1) * MAP_BLOCKSIZE - v3s16(1) != m_area.MaxEdge)
throw BaseException("MMVManip not block-aligned");
for(s16 z=bpmin.Z; z<=bpmax.Z; z++)
for(s16 y=bpmin.Y; y<=bpmax.Y; y++)
for(s16 x=bpmin.X; x<=bpmax.X; x++) {
v3s16 bp(x,y,z);
ret[bp] = check_block(bp);
}
return ret;
}
void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
bool overwrite_generated) const
{
@ -825,16 +852,27 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
return;
assert(m_map);
/*
Copy data of all blocks
*/
assert(!m_loaded_blocks.empty());
for (auto &loaded_block : m_loaded_blocks) {
v3s16 p = loaded_block.first;
size_t nload = 0;
// Copy all the blocks with data back to the map
const auto loaded_blocks = getCoveredBlocks();
for (auto &it : loaded_blocks) {
if (!it.second)
continue;
v3s16 p = it.first;
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST);
if (!existed || (block == NULL) ||
(!overwrite_generated && block->isGenerated()))
if (!block) {
if (!blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true);
nload++;
}
}
if (!block) {
warningstream << "blitBackAll: Couldn't load block " << p
<< " to write data to map" << std::endl;
continue;
}
if (!overwrite_generated && block->isGenerated())
continue;
block->copyFrom(*this);
@ -844,6 +882,10 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
if(modified_blocks)
(*modified_blocks)[p] = block;
}
if (nload > 0) {
verbosestream << "blitBackAll: " << nload << " blocks had to be loaded for writing" << std::endl;
}
}
MMVManip *MMVManip::clone() const
@ -860,11 +902,7 @@ MMVManip *MMVManip::clone() const
ret->m_flags = new u8[size];
memcpy(ret->m_flags, m_flags, size * sizeof(u8));
}
ret->m_is_dirty = m_is_dirty;
// Even if the copy is disconnected from a map object keep the information
// needed to write it back to one
ret->m_loaded_blocks = m_loaded_blocks;
return ret;
}

View file

@ -307,16 +307,29 @@ public:
MMVManip(Map *map);
virtual ~MMVManip() = default;
virtual void clear()
{
VoxelManipulator::clear();
m_loaded_blocks.clear();
}
/*
Loads specified area from map and *adds* it to the area already
contained in the VManip.
*/
void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
bool load_if_inexistent = true);
// This is much faster with big chunks of generated data
/**
Uses the flags array to determine which blocks the VManip covers,
and for which of them we have any data.
@warning requires VManip area to be block-aligned
@return map of blockpos -> any data?
*/
std::map<v3s16, bool> getCoveredBlocks() const;
/**
Writes data in VManip back to the map. Blocks without any data in the VManip
are skipped.
@note VOXELFLAG_NO_DATA is checked per-block, not per-node. So you need
to ensure that the relevant parts of m_data are initialized.
@param modified_blocks output array of touched blocks (optional)
@param overwrite_generated if false, blocks marked as generate in the map are not changed
*/
void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
bool overwrite_generated = true) const;
@ -339,13 +352,4 @@ protected:
// may be null
Map *m_map = nullptr;
/*
key = blockpos
value = flags describing the block
*/
std::map<v3s16, u8> m_loaded_blocks;
enum : u8 {
VMANIP_BLOCK_DATA_INEXIST = 1 << 0,
};
};

View file

@ -1314,11 +1314,6 @@ Connection::~Connection()
m_sendThread->stop();
m_receiveThread->stop();
//TODO for some unkonwn reason send/receive threads do not exit as they're
// supposed to be but wait on peer timeout. To speed up shutdown we reduce
// timeout to half a second.
m_sendThread->setPeerTimeout(0.5);
// wait for threads to finish
m_sendThread->wait();
m_receiveThread->wait();

View file

@ -12,6 +12,7 @@
#include "util/numeric.h"
#include "porting.h"
#include "network/networkprotocol.h"
#include <atomic>
#include <iostream>
#include <vector>
#include <memory>
@ -301,7 +302,7 @@ private:
// Backwards compatibility
PeerHandler *m_bc_peerhandler;
bool m_shutting_down = false;
std::atomic<bool> m_shutting_down = false;
};
} // namespace

View file

@ -40,10 +40,6 @@ void Server::handleCommand_Deprecated(NetworkPacket* pkt)
void Server::handleCommand_Init(NetworkPacket* pkt)
{
if(pkt->getSize() < 1)
return;
session_t peer_id = pkt->getPeerId();
RemoteClient *client = getClient(peer_id, CS_Created);
@ -75,15 +71,6 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
verbosestream << "Server: Got TOSERVER_INIT from " << addr_s <<
" (peer_id=" << peer_id << ")" << std::endl;
// Do not allow multiple players in simple singleplayer mode.
// This isn't a perfect way to do it, but will suffice for now
if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) {
infostream << "Server: Not allowing another client (" << addr_s <<
") to connect in simple singleplayer mode" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
return;
}
if (denyIfBanned(peer_id))
return;
@ -161,18 +148,14 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
return;
}
RemotePlayer *player = m_env->getPlayer(playername, true);
// If player is already connected, cancel
if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
actionstream << "Server: Player with name \"" << playername <<
"\" tried to connect, but player with same name is already connected" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
// Do not allow multiple players in simple singleplayer mode
if (isSingleplayer() && !m_clients.getClientIDs(CS_HelloSent).empty()) {
infostream << "Server: Not allowing another client (" << addr_s <<
") to connect in simple singleplayer mode" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
return;
}
m_clients.setPlayerName(peer_id, playername);
// Or the "singleplayer" name to be used on regular servers
if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) {
actionstream << "Server: Player with the name \"singleplayer\" tried "
"to connect from " << addr_s << std::endl;
@ -180,12 +163,25 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
return;
}
{
RemotePlayer *player = m_env->getPlayer(playername, true);
// If player is already connected, cancel
if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
actionstream << "Server: Player with name \"" << playername <<
"\" tried to connect, but player with same name is already connected" << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
return;
}
}
client->setName(playerName);
{
std::string reason;
if (m_script->on_prejoinplayer(playername, addr_s, &reason)) {
actionstream << "Server: Player with the name \"" << playerName <<
"\" tried to connect from " << addr_s <<
" but it was disallowed for the following reason: " << reason <<
" but was disallowed for the following reason: " << reason <<
std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason);
return;
@ -195,14 +191,11 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
infostream << "Server: New connection: \"" << playerName << "\" from " <<
addr_s << " (peer_id=" << peer_id << ")" << std::endl;
// Enforce user limit.
// Don't enforce for users that have some admin right or mod permits it.
if (m_clients.isUserLimitReached() &&
playername != g_settings->get("name") &&
!m_script->can_bypass_userlimit(playername, addr_s)) {
// Early check for user limit, so the client doesn't need to run
// through the join process only to be denied.
if (checkUserLimit(playerName, addr_s)) {
actionstream << "Server: " << playername << " tried to join from " <<
addr_s << ", but there are already max_users=" <<
g_settings->getU16("max_users") << " players." << std::endl;
addr_s << ", but the user limit was reached." << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
return;
}
@ -302,6 +295,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
sendMediaAnnouncement(peer_id, lang);
RemoteClient *client = getClient(peer_id, CS_InitDone);
assert(client);
// Keep client language for server translations
client->setLangCode(lang);
@ -354,6 +348,8 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
void Server::handleCommand_ClientReady(NetworkPacket* pkt)
{
session_t peer_id = pkt->getPeerId();
RemoteClient *client = getClient(peer_id, CS_Created);
assert(client);
// decode all information first
u8 major_ver, minor_ver, patch_ver, reserved;
@ -364,8 +360,17 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
if (pkt->getRemainingBytes() >= 2)
*pkt >> formspec_ver;
m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver,
full_ver);
client->setVersionInfo(major_ver, minor_ver, patch_ver, full_ver);
// Since only active clients count for the user limit, two could race the
// join process so we have to do a final check for the user limit here.
std::string addr_s = client->getAddress().serializeString();
if (checkUserLimit(client->getName(), addr_s)) {
actionstream << "Server: " << client->getName() << " tried to join from " <<
addr_s << ", but the user limit was reached (late)." << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
return;
}
// Emerge player
PlayerSAO* playersao = StageTwoClientInit(peer_id);
@ -1426,7 +1431,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
std::string salt, verification_key;
std::string addr_s = getPeerAddress(peer_id).serializeString();
std::string addr_s = client->getAddress().serializeString();
u8 is_empty;
*pkt >> salt >> verification_key >> is_empty;
@ -1512,9 +1517,11 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
RemoteClient *client = getClient(peer_id, CS_Invalid);
ClientState cstate = client->getState();
std::string addr_s = client->getAddress().serializeString();
if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
" from " << getPeerAddress(peer_id).serializeString() <<
" from " << addr_s <<
". Ignoring." << std::endl;
return;
}
@ -1524,7 +1531,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
if (client->chosen_mech != AUTH_MECHANISM_NONE) {
actionstream << "Server: got SRP _A packet, while auth is already "
"going on with mech " << client->chosen_mech << " from " <<
getPeerAddress(peer_id).serializeString() <<
addr_s <<
" (wantSudo=" << wantSudo << "). Ignoring." << std::endl;
if (wantSudo) {
DenySudoAccess(peer_id);
@ -1541,7 +1548,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
infostream << "Server: TOSERVER_SRP_BYTES_A received with "
<< "based_on=" << int(based_on) << " and len_A="
<< bytes_A.length() << "." << std::endl;
<< bytes_A.length() << std::endl;
AuthMechanism chosen = (based_on == 0) ?
AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP;
@ -1550,17 +1557,17 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
// Right now, the auth mechs don't change between login and sudo mode.
if (!client->isMechAllowed(chosen)) {
actionstream << "Server: Player \"" << client->getName() <<
"\" at " << getPeerAddress(peer_id).serializeString() <<
"\" from " << addr_s <<
" tried to change password using unallowed mech " << chosen <<
"." << std::endl;
std::endl;
DenySudoAccess(peer_id);
return;
}
} else {
if (!client->isMechAllowed(chosen)) {
actionstream << "Server: Client tried to authenticate from " <<
getPeerAddress(peer_id).serializeString() <<
" using unallowed mech " << chosen << "." << std::endl;
addr_s <<
" using unallowed mech " << chosen << std::endl;
DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}

View file

@ -13,6 +13,7 @@
#include "client/texturesource.h"
#include "client/tile.h"
#include <IMeshManipulator.h>
#include <SMesh.h>
#include <SkinnedMesh.h>
#endif
#include "log.h"
@ -959,23 +960,37 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
palette = tsrc->getPalette(palette_name);
if (drawtype == NDT_MESH && !mesh.empty()) {
// Read the mesh and apply scale
mesh_ptr = client->getMesh(mesh);
if (mesh_ptr) {
v3f scale = v3f(BS) * visual_scale;
scaleMesh(mesh_ptr, scale);
// Note: By freshly reading, we get an unencumbered mesh.
if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
bool apply_bs = false;
if (auto *skinned_mesh = dynamic_cast<scene::SkinnedMesh *>(src_mesh)) {
// Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS.
// See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329
bool is_gltf = skinned_mesh->getSourceFormat() ==
scene::SkinnedMesh::SourceFormat::GLTF;
apply_bs = skinned_mesh->isStatic() && !is_gltf;
// Nodes do not support mesh animation, so we clone the static pose.
// This simplifies working with the mesh: We can just scale the vertices
// as transformations have already been applied.
mesh_ptr = cloneStaticMesh(src_mesh);
src_mesh->drop();
} else {
auto *static_mesh = dynamic_cast<scene::SMesh *>(src_mesh);
assert(static_mesh);
mesh_ptr = static_mesh;
// Compatibility: Apply BS scaling to static meshes (.obj). See #15811.
apply_bs = true;
}
scaleMesh(mesh_ptr, v3f((apply_bs ? BS : 1.0f) * visual_scale));
recalculateBoundingBox(mesh_ptr);
if (!checkMeshNormals(mesh_ptr)) {
// TODO this should be done consistently when the mesh is loaded
infostream << "ContentFeatures: recalculating normals for mesh "
<< mesh << std::endl;
meshmanip->recalculateNormals(mesh_ptr, true, false);
} else {
// Animation is not supported, but we need to reset it to
// default state if it is animated.
// Note: recalculateNormals() also does this hence the else-block
if (mesh_ptr->getMeshType() == scene::EAMT_SKINNED)
((scene::SkinnedMesh*) mesh_ptr)->resetAnimation();
}
} else {
mesh_ptr = nullptr;
}
}
}

View file

@ -342,7 +342,7 @@ struct ContentFeatures
enum NodeDrawType drawtype;
std::string mesh;
#if CHECK_CLIENT_BUILD()
scene::IMesh *mesh_ptr; // mesh in case of mesh node
scene::SMesh *mesh_ptr; // mesh in case of mesh node
video::SColor minimap_color;
#endif
float visual_scale; // Misc. scale parameter

View file

@ -60,6 +60,7 @@
#include "util/string.h"
#include "util/tracy_wrapper.h"
#include <vector>
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <signal.h>
@ -81,31 +82,28 @@ namespace porting
Signal handler (grabs Ctrl-C on POSIX systems)
*/
static bool g_killed = false;
volatile static std::sig_atomic_t g_killed = false;
bool *signal_handler_killstatus()
volatile std::sig_atomic_t *signal_handler_killstatus()
{
return &g_killed;
}
#if !defined(_WIN32) // POSIX
#define STDERR_FILENO 2
static void signal_handler(int sig)
{
if (!g_killed) {
if (sig == SIGINT) {
dstream << "INFO: signal_handler(): "
<< "Ctrl-C pressed, shutting down." << std::endl;
const char *dbg_text{"INFO: signal_handler(): "
"Ctrl-C pressed, shutting down.\n"};
write(STDERR_FILENO, dbg_text, strlen(dbg_text));
} else if (sig == SIGTERM) {
dstream << "INFO: signal_handler(): "
<< "got SIGTERM, shutting down." << std::endl;
const char *dbg_text{"INFO: signal_handler(): "
"got SIGTERM, shutting down.\n"};
write(STDERR_FILENO, dbg_text, strlen(dbg_text));
}
// Comment out for less clutter when testing scripts
/*dstream << "INFO: sigint_handler(): "
<< "Printing debug stacks" << std::endl;
debug_stacks_print();*/
g_killed = true;
} else {
(void)signal(sig, SIG_DFL);

View file

@ -13,6 +13,7 @@
#endif
// Be mindful of what you include here!
#include <csignal>
#include <string>
#include "config.h"
#include "irrlichttypes.h" // u64
@ -77,7 +78,7 @@ namespace porting
void signal_handler_init();
// Returns a pointer to a bool.
// When the bool is true, program should quit.
[[nodiscard]] bool *signal_handler_killstatus();
[[nodiscard]] volatile std::sig_atomic_t *signal_handler_killstatus();
/*
Path of static data directory.

View file

@ -56,9 +56,16 @@ void read_item_definition(lua_State* L, int index,
if (index < 0)
index = lua_gettop(L) + 1 + index;
def.type = (ItemType)getenumfield(L, index, "type",
es_ItemType, ITEM_NONE);
def.name.clear();
getstringfield(L, index, "name", def.name);
{
auto str = getstringfield_default(L, index, "type", "");
if (!string_to_enum(es_ItemType, def.type, str))
warningstream << "Item " << def.name
<< " has unknown type \"" << str << '"' << std::endl;
}
getstringfield(L, index, "description", def.description);
getstringfield(L, index, "short_description", def.short_description);
getstringfield(L, index, "inventory_image", def.inventory_image);
@ -605,9 +612,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
case NDT_PLANTLIKE:
case NDT_FIRELIKE:
default_tiling = false;
// "break" is omitted here intentionaly, as PLANTLIKE
// FIRELIKE drawtype both should default to having
// backface_culling to false.
[[fallthrough]];
case NDT_MESH:
case NDT_LIQUID:
@ -621,7 +625,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
break;
}
// key at index -2 and value at index
if(lua_isstring(L, index)){
// "default_lava.png"
tiledef.name = lua_tostring(L, index);
@ -634,7 +637,10 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
// name="default_lava.png"
tiledef.name.clear();
getstringfield(L, index, "name", tiledef.name);
getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat.
warn_if_field_exists(L, index, "image", "TileDef",
"Deprecated: new name is \"name\".");
getstringfield(L, index, "image", tiledef.name);
tiledef.backface_culling = getboolfield_default(
L, index, "backface_culling", default_culling);
tiledef.tileable_horizontal = getboolfield_default(
@ -659,6 +665,9 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
lua_getfield(L, index, "animation");
tiledef.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
} else if (!lua_isnil(L, index)) {
// TODO: should be an error
errorstream << "TileDef: Invalid type! (expected string or table)" << std::endl;
}
return tiledef;
@ -672,13 +681,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
/* Cache existence of some callbacks */
lua_getfield(L, index, "on_construct");
if(!lua_isnil(L, -1)) f.has_on_construct = true;
f.has_on_construct = !lua_isnil(L, -1);
lua_pop(L, 1);
lua_getfield(L, index, "on_destruct");
if(!lua_isnil(L, -1)) f.has_on_destruct = true;
f.has_on_destruct = !lua_isnil(L, -1);
lua_pop(L, 1);
lua_getfield(L, index, "after_destruct");
if(!lua_isnil(L, -1)) f.has_after_destruct = true;
f.has_after_destruct = !lua_isnil(L, -1);
lua_pop(L, 1);
lua_getfield(L, index, "on_rightclick");
@ -695,8 +704,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
/* Visual definition */
f.drawtype = (NodeDrawType)getenumfield(L, index, "drawtype",
ScriptApiNode::es_DrawType,NDT_NORMAL);
{
auto str = getstringfield_default(L, index, "drawtype", "");
if (!string_to_enum(ScriptApiNode::es_DrawType, f.drawtype, str))
warningstream << "Node " << f.name
<< " has unknown drawtype \"" << str << '"' << std::endl;
}
getfloatfield(L, index, "visual_scale", f.visual_scale);
/* Meshnode model filename */
@ -796,10 +810,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
if (lua_toboolean(L, -1))
f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND;
} else if (check_field_or_nil(L, -1, LUA_TSTRING, "use_texture_alpha")) {
int result = f.alpha;
string_to_enum(ScriptApiNode::es_TextureAlphaMode, result,
std::string(lua_tostring(L, -1)));
f.alpha = static_cast<enum AlphaMode>(result);
string_to_enum(ScriptApiNode::es_TextureAlphaMode, f.alpha, lua_tostring(L, -1));
}
lua_pop(L, 1);
@ -817,10 +828,18 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
getboolfield(L, index, "post_effect_color_shaded", f.post_effect_color_shaded);
f.param_type = (ContentParamType)getenumfield(L, index, "paramtype",
ScriptApiNode::es_ContentParamType, CPT_NONE);
f.param_type_2 = (ContentParamType2)getenumfield(L, index, "paramtype2",
ScriptApiNode::es_ContentParamType2, CPT2_NONE);
{
auto str = getstringfield_default(L, index, "paramtype", "");
if (!string_to_enum(ScriptApiNode::es_ContentParamType, f.param_type, str))
warningstream << "Node " << f.name
<< " has unknown paramtype \"" << str << '"' << std::endl;
}
{
auto str = getstringfield_default(L, index, "paramtype2", "");
if (!string_to_enum(ScriptApiNode::es_ContentParamType2, f.param_type_2, str))
warningstream << "Node " << f.name
<< " has unknown paramtype2 \"" << str << '"' << std::endl;
}
if (!f.palette_name.empty() &&
!(f.param_type_2 == CPT2_COLOR ||
@ -855,8 +874,12 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
// Liquids flow into and replace node
getboolfield(L, index, "floodable", f.floodable);
// Whether the node is non-liquid, source liquid or flowing liquid
f.liquid_type = (LiquidType)getenumfield(L, index, "liquidtype",
ScriptApiNode::es_LiquidType, LIQUID_NONE);
{
auto str = getstringfield_default(L, index, "liquidtype", "");
if (!string_to_enum(ScriptApiNode::es_LiquidType, f.liquid_type, str))
warningstream << "Node " << f.name
<< " has unknown liquidtype \"" << str << '"' << std::endl;
}
// If the content is liquid, this is the flowing version of the liquid.
getstringfield(L, index, "liquid_alternative_flowing",
f.liquid_alternative_flowing);
@ -915,7 +938,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
lua_pushnil(L);
while (lua_next(L, table) != 0) {
// Value at -1
std::string side(lua_tostring(L, -1));
std::string_view side(lua_tostring(L, -1));
// Note faces are flipped to make checking easier
if (side == "top")
f.connect_sides |= 2;
@ -986,6 +1009,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
} else if(lua_isnil(L, -1)) {
f.liquid_move_physics = f.liquid_type != LIQUID_NONE;
} else {
// TODO: should be an error
errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl;
}
lua_pop(L, 1);
@ -1805,10 +1829,8 @@ WearBarParams read_wear_bar_params(
auto blend = WearBarParams::BLEND_MODE_CONSTANT;
lua_getfield(L, stack_idx, "blend");
if (check_field_or_nil(L, -1, LUA_TSTRING, "blend")) {
int blendInt;
if (!string_to_enum(WearBarParams::es_BlendMode, blendInt, std::string(lua_tostring(L, -1))))
if (!string_to_enum(WearBarParams::es_BlendMode, blend, lua_tostring(L, -1)))
throw LuaError("Invalid wear bar color blend mode");
blend = static_cast<WearBarParams::BlendMode>(blendInt);
}
lua_pop(L, 1);
@ -2395,16 +2417,11 @@ void push_hud_element(lua_State *L, HudElement *elem)
bool read_hud_change(lua_State *L, HudElementStat &stat, HudElement *elem, void **value)
{
std::string statstr = lua_tostring(L, 3);
{
int statint;
if (!string_to_enum(es_HudElementStat, statint, statstr)) {
if (!string_to_enum(es_HudElementStat, stat, statstr)) {
script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream);
return false;
}
stat = static_cast<HudElementStat>(statint);
}
switch (stat) {
case HUD_STAT_POS:
elem->pos = read_v2f(L, 4);

View file

@ -18,6 +18,8 @@ extern "C" {
#include <cmath>
#include "common/c_types.h"
static v3d read_v3d(lua_State *L, int index);
static v3d check_v3d(lua_State *L, int index);
#define CHECK_TYPE(index, name, type) do { \
int t = lua_type(L, (index)); \
@ -28,6 +30,16 @@ extern "C" {
} \
} while(0)
// TODO: this should be turned into an error in 2026.
// Just revert the commit that added this line.
#define CHECK_NOT_NIL(index, name) do { \
if (lua_isnoneornil(L, (index))) { \
auto msg = std::string("Invalid ") + (name) + \
" (value is nil)."; \
log_deprecated(L, msg, 1, true); \
} \
} while(0)
#define CHECK_FLOAT(value, name) do {\
if (std::isnan(value) || std::isinf(value)) { \
throw LuaError("Invalid float value for '" name \
@ -35,7 +47,13 @@ extern "C" {
} \
} while (0)
// strictly check type of coordinate
// (this won't permit string-to-int conversion, so maybe not the best idea?)
#define CHECK_POS_COORD(index, name) CHECK_TYPE(index, "vector coordinate " name, LUA_TNUMBER)
// loosely check type of coordinate
#define CHECK_POS_COORD2(index, name) CHECK_NOT_NIL(index, "vector coordinate " name)
// Note: not needed when using read_v3_aux
#define CHECK_POS_TAB(index) CHECK_TYPE(index, "vector", LUA_TTABLE)
@ -44,6 +62,7 @@ extern "C" {
*/
static void read_v3_aux(lua_State *L, int index)
{
// TODO: someone find out if it's faster to have the type check in Lua too
CHECK_POS_TAB(index);
lua_pushvalue(L, index);
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR);
@ -87,24 +106,12 @@ void push_v2f(lua_State *L, v2f p)
v2s16 read_v2s16(lua_State *L, int index)
{
v2s16 p;
CHECK_POS_TAB(index);
lua_getfield(L, index, "x");
p.X = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, index, "y");
p.Y = lua_tonumber(L, -1);
lua_pop(L, 1);
return p;
return v2s16::from(read_v2f(L, index));
}
void push_v2s16(lua_State *L, v2s16 p)
{
lua_createtable(L, 0, 2);
lua_pushinteger(L, p.X);
lua_setfield(L, -2, "x");
lua_pushinteger(L, p.Y);
lua_setfield(L, -2, "y");
push_v2s32(L, v2s32::from(p));
}
void push_v2s32(lua_State *L, v2s32 p)
@ -127,15 +134,7 @@ void push_v2u32(lua_State *L, v2u32 p)
v2s32 read_v2s32(lua_State *L, int index)
{
v2s32 p;
CHECK_POS_TAB(index);
lua_getfield(L, index, "x");
p.X = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, index, "y");
p.Y = lua_tonumber(L, -1);
lua_pop(L, 1);
return p;
return v2s32::from(read_v2f(L, index));
}
v2f read_v2f(lua_State *L, int index)
@ -143,9 +142,11 @@ v2f read_v2f(lua_State *L, int index)
v2f p;
CHECK_POS_TAB(index);
lua_getfield(L, index, "x");
CHECK_POS_COORD2(-1, "x");
p.X = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, index, "y");
CHECK_POS_COORD2(-1, "y");
p.Y = lua_tonumber(L, -1);
lua_pop(L, 1);
return p;
@ -170,30 +171,20 @@ v2f check_v2f(lua_State *L, int index)
v3f read_v3f(lua_State *L, int index)
{
read_v3_aux(L, index);
float x = lua_tonumber(L, -3);
float y = lua_tonumber(L, -2);
float z = lua_tonumber(L, -1);
lua_pop(L, 3);
return v3f(x, y, z);
return v3f::from(read_v3d(L, index));
}
v3f check_v3f(lua_State *L, int index)
{
read_v3_aux(L, index);
CHECK_POS_COORD(-3, "x");
CHECK_POS_COORD(-2, "y");
CHECK_POS_COORD(-1, "z");
float x = lua_tonumber(L, -3);
float y = lua_tonumber(L, -2);
float z = lua_tonumber(L, -1);
lua_pop(L, 3);
return v3f(x, y, z);
return v3f::from(check_v3d(L, index));
}
v3d read_v3d(lua_State *L, int index)
{
read_v3_aux(L, index);
CHECK_POS_COORD2(-3, "x");
CHECK_POS_COORD2(-2, "y");
CHECK_POS_COORD2(-1, "z");
double x = lua_tonumber(L, -3);
double y = lua_tonumber(L, -2);
double z = lua_tonumber(L, -1);
@ -211,6 +202,9 @@ v3d check_v3d(lua_State *L, int index)
double y = lua_tonumber(L, -2);
double z = lua_tonumber(L, -1);
lua_pop(L, 3);
CHECK_FLOAT(x, "x");
CHECK_FLOAT(y, "y");
CHECK_FLOAT(z, "z");
return v3d(x, y, z);
}
@ -286,18 +280,23 @@ video::SColor read_ARGB8(lua_State *L, int index)
return std::fmax(0.0, std::fmin(255.0, c));
};
// FIXME: maybe we should have strict type checks here. compare to is_color_table()
video::SColor color(0);
CHECK_TYPE(index, "ARGB color", LUA_TTABLE);
lua_getfield(L, index, "a");
color.setAlpha(lua_isnumber(L, -1) ? clamp_col(lua_tonumber(L, -1)) : 0xFF);
lua_pop(L, 1);
lua_getfield(L, index, "r");
CHECK_NOT_NIL(-1, "color component R");
color.setRed(clamp_col(lua_tonumber(L, -1)));
lua_pop(L, 1);
lua_getfield(L, index, "g");
CHECK_NOT_NIL(-1, "color component G");
color.setGreen(clamp_col(lua_tonumber(L, -1)));
lua_pop(L, 1);
lua_getfield(L, index, "b");
CHECK_NOT_NIL(-1, "color component B");
color.setBlue(clamp_col(lua_tonumber(L, -1)));
lua_pop(L, 1);
return color;

View file

@ -74,13 +74,23 @@ v2f check_v2f(lua_State *L, int index);
v3f check_v3f(lua_State *L, int index);
v3s16 check_v3s16(lua_State *L, int index);
// TODO: some day we should figure out the type-checking situation so it's done
// everywhere. (right now {x=true, y=false} as v2f is {0,0} with no warning)
/// @warning relaxed type-checking, prefer `check_v3f`.
v3f read_v3f(lua_State *L, int index);
/// @warning relaxed type-checking, prefer `check_v2f`.
v2f read_v2f(lua_State *L, int index);
/// @warning relaxed type-checking
v2s16 read_v2s16(lua_State *L, int index);
/// @warning relaxed type-checking
v2s32 read_v2s32(lua_State *L, int index);
/// @warning relaxed type-checking, prefer `check_v3s16`.
v3s16 read_v3s16(lua_State *L, int index);
video::SColor read_ARGB8(lua_State *L, int index);
bool read_color(lua_State *L, int index, video::SColor *color);
bool is_color_table (lua_State *L, int index);
bool is_color_table(lua_State *L, int index);
/**
* Read a floating-point axis-aligned box from Lua.
@ -95,7 +105,6 @@ bool is_color_table (lua_State *L, int index);
*/
aabb3f read_aabb3f(lua_State *L, int index, f32 scale);
v3s16 read_v3s16(lua_State *L, int index);
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);

View file

@ -38,7 +38,6 @@ public:
static struct EnumString es_ContentParamType[];
static struct EnumString es_ContentParamType2[];
static struct EnumString es_LiquidType[];
static struct EnumString es_LiquidMoveType[];
static struct EnumString es_NodeBoxType[];
static struct EnumString es_TextureAlphaMode[];
};

View file

@ -266,15 +266,22 @@ int ModApiEnv::l_bulk_swap_node(lua_State *L)
// get_node_raw(x, y, z) -> content, param1, param2, pos_ok
int ModApiEnv::l_get_node_raw(lua_State *L)
{
GET_ENV_PTR;
GET_PLAIN_ENV_PTR;
// pos
// mirrors implementation of read_v3s16 (with the exact same rounding)
v3s16 pos;
// mirrors the implementation of read_v3s16 (with the exact same rounding)
{
if (lua_isnoneornil(L, 1))
log_deprecated(L, "X position is nil", 1, true);
if (lua_isnoneornil(L, 2))
log_deprecated(L, "Y position is nil", 1, true);
if (lua_isnoneornil(L, 3))
log_deprecated(L, "Z position is nil", 1, true);
double x = lua_tonumber(L, 1);
double y = lua_tonumber(L, 2);
double z = lua_tonumber(L, 3);
v3s16 pos = doubleToInt(v3d(x, y, z), 1.0);
// Do it
pos = doubleToInt(v3d(x, y, z), 1.0);
}
bool pos_ok;
MapNode n = env->getMap().getNode(pos, &pos_ok);
// Return node and pos_ok

Some files were not shown because too many files have changed in this diff Show more