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: | run: |
./util/test_multiplayer.sh ./util/test_multiplayer.sh
# Build with prometheus-cpp (server-only) # Build with prometheus-cpp (server-only), also runs on ARM64
clang_11_prometheus: clang_prometheus_arm:
name: "clang_11 (PROMETHEUS=1)" name: "clang (with Prometheus, ARM64)"
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps clang-11 install_linux_deps --headless clang libluajit-5.1-dev
- name: Build prometheus-cpp - name: Build prometheus-cpp
run: ./util/ci/build_prometheus_cpp.sh run: ./util/ci/build_prometheus_cpp.sh
@ -150,8 +150,8 @@ jobs:
run: | run: |
./util/ci/build.sh ./util/ci/build.sh
env: env:
CC: clang-11 CC: clang
CXX: clang++-11 CXX: clang++
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0" CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0"
- name: Test - 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 # You should not need to edit these manually, use util/bump_version.sh
set(VERSION_MAJOR 5) set(VERSION_MAJOR 5)
set(VERSION_MINOR 12) set(VERSION_MINOR 13)
set(VERSION_PATCH 0) set(VERSION_PATCH 0)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") 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. // 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("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 project.ext.set("versionPatch", 0) // Version Patch
// ^ keep in sync with cmake // ^ keep in sync with cmake

View file

@ -102,7 +102,7 @@ end
local translation_file_header = [[ local translation_file_header = [[
// This file is automatically generated // This file is automatically generated
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files // 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() {]] fake_function() {]]
@ -110,15 +110,15 @@ local function create_translation_file(settings)
local result = { translation_file_header } local result = { translation_file_header }
for _, entry in ipairs(settings) do for _, entry in ipairs(settings) do
if entry.type == "category" then 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 else
if entry.readable_name then 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 end
if entry.comment ~= "" then if entry.comment ~= "" then
local comment_escaped = entry.comment:gsub("\n", "\\n") local comment_escaped = entry.comment:gsub("\n", "\\n")
comment_escaped = comment_escaped:gsub("\"", "\\\"") comment_escaped = comment_escaped:gsub("\"", "\\\"")
insert(result, "\tgettext(\"" .. comment_escaped .. "\");") insert(result, "\t/* xgettext:no-c-format */ gettext(\"" .. comment_escaped .. "\");")
end end
end end
end end

View file

@ -19,12 +19,14 @@ function meta:__newindex(name, value)
return return
end end
local info = getinfo(2, "Sl") local info = getinfo(2, "Sl")
local desc = ("%s:%d"):format(info.short_src, info.currentline) if info ~= nil then
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) local desc = ("%s:%d"):format(info.short_src, info.currentline)
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.") if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
:format(name, desc)) core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
warned[warn_key] = true :format(name, desc))
warned[warn_key] = true
end
end end
declared[name] = true declared[name] = true
end end
@ -35,6 +37,9 @@ function meta:__index(name)
return return
end end
local info = getinfo(2, "Sl") 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) local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not warned[warn_key] and info.what ~= "C" then if not warned[warn_key] and info.what ~= "C" then
core.log("warning", ("Undeclared global variable %q accessed at %s:%s") 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 if (active_toplevel_ui_elements > 1) then
core.log("warning", "more than one active ui ".. core.log("warning", "more than one active ui "..
"element, self most likely isn't intended") "element, this most likely isn't intended")
end end
if (active_toplevel_ui_elements == 0) then if (active_toplevel_ui_elements == 0) then
@ -166,6 +166,10 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
core.button_handler = function(fields) 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 if fields["btn_reconnect_yes"] then
gamedata.reconnect_requested = false gamedata.reconnect_requested = false
gamedata.errormessage = nil gamedata.errormessage = nil

View file

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

View file

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

View file

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

View file

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

View file

@ -112,8 +112,12 @@ local function init_globals()
tv_main:show() tv_main:show()
ui.update() ui.update()
check_reinstall_mtg() -- synchronous, chain parents to only show one at a time
migrate_keybindings() 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() check_new_version()
end 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 built-in knockback and related functions entirely
* remove `safe` parameter from `core.serialize`, always enforce `safe = true`. * remove `safe` parameter from `core.serialize`, always enforce `safe = true`.
possibly error when `loadstring` calls are encountered in `core.deserialize`. 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`), **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). 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. `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/> * More information at <http://www.luanti.org/>
* Luanti Documentation: <https://docs.luanti.org/> * Additional documentation: <https://docs.luanti.org/>
Introduction 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). 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. `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/> * More information at <http://www.luanti.org/>
* Luanti Documentation: <https://docs.luanti.org/> * Additional documentation: <https://docs.luanti.org/>
* (Unofficial) Minetest Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/> * (Unofficial) Luanti Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
* Modding tools: <https://github.com/luanti-org/modtools> * Modding tools: <https://github.com/luanti-org/modtools>
Introduction 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). 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) You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator)
to check whether a model is a valid glTF file. 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: The final fractal value noise variation is created as follows:
```
noise = offset + scale * (octave1 + noise = offset + scale * (octave1 +
octave2 * persistence + octave2 * persistence +
octave3 * persistence ^ 2 + octave3 * persistence ^ 2 +
octave4 * persistence ^ 3 + octave4 * persistence ^ 3 +
...) ...)
```
Noise Parameters Noise Parameters
---------------- ----------------
@ -4699,11 +4707,13 @@ with restraint.
The absolute value of each octave's noise variation is used when combining the The absolute value of each octave's noise variation is used when combining the
octaves. The final value noise variation is created as follows: octaves. The final value noise variation is created as follows:
```
noise = offset + scale * (abs(octave1) + noise = offset + scale * (abs(octave1) +
abs(octave2) * persistence + abs(octave2) * persistence +
abs(octave3) * persistence ^ 2 + abs(octave3) * persistence ^ 2 +
abs(octave4) * persistence ^ 3 + abs(octave4) * persistence ^ 3 +
...) ...)
```
### Format example ### 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, 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. 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 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 Note that `VoxelManip:read_from_map()` returns two position vectors. The region
formed by these positions indicate the minimum and maximum (respectively) 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 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()`, 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 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 Nodes in a VoxelManip object may also be read in bulk to a flat array table
using: using:
* `VoxelManip:get_data()` for node content (in Content ID form, see section * `VoxelManip:get_data()` for node content (in Content ID form, see section
[Content IDs]), [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. * `VoxelManip:get_param2_data()` for the node type-dependent "param2" values.
See section [Flat array format] for more details. 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 Once the bulk data has been edited to your liking, the internal VoxelManip
state can be set using: state can be set using:
* `VoxelManip:set_data()` for node content (in Content ID form, see section * `VoxelManip:set_data()` or
[Content IDs]), * `VoxelManip:set_light_data()` or
* `VoxelManip:set_light_data()` for node light levels, and * `VoxelManip:set_param2_data()`
* `VoxelManip:set_param2_data()` for the node type-dependent `param2` values.
The parameter to each of the above three functions can use any table at all in 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 the same flat array format as produced by `get_data()` etc. and is not required
to be a table retrieved from `get_data()`. to be a table retrieved from `get_data()`.
Once the internal VoxelManip state has been modified to your liking, the 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 ### Flat array format
@ -5173,15 +5183,22 @@ inside the VoxelManip.
Methods 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`. 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 * Note that calling this multiple times will *add* to the area loaded in the
VoxelManip, and not reset it. 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 * `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to
the map. the map.
* **important**: data must be set using `VoxelManip:set_data()` before * **important**: you should call `set_data()` before this, or nothing will change.
calling this.
* if `light` is true, then lighting is automatically recalculated. * if `light` is true, then lighting is automatically recalculated.
The default value is true. The default value is true.
If `light` is false, no light calculations happen, and you should correct 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. 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! * 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. * `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` `VoxelArea`
----------- -----------
@ -6562,13 +6588,10 @@ Environment access
* The actual seed used is the noiseparams seed plus the world seed. * The actual seed used is the noiseparams seed plus the world seed.
* `core.get_value_noise(seeddiff, octaves, persistence, spread)` * `core.get_value_noise(seeddiff, octaves, persistence, spread)`
* Deprecated: use `core.get_value_noise(noiseparams)` instead. * Deprecated: use `core.get_value_noise(noiseparams)` instead.
* Return world-specific value noise
* `core.get_perlin(noiseparams)` * `core.get_perlin(noiseparams)`
* Deprecated: use `core.get_value_noise(noiseparams)` instead. * Deprecated: renamed to `core.get_value_noise` in version 5.12.0.
* Return world-specific value noise (was not Perlin noise)
* `core.get_perlin(seeddiff, octaves, persistence, spread)` * `core.get_perlin(seeddiff, octaves, persistence, spread)`
* Deprecated: use `core.get_value_noise(noiseparams)` instead. * Deprecated: renamed to `core.get_value_noise` in version 5.12.0.
* Return world-specific value noise (was not Perlin noise)
* `core.get_voxel_manip([pos1, pos2])` * `core.get_voxel_manip([pos1, pos2])`
* Return voxel manipulator object. * Return voxel manipulator object.
* Loads the manipulator from the map if positions are passed. * 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 * `get_state()`: return generator state encoded in string
* `set_state(state_string)`: restore generator state from encoded 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` `PlayerMetaRef`
--------------- ---------------
@ -9184,14 +9135,17 @@ end
The map is loaded as the ray advances. If the map is modified after the 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. `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 It can be created via `Raycast(pos1, pos2, objects, liquids, pointabilities)`
`core.raycast(pos1, pos2, objects, liquids)` where: or `core.raycast(pos1, pos2, objects, liquids, pointabilities)` where:
* `pos1`: start of the ray * `pos1`: start of the ray
* `pos2`: end 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 * `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 ### Limitations
@ -9307,6 +9261,81 @@ to restrictions of JSON.
* All methods in MetaDataRef * 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 = "", mesh = "",
-- File name of mesh when using "mesh" drawtype -- File name of mesh when using "mesh" drawtype
-- The center of the node is the model origin. -- The center of the node is the model origin.
-- For legacy reasons, models in OBJ format use a scale of 1 node = 1 unit; -- For legacy reasons, this uses a different scale depending on the mesh:
-- all other model file formats use a scale of 1 node = 10 units, -- 1. For glTF models: 10 units = 1 node (consistent with the scale for entities).
-- consistent with the scale used 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 = { selection_box = {
-- see [Node boxes] for possibilities -- 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 Introduction
@ -23,8 +23,8 @@ Callbacks
* `core.button_handler(fields)`: called when a button is pressed. * `core.button_handler(fields)`: called when a button is pressed.
* `fields` = `{name1 = value1, name2 = value2, ...}` * `fields` = `{name1 = value1, name2 = value2, ...}`
* `core.event_handler(event)` * `core.event_handler(event)`
* `event`: `"MenuQuit"`, `"KeyEnter"`, `"ExitButton"`, `"EditBoxEnter"` or * `event`: `"MenuQuit"` (derived from `quit`) or `"FullscreenChange"`
`"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 * `core.on_before_close()`: called before the menu is closed, either to exit or
to join a game to join a game

View file

@ -12,4 +12,10 @@ Jordach (CC BY-SA 3.0):
Zeg9 (CC BY-SA 3.0): Zeg9 (CC BY-SA 3.0):
testentities_lava_flan.x testentities_lava_flan.x
testentities_lava_flan.png 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, 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 -- Advanced visual tests
-- An entity for testing animated and yaw-modulated sprites -- An entity for testing animated and yaw-modulated sprites

View file

@ -67,18 +67,6 @@ local function test_dynamic_media(cb, player)
end end
unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true}) 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 function test_clear_meta(_, pos)
local ref = core.get_meta(pos) local ref = core.get_meta(pos)

View file

@ -13,8 +13,8 @@ namespace scene
//! Interface for an animated mesh. //! Interface for an animated mesh.
/** There are already simple implementations of this interface available so /** 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 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, You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc.
irr::scene::SMeshBuffer etc. */ */
class IAnimatedMesh : public IMesh class IAnimatedMesh : public IMesh
{ {
public: public:
@ -34,22 +34,8 @@ public:
scene node the mesh is instantiated in.*/ scene node the mesh is instantiated in.*/
virtual void setAnimationSpeed(f32 fps) = 0; virtual void setAnimationSpeed(f32 fps) = 0;
//! Returns the IMesh interface for a frame. //! Returns the type of the animated mesh. Useful for safe downcasts.
/** \param frame: Frame number, >= 0, <= getMaxFrameNumber() E_ANIMATED_MESH_TYPE getMeshType() const = 0;
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;
}
}; };
} // end namespace scene } // end namespace scene

View file

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

View file

@ -11,85 +11,41 @@ namespace irr
namespace scene 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. //! Interface for bones used for skeletal animation.
/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */ /** Used with SkinnedMesh and IAnimatedMeshSceneNode. */
class IBoneSceneNode : public ISceneNode class IBoneSceneNode : public ISceneNode
{ {
public: public:
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) : IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {} 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 //! Returns the index of the bone
virtual u32 getBoneIndex() const = 0; u32 getBoneIndex() const
{
return BoneIndex;
}
//! Sets the animation mode of the bone. //! returns the axis aligned bounding box of this node
/** \return True if successful. (Unused) */ const core::aabbox3d<f32> &getBoundingBox() const override
virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0; {
return Box;
}
//! Gets the current animation mode of the bone const u32 BoneIndex;
virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0;
//! Get the axis aligned bounding box of this node // Bogus box; bone scene nodes are not rendered anyways.
const core::aabbox3d<f32> &getBoundingBox() const override = 0; static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};
//! Returns the relative transformation of the scene node.
// virtual core::matrix4 getRelativeTransformation() const = 0;
//! The animation method.
void OnAnimate(u32 timeMs) override = 0;
//! The render method. //! The render method.
/** Does nothing as bones are not visible. */ /** Does nothing as bones are not visible. */
void render() override {} 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 } // end namespace scene

View file

@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE
//! Unknown animated mesh type. //! Unknown animated mesh type.
EAMT_UNKNOWN = 0, 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 //! generic skinned mesh
EAMT_SKINNED, EAMT_SKINNED,
@ -119,9 +87,7 @@ public:
virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0; virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
//! Returns the type of the meshes. //! Returns the type of the meshes.
/** This is useful for making a safe downcast. For example, /** This is useful for making a safe downcast.
if getMeshType() returns EAMT_MD2 it's safe to cast the
IMesh to IAnimatedMeshMD2.
Note: It's no longer just about animated meshes, that name has just historical reasons. Note: It's no longer just about animated meshes, that name has just historical reasons.
\returns Type of the mesh */ \returns Type of the mesh */
virtual E_ANIMATED_MESH_TYPE getMeshType() const virtual E_ANIMATED_MESH_TYPE getMeshType() const

View file

@ -66,26 +66,6 @@ public:
IReferenceCounted::drop() for more information. */ IReferenceCounted::drop() for more information. */
virtual SMesh *createMeshCopy(IMesh *mesh) const = 0; 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 //! Apply a manipulator on the Meshbuffer
/** \param func A functor defining the mesh manipulation. /** \param func A functor defining the mesh manipulation.
\param buffer The Meshbuffer to apply the manipulator to. \param buffer The Meshbuffer to apply the manipulator to.

View file

@ -32,7 +32,7 @@ public:
//! Get the currently defined mesh for display. //! Get the currently defined mesh for display.
/** \return Pointer to mesh which is displayed by this node. */ /** \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. //! 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 /** 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. */ \param timeMs Current time in milliseconds. */
virtual void OnAnimate(u32 timeMs) virtual void OnAnimate(u32 timeMs)
{ {
if (IsVisible) { if (!IsVisible && Children.empty())
// update absolute position return;
updateAbsolutePosition();
// perform the post render process on all children updateAbsolutePosition();
for (auto *child : Children)
ISceneNodeList::iterator it = Children.begin(); child->OnAnimate(timeMs);
for (; it != Children.end(); ++it)
(*it)->OnAnimate(timeMs);
}
} }
//! Renders the node. //! Renders the node.

View file

@ -172,7 +172,7 @@ public:
\return Pointer to the texture, or 0 if the texture \return Pointer to the texture, or 0 if the texture
could not be loaded. This pointer should not be dropped. See could not be loaded. This pointer should not be dropped. See
IReferenceCounted::drop() for more information. */ 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. //! Get access to a named texture.
/** Loads the texture from disk if it is not /** Loads the texture from disk if it is not
@ -184,7 +184,7 @@ public:
\return Pointer to the texture, or 0 if the texture \return Pointer to the texture, or 0 if the texture
could not be loaded. This pointer should not be dropped. See could not be loaded. This pointer should not be dropped. See
IReferenceCounted::drop() for more information. */ 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 //! Returns amount of textures currently loaded
/** \return 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 #pragma once
#include <vector> #include <vector>
#include "IMesh.h" #include "IAnimatedMesh.h"
#include "IMeshBuffer.h" #include "IMeshBuffer.h"
#include "aabbox3d.h" #include "aabbox3d.h"
@ -14,7 +14,7 @@ namespace irr
namespace scene namespace scene
{ {
//! Simple implementation of the IMesh interface. //! Simple implementation of the IMesh interface.
struct SMesh final : public IMesh struct SMesh final : public IAnimatedMesh
{ {
//! constructor //! constructor
SMesh() {} SMesh() {}
@ -134,6 +134,15 @@ struct SMesh final : public IMesh
//! The bounding box of this mesh //! The bounding box of this mesh
core::aabbox3d<f32> BoundingBox{{0, 0, 0}}; 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 } // end namespace scene

View file

@ -8,11 +8,18 @@
#include "ISceneManager.h" #include "ISceneManager.h"
#include "SMeshBuffer.h" #include "SMeshBuffer.h"
#include "SSkinMeshBuffer.h" #include "SSkinMeshBuffer.h"
#include "aabbox3d.h"
#include "irrMath.h"
#include "irrTypes.h"
#include "matrix4.h"
#include "quaternion.h" #include "quaternion.h"
#include "vector3d.h" #include "vector3d.h"
#include "Transform.h"
#include <optional> #include <optional>
#include <string> #include <string>
#include <variant>
#include <vector>
namespace irr namespace irr
{ {
@ -26,12 +33,20 @@ class ISceneManager;
class SkinnedMesh : public IAnimatedMesh class SkinnedMesh : public IAnimatedMesh
{ {
public: public:
enum class SourceFormat {
B3D,
X,
GLTF,
OTHER,
};
//! constructor //! constructor
SkinnedMesh() : SkinnedMesh(SourceFormat src_format) :
EndFrame(0.f), FramesPerSecond(25.f), EndFrame(0.f), FramesPerSecond(25.f),
LastAnimatedFrame(-1), SkinnedLastFrame(false),
HasAnimation(false), PreparedForSkinning(false), HasAnimation(false), PreparedForSkinning(false),
AnimateNormals(true), HardwareSkinning(false) AnimateNormals(true),
SrcFormat(src_format)
{ {
SkinningBuffers = &LocalBuffers; SkinningBuffers = &LocalBuffers;
} }
@ -39,6 +54,10 @@ public:
//! destructor //! destructor
virtual ~SkinnedMesh(); 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. //! If the duration is 0, it is a static (=non animated) mesh.
f32 getMaxFrameNumber() const override; f32 getMaxFrameNumber() const override;
@ -51,14 +70,12 @@ public:
The actual speed is set in the scene node the mesh is instantiated in.*/ The actual speed is set in the scene node the mesh is instantiated in.*/
void setAnimationSpeed(f32 fps) override; void setAnimationSpeed(f32 fps) override;
//! returns the animated mesh for the given frame //! Turns the given array of local matrices into an array of global matrices
IMesh *getMesh(f32) override; //! by multiplying with respective parent matrices.
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const;
//! Animates joints based on frame input //! Performs a software skin on this mesh based on the given joint matrices
void animateMesh(f32 frame); void skinMesh(const std::vector<core::matrix4> &animated_transforms);
//! Performs a software skin on this mesh based of joint positions
void skinMesh();
//! returns amount of mesh buffers. //! returns amount of mesh buffers.
u32 getMeshBufferCount() const override; u32 getMeshBufferCount() const override;
@ -76,14 +93,15 @@ public:
void setTextureSlot(u32 meshbufNr, u32 textureSlot); 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 { 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 { void setBoundingBox(const core::aabbox3df &box) override {
BoundingBox = box; StaticPoseBox = box;
} }
//! set the hardware mapping hint, for driver //! set the hardware mapping hint, for driver
@ -127,28 +145,15 @@ public:
return !HasAnimation; 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 //! Refreshes vertex data cached in joints such as positions and normals
void refreshJointCache(); void refreshJointCache();
//! Moves the mesh into static position. //! Moves the mesh into static position.
void resetAnimation(); 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 //! Creates an array of joints from this mesh as children of node
void addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes, std::vector<IBoneSceneNode *> addJoints(
IAnimatedMeshSceneNode *node, IAnimatedMeshSceneNode *node, ISceneManager *smgr);
ISceneManager *smgr);
//! A vertex weight //! A vertex weight
struct SWeight struct SWeight
@ -223,7 +228,7 @@ public:
static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) { static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) {
core::quaternion result; core::quaternion result;
result.slerp(from, to, time, 0.001f); result.slerp(from, to, time);
return result; return result;
} }
@ -275,15 +280,14 @@ public:
}); });
} }
void updateTransform(f32 frame, void updateTransform(f32 frame, core::Transform &transform) const
core::vector3df &t, core::quaternion &r, core::vector3df &s) const
{ {
if (auto pos = position.get(frame)) if (auto pos = position.get(frame))
t = *pos; transform.translation = *pos;
if (auto rot = rotation.get(frame)) if (auto rot = rotation.get(frame))
r = *rot; transform.rotation = *rot;
if (auto scl = scale.get(frame)) if (auto scl = scale.get(frame))
s = *scl; transform.scale = *scl;
} }
void cleanup() { void cleanup() {
@ -296,16 +300,34 @@ public:
//! Joints //! Joints
struct SJoint struct SJoint
{ {
SJoint() : GlobalSkinningSpace(false) {} SJoint() {}
//! The name of this joint //! The name of this joint
std::optional<std::string> Name; std::optional<std::string> Name;
//! Local matrix of this joint //! Local transformation to be set by loaders. Mutated by animation.
core::matrix4 LocalMatrix; 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 //! List of attached meshes
std::vector<u32> AttachedMeshes; std::vector<u32> AttachedMeshes;
@ -316,42 +338,49 @@ public:
//! Skin weights //! Skin weights
std::vector<SWeight> 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 //! 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 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 // The .x and .gltf formats pre-calculate this
std::optional<core::matrix4> GlobalInversedMatrix; 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 { const std::vector<SJoint *> &getAllJoints() const {
return AllJoints; return AllJoints;
} }
protected: protected:
void checkForAnimation(); bool checkForAnimation() const;
void topoSortJoints();
void prepareForSkinning();
void calculateStaticBoundingBox();
void calculateJointBoundingBoxes();
void calculateBufferBoundingBoxes();
void normalizeWeights(); 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, void calculateTangents(core::vector3df &normal,
core::vector3df &tangent, core::vector3df &binormal, core::vector3df &tangent, core::vector3df &binormal,
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, 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 //! Mapping from meshbuffer number to bindable texture slot
std::vector<u32> TextureSlots; std::vector<u32> TextureSlots;
//! Joints, topologically sorted (parents come before their children).
std::vector<SJoint *> AllJoints; std::vector<SJoint *> AllJoints;
std::vector<SJoint *> RootJoints;
// bool can't be used here because std::vector<bool> // bool can't be used here because std::vector<bool>
// doesn't allow taking a reference to individual elements. // doesn't allow taking a reference to individual elements.
std::vector<std::vector<char>> Vertices_Moved; 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 EndFrame;
f32 FramesPerSecond; f32 FramesPerSecond;
f32 LastAnimatedFrame;
bool SkinnedLastFrame;
bool HasAnimation; bool HasAnimation;
bool PreparedForSkinning; bool PreparedForSkinning;
bool AnimateNormals; bool AnimateNormals;
bool HardwareSkinning;
SourceFormat SrcFormat;
}; };
// Interface for mesh loaders // Interface for mesh loaders
class SkinnedMeshBuilder : public SkinnedMesh { class SkinnedMeshBuilder : public SkinnedMesh {
public: public:
SkinnedMeshBuilder() : SkinnedMesh() {} SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {}
//! loaders should call this after populating the mesh //! loaders should call this after populating the mesh
// returns *this, so do not try to drop the mesh builder instance // 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. linear interpolation.
*/ */
quaternion &slerp(quaternion q1, quaternion q2, 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. //! Set this quaternion to represent a rotation from angle and axis.
/** Axis must be unit length. /** Axis must be unit length.

View file

@ -3,9 +3,13 @@
// For conditions of distribution and use, see copyright notice in irrlicht.h // For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CAnimatedMeshSceneNode.h" #include "CAnimatedMeshSceneNode.h"
#include "CBoneSceneNode.h"
#include "IVideoDriver.h" #include "IVideoDriver.h"
#include "ISceneManager.h" #include "ISceneManager.h"
#include "S3DVertex.h" #include "S3DVertex.h"
#include "Transform.h"
#include "irrTypes.h"
#include "matrix4.h"
#include "os.h" #include "os.h"
#include "SkinnedMesh.h" #include "SkinnedMesh.h"
#include "IDummyTransformationSceneNode.h" #include "IDummyTransformationSceneNode.h"
@ -17,6 +21,9 @@
#include "IFileSystem.h" #include "IFileSystem.h"
#include "quaternion.h" #include "quaternion.h"
#include <algorithm> #include <algorithm>
#include <cstddef>
#include <optional>
#include <cassert>
namespace irr namespace irr
{ {
@ -30,13 +37,13 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
const core::vector3df &rotation, const core::vector3df &rotation,
const core::vector3df &scale) : const core::vector3df &scale) :
IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale), IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale),
Mesh(0), Mesh(nullptr),
StartFrame(0), EndFrame(0), FramesPerSecond(0.025f), StartFrame(0), EndFrame(0), FramesPerSecond(0.025f),
CurrentFrameNr(0.f), LastTimeMs(0), CurrentFrameNr(0.f), LastTimeMs(0),
TransitionTime(0), Transiting(0.f), TransitingBlend(0.f), TransitionTime(0), Transiting(0.f), TransitingBlend(0.f),
JointMode(EJUOR_NONE), JointsUsed(false), JointsUsed(false),
Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false), Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false),
LoopCallBack(0), PassCount(0) PassCount(0)
{ {
setMesh(mesh); setMesh(mesh);
} }
@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
//! destructor //! destructor
CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode() CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode()
{ {
if (LoopCallBack)
LoopCallBack->drop();
if (Mesh) if (Mesh)
Mesh->drop(); Mesh->drop();
} }
@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
if (FramesPerSecond > 0.f) { // forwards... if (FramesPerSecond > 0.f) { // forwards...
if (CurrentFrameNr > EndFrame) if (CurrentFrameNr > EndFrame)
CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame); CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame);
} else // backwards... } else { // backwards...
{
if (CurrentFrameNr < StartFrame) if (CurrentFrameNr < StartFrame)
CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame); CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame);
} }
@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
CurrentFrameNr += timeMs * FramesPerSecond; CurrentFrameNr += timeMs * FramesPerSecond;
if (FramesPerSecond > 0.f) { // forwards... if (FramesPerSecond > 0.f) { // forwards...
if (CurrentFrameNr > EndFrame) { CurrentFrameNr = std::min(CurrentFrameNr, EndFrame);
CurrentFrameNr = EndFrame; } else { // backwards...
if (LoopCallBack) CurrentFrameNr = std::max(CurrentFrameNr, StartFrame);
LoopCallBack->OnAnimationEnd(this);
}
} else // backwards...
{
if (CurrentFrameNr < StartFrame) {
CurrentFrameNr = StartFrame;
if (LoopCallBack)
LoopCallBack->OnAnimationEnd(this);
}
} }
} }
} }
@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode()
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame() IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
{ {
if (Mesh->getMeshType() != EAMT_SKINNED) { if (Mesh->getMeshType() != EAMT_SKINNED) {
return Mesh->getMesh(getFrameNr()); return Mesh;
} else {
// 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);
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();
}
return skinnedMesh;
} }
// 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.
auto *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
// Matrices have already been calculated in OnAnimate
skinnedMesh->skinMesh(PerJoint.GlobalMatrices);
return skinnedMesh;
} }
//! OnAnimate() is called just before rendering the whole scene. //! OnAnimate() is called just before rendering the whole scene.
@ -201,7 +176,28 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs)
buildFrameNr(timeMs - LastTimeMs); buildFrameNr(timeMs - LastTimeMs);
LastTimeMs = timeMs; 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); 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. //! renders the node.
@ -218,15 +214,7 @@ void CAnimatedMeshSceneNode::render()
++PassCount; ++PassCount;
scene::IMesh *m = getMeshForCurrentFrame(); scene::IMesh *m = getMeshForCurrentFrame();
assert(m);
if (m) {
Box = m->getBoundingBox();
} else {
#ifdef _DEBUG
os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING);
#endif
return;
}
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render()
if (DebugDataVisible & scene::EDS_SKELETON) { if (DebugDataVisible & scene::EDS_SKELETON) {
if (Mesh->getMeshType() == EAMT_SKINNED) { if (Mesh->getMeshType() == EAMT_SKINNED) {
// draw skeleton // draw skeleton
const auto &joints = (static_cast<SkinnedMesh *>(Mesh))->getAllJoints();
for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) { for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) {
for (const auto *childJoint : joint->Children) { const auto translation = PerJoint.GlobalMatrices[i].getTranslation();
driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(), if (auto pjid = joints[i]->ParentJointID) {
childJoint->GlobalAnimatedMatrix.getTranslation(), const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation();
driver->draw3DLine(parent_translation, translation,
video::SColor(255, 51, 66, 255)); video::SColor(255, 51, 66, 255));
} }
} }
@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName)
return 0; 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); os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING);
return 0; return 0;
} }
return JointChildSceneNodes[*number]; return PerJoint.SceneNodes[*number];
} }
//! Returns a pointer to a child node, which has the same transformation as //! Returns a pointer to a child node, which has the same transformation as
@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID)
checkJoints(); checkJoints();
if (JointChildSceneNodes.size() <= jointID) { if (PerJoint.SceneNodes.size() <= jointID) {
os::Printer::log("Joint not loaded into node", ELL_WARNING); os::Printer::log("Joint not loaded into node", ELL_WARNING);
return 0; return 0;
} }
return JointChildSceneNodes[jointID]; return PerJoint.SceneNodes[jointID];
} }
//! Gets joint count. //! Gets joint count.
@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child)
{ {
if (ISceneNode::removeChild(child)) { if (ISceneNode::removeChild(child)) {
if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) { for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
if (JointChildSceneNodes[i] == child) { if (PerJoint.SceneNodes[i] == child) {
JointChildSceneNodes[i] = 0; // remove link to child PerJoint.SceneNodes[i] = 0; // remove link to child
break; break;
} }
} }
@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const
return Looping; 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. //! 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) void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly)
{ {
@ -525,18 +498,15 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
// get materials and bounding box // get materials and bounding box
Box = Mesh->getBoundingBox(); Box = Mesh->getBoundingBox();
IMesh *m = Mesh->getMesh(0); Materials.clear();
if (m) { Materials.reallocate(Mesh->getMeshBufferCount());
Materials.clear();
Materials.reallocate(m->getMeshBufferCount());
for (u32 i = 0; i < m->getMeshBufferCount(); ++i) { for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) {
IMeshBuffer *mb = m->getMeshBuffer(i); IMeshBuffer *mb = Mesh->getMeshBuffer(i);
if (mb) if (mb)
Materials.push_back(mb->getMaterial()); Materials.push_back(mb->getMaterial());
else else
Materials.push_back(video::SMaterial()); Materials.push_back(video::SMaterial());
}
} }
// clean up joint nodes // clean up joint nodes
@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition()
IAnimatedMeshSceneNode::updateAbsolutePosition(); IAnimatedMeshSceneNode::updateAbsolutePosition();
} }
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set) //! Sets the transition time in seconds (note: This needs to enable joints)
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)
//! you must call animateJoints(), or the mesh will not animate //! you must call animateJoints(), or the mesh will not animate
void CAnimatedMeshSceneNode::setTransitionTime(f32 time) void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
{ {
@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
if (TransitionTime == ttime) if (TransitionTime == ttime)
return; return;
TransitionTime = ttime; TransitionTime = ttime;
if (ttime != 0)
setJointMode(EJUOR_CONTROL);
else
setJointMode(EJUOR_NONE);
} }
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected) //! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable)
RenderFromIdentity = enable; RenderFromIdentity = enable;
} }
//! updates the joint positions of this mesh void CAnimatedMeshSceneNode::addJoints()
void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions)
{ {
if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) { const auto &joints = static_cast<SkinnedMesh*>(Mesh)->getAllJoints();
checkJoints(); PerJoint.setN(joints.size());
const f32 frame = getFrameNr(); // old? 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>{}));
}
}
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh); void CAnimatedMeshSceneNode::updateJointSceneNodes(
const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms)
skinnedMesh->animateMesh(frame); {
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes); for (size_t i = 0; i < transforms.size(); ++i) {
const auto &transform = transforms[i];
//----------------------------------------- auto *node = static_cast<CBoneSceneNode*>(PerJoint.SceneNodes[i]);
// Transition if (const auto *trs = std::get_if<core::Transform>(&transform)) {
//----------------------------------------- node->setTransform(*trs);
// .x lets animations override matrix transforms entirely.
if (Transiting != 0.f) { node->Matrix = std::nullopt;
// Init additional matrices } else {
if (PretransitingSave.size() < JointChildSceneNodes.size()) { node->Matrix = std::get<core::matrix4>(transform);
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) { //! updates the joint positions of this mesh
//---slow--- void CAnimatedMeshSceneNode::animateJoints()
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) { {
if (JointChildSceneNodes[n]->getParent() == this) { if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option return;
}
checkJoints();
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
if (!skinnedMesh->isStatic())
updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr()));
//-----------------------------------------
// Transition
//-----------------------------------------
if (Transiting != 0.f) {
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() void CAnimatedMeshSceneNode::checkJoints()
{ {
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED) if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
return; return;
if (!JointsUsed) { if (!JointsUsed) {
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i)
removeChild(JointChildSceneNodes[i]); removeChild(PerJoint.SceneNodes[i]);
JointChildSceneNodes.clear(); addJoints();
// Create joints for SkinnedMesh
((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager);
((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes);
JointsUsed = true; 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() void CAnimatedMeshSceneNode::beginTransition()
{ {
if (!JointsUsed) if (!JointsUsed)
return; return;
if (TransitionTime != 0) { 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); Transiting = core::reciprocal((f32)TransitionTime);
} }
TransitingBlend = 0.f; TransitingBlend = 0.f;
} }
/*!
*/
ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager) ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager)
{ {
if (!newParent) if (!newParent)
@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *
newNode->EndFrame = EndFrame; newNode->EndFrame = EndFrame;
newNode->FramesPerSecond = FramesPerSecond; newNode->FramesPerSecond = FramesPerSecond;
newNode->CurrentFrameNr = CurrentFrameNr; newNode->CurrentFrameNr = CurrentFrameNr;
newNode->JointMode = JointMode;
newNode->JointsUsed = JointsUsed; newNode->JointsUsed = JointsUsed;
newNode->TransitionTime = TransitionTime; newNode->TransitionTime = TransitionTime;
newNode->Transiting = Transiting; newNode->Transiting = Transiting;
newNode->TransitingBlend = TransitingBlend; newNode->TransitingBlend = TransitingBlend;
newNode->Looping = Looping; newNode->Looping = Looping;
newNode->ReadOnlyMaterials = ReadOnlyMaterials; newNode->ReadOnlyMaterials = ReadOnlyMaterials;
newNode->LoopCallBack = LoopCallBack;
if (newNode->LoopCallBack)
newNode->LoopCallBack->grab();
newNode->PassCount = PassCount; newNode->PassCount = PassCount;
newNode->JointChildSceneNodes = JointChildSceneNodes; newNode->PerJoint.SceneNodes = PerJoint.SceneNodes;
newNode->PretransitingSave = PretransitingSave; newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves;
newNode->RenderFromIdentity = RenderFromIdentity; newNode->RenderFromIdentity = RenderFromIdentity;
return newNode; return newNode;

View file

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

View file

@ -48,7 +48,7 @@ IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file)
return 0; return 0;
B3DFile = file; 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 ShowWarning = true; // If true a warning is issued if too many textures are used
VerticesStart = 0; 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); os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG);
#endif #endif
f32 position[3], scale[3], rotation[4]; core::Transform transform;
{
f32 t[3], s[3], r[4];
readFloats(position, 3); readFloats(t, 3);
readFloats(scale, 3); readFloats(s, 3);
readFloats(rotation, 4); readFloats(r, 4);
joint->Animatedposition = core::vector3df(position[0], position[1], position[2]); joint->transform = transform = {
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); {t[0], t[1], t[2]},
joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]); {r[1], r[2], r[3], r[0]},
{s[0], s[1], s[2]},
// 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;
if (inJoint) if (inJoint)
joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix; joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix();
else else
joint->GlobalMatrix = joint->LocalMatrix; joint->GlobalMatrix = transform.buildMatrix();
while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats 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 // Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
#include "IBoneSceneNode.h" #include "IBoneSceneNode.h"
#include "Transform.h"
#include "matrix4.h"
#include <optional> #include <optional>
@ -21,49 +23,48 @@ public:
//! constructor //! constructor
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0, s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt); const std::optional<std::string> &boneName = std::nullopt,
const core::Transform &transform = {},
//! Returns the index of the bone const std::optional<core::matrix4> &matrix = std::nullopt) :
u32 getBoneIndex() const override; IBoneSceneNode(parent, mgr, id, boneIndex, boneName),
Matrix(matrix)
//! 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
{ {
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: core::Transform getTransform() const
void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node); {
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}; //! Some file formats alternatively let bones specify a transformation matrix.
//! If this is set, it overrides the TRS properties.
E_BONE_ANIMATION_MODE AnimationMode; std::optional<core::matrix4> Matrix;
E_BONE_SKINNING_SPACE SkinningSpace;
}; };
} // end namespace scene } // end namespace scene

View file

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

View file

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

View file

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

View file

@ -6,7 +6,6 @@
#include "SkinnedMesh.h" #include "SkinnedMesh.h"
#include "SMesh.h" #include "SMesh.h"
#include "CMeshBuffer.h" #include "CMeshBuffer.h"
#include "SAnimatedMesh.h"
#include "os.h" #include "os.h"
#include <cassert> #include <cassert>
@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const
return clone; 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 scene
} // end namespace irr } // end namespace irr

View file

@ -31,15 +31,6 @@ public:
//! Clones a static IMesh into a modifiable SMesh. //! Clones a static IMesh into a modifiable SMesh.
SMesh *createMeshCopy(scene::IMesh *mesh) const override; 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 } // 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) else if (node->getType() == scene::ESNT_MESH)
mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh(); mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh();
else else
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh()->getMesh(0); mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh();
if (!mesh) if (!mesh)
return; return;
} }

View file

@ -7,7 +7,6 @@
#include "IVideoDriver.h" #include "IVideoDriver.h"
#include "SMesh.h" #include "SMesh.h"
#include "SMeshBuffer.h" #include "SMeshBuffer.h"
#include "SAnimatedMesh.h"
#include "IReadFile.h" #include "IReadFile.h"
#include "fast_atof.h" #include "fast_atof.h"
#include "coreutil.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 // Clean up the allocate obj file contents
delete[] buf; delete[] buf;
// more cleaning up // more cleaning up
cleanUp(); cleanUp();
mesh->drop();
return animMesh; // Nothing in the mesh
if (mesh->getMeshBufferCount() == 0) {
mesh->drop();
return nullptr;
}
mesh->recalculateBoundingBox();
return mesh;
} }
//! Read RGB color //! Read RGB color

View file

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

View file

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

View file

@ -7,7 +7,15 @@
#include "CBoneSceneNode.h" #include "CBoneSceneNode.h"
#include "IAnimatedMeshSceneNode.h" #include "IAnimatedMeshSceneNode.h"
#include "SSkinMeshBuffer.h" #include "SSkinMeshBuffer.h"
#include "Transform.h"
#include "aabbox3d.h"
#include "irrMath.h"
#include "matrix4.h"
#include "os.h" #include "os.h"
#include "vector3d.h"
#include <cassert>
#include <cstddef>
#include <variant>
#include <vector> #include <vector>
#include <cassert> #include <cassert>
@ -48,183 +56,77 @@ void SkinnedMesh::setAnimationSpeed(f32 fps)
FramesPerSecond = fps; FramesPerSecond = fps;
} }
//! returns the animated mesh based // Keyframe Animation
IMesh *SkinnedMesh::getMesh(f32 frame)
{
// animate(frame,startFrameLoop, endFrameLoop);
if (frame == -1)
return this;
animateMesh(frame);
skinMesh(); using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
return this; std::vector<VariantTransform> SkinnedMesh::animateMesh(f32 frame)
{
assert(HasAnimation);
std::vector<VariantTransform> result;
result.reserve(AllJoints.size());
for (auto *joint : AllJoints)
result.push_back(joint->animate(frame));
return result;
} }
//-------------------------------------------------------------------------- core::aabbox3df SkinnedMesh::calculateBoundingBox(
// Keyframe Animation const std::vector<core::matrix4> &global_transforms)
//--------------------------------------------------------------------------
//! Animates joints based on frame input
void SkinnedMesh::animateMesh(f32 frame)
{ {
if (!HasAnimation || LastAnimatedFrame == frame) assert(global_transforms.size() == AllJoints.size());
return; core::aabbox3df result = StaticPartsBox;
// skeletal animation
LastAnimatedFrame = frame; for (u16 i = 0; i < AllJoints.size(); ++i) {
SkinnedLastFrame = false; auto box = AllJoints[i]->LocalBoundingBox;
global_transforms[i].transformBoxEx(box);
for (auto *joint : AllJoints) { result.addInternalBox(box);
// 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);
} }
// rigid animation
// Note: for (u16 i = 0; i < AllJoints.size(); ++i) {
// LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for for (u32 j : AllJoints[i]->AttachedMeshes) {
// one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once. auto box = (*SkinningBuffers)[j]->BoundingBox;
// a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move global_transforms[i].transformBoxEx(box);
result.addInternalBox(box);
//----------------
// Temp!
buildAllLocalAnimatedMatrices();
//-----------------
updateBoundingBox();
}
void SkinnedMesh::buildAllLocalAnimatedMatrices()
{
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;
// -----------------------------------
}
} else {
joint->LocalAnimatedMatrix = joint->LocalMatrix;
} }
} }
SkinnedLastFrame = false; return result;
} }
void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint) // Software Skinning
void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
{ {
if (!joint) { if (!HasAnimation)
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; return;
//---------------- // rigid animation
// This is marked as "Temp!". A shiny dubloon to whomever can tell me why. for (size_t i = 0; i < AllJoints.size(); ++i) {
buildAllGlobalAnimatedMatrices(); auto *joint = AllJoints[i];
//----------------- for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
SkinnedLastFrame = true; Buffer->Transformation = global_matrices[i];
if (!HardwareSkinning) {
// rigid animation
for (auto *joint : AllJoints) {
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = joint->GlobalAnimatedMatrix;
}
} }
// clear skinning helper array
for (std::vector<char> &buf : Vertices_Moved)
std::fill(buf.begin(), buf.end(), false);
// skin starting with the root joints
for (auto *rootJoint : RootJoints)
skinJoint(rootJoint, 0);
for (auto *buffer : *SkinningBuffers)
buffer->setDirty(EBT_VERTEX);
} }
updateBoundingBox();
}
void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) // clear skinning helper array
{ for (std::vector<char> &buf : Vertices_Moved)
if (joint->Weights.size()) { std::fill(buf.begin(), buf.end(), false);
// Find this joints pull on vertices...
// skin starting with the root joints
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
if (joint->Weights.empty())
continue;
// Find this joints pull on vertices
// Note: It is assumed that the global inversed matrix has been calculated at this point. // 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; core::vector3df thisVertexMove, thisNormalMove;
auto &buffersUsed = *SkinningBuffers; auto &buffersUsed = *SkinningBuffers;
// Skin Vertices Positions and Normals... // Skin Vertices, Positions and Normals
for (const auto &weight : joint->Weights) { for (const auto &weight : joint->Weights) {
// Pull this vertex... // Pull this vertex...
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
//*(weight._Pos) += thisVertexMove * weight.strength; //*(weight._Pos) += thisVertexMove * weight.strength;
} }
buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
} }
} }
// Skin all children for (auto *buffer : *SkinningBuffers)
for (auto *childJoint : joint->Children) buffer->setDirty(EBT_VERTEX);
skinJoint(childJoint, joint);
} }
//! Gets joint count. //! Gets joint count.
@ -310,7 +209,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
if (LocalBuffers[i]->getMaterial() == material) if (LocalBuffers[i]->getMaterial() == material)
return LocalBuffers[i]; return LocalBuffers[i];
} }
return 0; return nullptr;
} }
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
@ -337,29 +236,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
LocalBuffers[i]->setDirty(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() void SkinnedMesh::refreshJointCache()
{ {
// copy cache from the mesh... // copy cache from the mesh...
@ -384,113 +260,192 @@ void SkinnedMesh::resetAnimation()
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal; 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 // Note that the joints are topologically sorted.
return; for (u16 i = 0; i < AllJoints.size(); ++i) {
if (auto parent_id = AllJoints[i]->ParentJointID) {
// Go through the root bones matrices[i] = matrices[*parent_id] * matrices[i];
if (!joint) { }
for (auto *rootJoint : RootJoints)
calculateGlobalMatrices(rootJoint, nullptr);
return;
} }
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) { for (auto *joint : AllJoints) {
if (!joint->keys.empty()) { if (!joint->keys.empty()) {
HasAnimation = true; return true;
break;
} }
} }
// meshes with weights, are still counted as animated for ragdolls, etc // meshes with weights are animatable
if (!HasAnimation) { for (auto *joint : AllJoints) {
for (auto *joint : AllJoints) { if (!joint->Weights.empty()) {
if (joint->Weights.size()) { return true;
HasAnimation = true; }
break; }
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());
}
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
// check for invalid ids
if (buffer_id >= LocalBuffers.size()) {
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
} }
} }
} }
if (HasAnimation) { for (u32 i = 0; i < Vertices_Moved.size(); ++i)
EndFrame = 0.0f; for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
for (const auto *joint : AllJoints) { Vertices_Moved[i][j] = false;
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
// For skinning: cache weight values for speed
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
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;
} }
} }
if (HasAnimation && !PreparedForSkinning) { normalizeWeights();
PreparedForSkinning = true;
// check for bugs: for (auto *joint : AllJoints) {
for (auto *joint : AllJoints) { joint->keys.cleanup();
for (auto &weight : joint->Weights) { }
const u16 buffer_id = weight.buffer_id; }
const u32 vertex_id = weight.vertex_id;
// check for invalid ids void SkinnedMesh::calculateStaticBoundingBox()
if (buffer_id >= LocalBuffers.size()) { {
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); std::vector<std::vector<bool>> animated(getMeshBufferCount());
weight.buffer_id = weight.vertex_id = 0; for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) { animated[mb] = std::vector<bool>(getMeshBuffer(mb)->getVertexCount());
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0; 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;
} }
} }
} }
}
}
// An array used in skinning void SkinnedMesh::calculateJointBoundingBoxes()
{
for (u32 i = 0; i < Vertices_Moved.size(); ++i) for (auto *joint : AllJoints) {
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) bool first = true;
Vertices_Moved[i][j] = false; for (auto &weight : joint->Weights) {
if (weight.strength < 1e-6)
// For skinning: cache weight values for speed continue;
auto pos = weight.StaticPos;
for (auto *joint : AllJoints) { joint->GlobalInversedMatrix.value().transformVect(pos);
for (auto &weight : joint->Weights) { if (!first) {
const u16 buffer_id = weight.buffer_id; joint->LocalBoundingBox.addInternalPoint(pos);
const u32 vertex_id = weight.vertex_id; } else {
joint->LocalBoundingBox.reset(pos);
weight.Moved = &Vertices_Moved[buffer_id][vertex_id]; first = false;
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();
} }
SkinnedLastFrame = 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);
}
} }
//! called by loader after populating with mesh and bone data //! 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); os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
// Make sure we recalc the next frame topoSortJoints();
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...
// Set array sizes
for (u32 i = 0; i < LocalBuffers.size(); ++i) { for (u32 i = 0; i < LocalBuffers.size(); ++i) {
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount()); Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
} }
checkForAnimation(); prepareForSkinning();
if (HasAnimation) { std::vector<core::matrix4> matrices;
for (auto *joint : AllJoints) { matrices.reserve(AllJoints.size());
joint->keys.cleanup();
}
}
// Needed for animation and skinning...
calculateGlobalMatrices(0, 0);
// rigid animation for non animated meshes
for (auto *joint : AllJoints) { for (auto *joint : AllJoints) {
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();
}
// rigid animation for non animated meshes
for (u32 attachedMeshIdx : joint->AttachedMeshes) { for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = joint->GlobalAnimatedMatrix; Buffer->Transformation = matrices[i];
} }
} }
// calculate bounding box recalculateBaseBoundingBoxes();
if (LocalBuffers.empty()) StaticPoseBox = calculateBoundingBox(matrices);
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);
}
}
return this; 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 *SkinnedMeshBuilder::addMeshBuffer()
{ {
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer(); scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent) SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
{ {
SJoint *joint = new SJoint; SJoint *joint = new SJoint;
joint->setParent(parent);
joint->JointID = AllJoints.size();
AllJoints.push_back(joint); 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; 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() void SkinnedMesh::convertMeshToTangents()
{ {
// now calculate tangents // now calculate tangents

View file

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

View file

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

View file

@ -56,5 +56,3 @@ script: |
# Is a backup icon location in case # Is a backup icon location in case
mkdir -p AppDir/usr/share/luanti/misc 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 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 Exec=luanti
Icon=luanti Icon=luanti
Terminal=false Terminal=false
PrefersNonDefaultGPU=true # Note: don't add PrefersNonDefaultGPU here, see #16095
Type=Application Type=Application
Categories=Game;Simulation; Categories=Game;Simulation;
StartupNotify=false StartupNotify=false

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: French (Minetest)\n" "Project-Id-Version: French (Minetest)\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-14 23:02+0200\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" "Last-Translator: waxtatect <piero@live.ie>\n"
"Language-Team: French <https://hosted.weblate.org/projects/minetest/minetest/" "Language-Team: French <https://hosted.weblate.org/projects/minetest/minetest/"
"fr/>\n" "fr/>\n"
@ -938,29 +938,27 @@ msgstr "Supprimer le monde « $1 » ?"
#: builtin/mainmenu/dlg_rebind_keys.lua #: builtin/mainmenu/dlg_rebind_keys.lua
msgid "As a result, your keybindings may have been changed." 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 #: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Check out the key settings or refer to the documentation:" 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 #: builtin/mainmenu/dlg_rebind_keys.lua
msgid "Close" msgid "Close"
msgstr "" msgstr "Fermer"
#: builtin/mainmenu/dlg_rebind_keys.lua #: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Keybindings changed" msgid "Keybindings changed"
msgstr "Raccourcis clavier" msgstr "Modification des raccourcis clavier"
#: builtin/mainmenu/dlg_rebind_keys.lua #: builtin/mainmenu/dlg_rebind_keys.lua
#, fuzzy
msgid "Open settings" msgid "Open settings"
msgstr "Paramètres" msgstr "Ouvrir les paramètres"
#: builtin/mainmenu/dlg_rebind_keys.lua #: builtin/mainmenu/dlg_rebind_keys.lua
msgid "The input handling system was reworked in Luanti 5.12.0." 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 #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
msgid "Confirm Password" msgid "Confirm Password"

View file

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

View file

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

View file

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

View file

@ -147,8 +147,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
/* /*
Menu-game loop Menu-game loop
*/ */
bool retval = true; bool retval = true;
bool *kill = porting::signal_handler_killstatus(); volatile auto *kill = porting::signal_handler_killstatus();
while (m_rendering_engine->run() && !*kill && while (m_rendering_engine->run() && !*kill &&
!g_gamecallback->shutdown_requested) { !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_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() void ClientLauncher::config_guienv()
{ {
gui::IGUISkin *skin = guienv->getSkin(); gui::IGUISkin *skin = guienv->getSkin();
@ -364,10 +376,9 @@ void ClientLauncher::config_guienv()
if (cached_id != sprite_ids.end()) { if (cached_id != sprite_ids.end()) {
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, cached_id->second); skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, cached_id->second);
} else { } else {
gui::IGUISpriteBank *sprites = skin->getSpriteBank(); auto *driver = m_rendering_engine->get_video_driver();
video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); auto *texture = loadTexture(driver, path.c_str());
video::ITexture *texture = driver->getTexture(path.c_str()); s32 id = skin->getSpriteBank()->addTextureAsSprite(texture);
s32 id = sprites->addTextureAsSprite(texture);
if (id != -1) { if (id != -1) {
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, id); skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, id);
sprite_ids.emplace(path, id); sprite_ids.emplace(path, id);
@ -529,9 +540,9 @@ bool ClientLauncher::launch_game(std::string &error_message,
void ClientLauncher::main_menu(MainMenuData *menudata) 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(); video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
auto *device = m_rendering_engine->get_raw_device(); auto *device = m_rendering_engine->get_raw_device();
// Wait until app is in foreground because of #15883 // Wait until app is in foreground because of #15883
infostream << "Waiting for app to be in foreground" << std::endl; infostream << "Waiting for app to be in foreground" << std::endl;

View file

@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
matrix.setTextureScale(txs, tys); 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) static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
{ {
thread_local std::vector<u64> logged; 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 = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
m_animated_meshnode->grab(); m_animated_meshnode->grab();
mesh->drop(); // The scene node took hold of it 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); m_animated_meshnode->setScale(m_prop.visual_size);
// set vertex colors to ensure alpha is set // 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) { m_animated_meshnode->forEachMaterial([this] (auto &mat) {
mat.BackfaceCulling = m_prop.backface_culling; 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 } else
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl; errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
break; break;
@ -783,7 +788,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
updateMarker(); updateMarker();
updateNodePos(); updateNodePos();
updateAnimation(); updateAnimation();
updateBones(.0f);
updateAttachments(); updateAttachments();
setNodeLight(m_last_light); setNodeLight(m_last_light);
updateMeshCulling(); updateMeshCulling();
@ -1174,18 +1178,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
rot_translator.val_current = m_rotation; rot_translator.val_current = m_rotation;
updateNodePos(); 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) 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); 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() void GenericCAO::updateAttachments()
{ {
ClientActiveObject *parent = getParent(); ClientActiveObject *parent = getParent();
@ -1747,7 +1701,6 @@ void GenericCAO::processMessage(const std::string &data)
} else { } else {
m_bone_override[bone] = props; m_bone_override[bone] = props;
} }
// updateBones(); now called every step
} else if (cmd == AO_CMD_ATTACH_TO) { } else if (cmd == AO_CMD_ATTACH_TO) {
u16 parent_id = readS16(is); u16 parent_id = readS16(is);
std::string bone = deSerializeString16(is); std::string bone = deSerializeString16(is);

View file

@ -286,8 +286,6 @@ public:
void updateAnimationSpeed(); void updateAnimationSpeed();
void updateBones(f32 dtime);
void processMessage(const std::string &data) override; void processMessage(const std::string &data) override;
bool directReportPunch(v3f dir, const ItemStack *punchitem, 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, 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 min = box.MinEdge;
v3f max = box.MaxEdge; 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 = {{ std::array<video::S3DVertex, 24> vertices = {{
// top // top
video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, {}, txc[0], txc[1]), 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: case TileRotation::None:
break; break;
case TileRotation::R90: case TileRotation::R90:
tcoords.set(-tcoords.Y, tcoords.X); tcoords.set(1 - tcoords.Y, tcoords.X);
break; break;
case TileRotation::R180: case TileRotation::R180:
tcoords.set(-tcoords.X, -tcoords.Y); tcoords.set(1 - tcoords.X, 1 - tcoords.Y);
break; break;
case TileRotation::R270: case TileRotation::R270:
tcoords.set(tcoords.Y, -tcoords.X); tcoords.set(tcoords.Y, 1 - tcoords.X);
break; 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 // for the opposite corners of each face - therefore, there
// should be (2+2)*6=24 values in the list. The order of // 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 // the faces in the list is up-down-right-left-back-front
// if nullptr use standard [0,1] coords
// (compatible with ContentFeatures). // (compatible with ContentFeatures).
// mask - a bit mask that suppresses drawing of tiles. // mask - a bit mask that suppresses drawing of tiles.
// tile i will not be drawn if mask & (1 << i) is 1 // 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 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) { for (int k = 0; k < 6; ++k) {
if (mask & (1 << 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) void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
{ {
f32 tx1 = (box.MinEdge.X / BS) + 0.5; // Generate texture coords which are aligned to coords of a solid nodes
f32 ty1 = (box.MinEdge.Y / BS) + 0.5; f32 tx1 = (box.MinEdge.X / BS) + 0.5f;
f32 tz1 = (box.MinEdge.Z / BS) + 0.5; f32 ty1 = (box.MinEdge.Y / BS) + 0.5f;
f32 tx2 = (box.MaxEdge.X / BS) + 0.5; f32 tz1 = (box.MinEdge.Z / BS) + 0.5f;
f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; f32 tx2 = (box.MaxEdge.X / BS) + 0.5f;
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; f32 ty2 = (box.MaxEdge.Y / BS) + 0.5f;
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5f;
f32 txc[24] = { f32 txc[24] = {
tx1, 1 - tz2, tx2, 1 - tz1, // up tx1, 1 - tz2, tx2, 1 - tz1, // up
tx1, tz1, tx2, tz2, // down 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) const TileSpec *tiles, int tile_count, const f32 *txc, u8 mask)
{ {
bool scale = std::fabs(cur_node.f->visual_scale - 1.0f) > 1e-3f; bool scale = std::fabs(cur_node.f->visual_scale - 1.0f) > 1e-3f;
f32 texture_coord_buf[24];
f32 dx1 = box.MinEdge.X; f32 dx1 = box.MinEdge.X;
f32 dy1 = box.MinEdge.Y; f32 dy1 = box.MinEdge.Y;
f32 dz1 = box.MinEdge.Z; f32 dz1 = box.MinEdge.Z;
@ -342,19 +384,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box,
f32 dy2 = box.MaxEdge.Y; f32 dy2 = box.MaxEdge.Y;
f32 dz2 = box.MaxEdge.Z; f32 dz2 = box.MaxEdge.Z;
if (scale) { 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.MinEdge *= cur_node.f->visual_scale;
box.MaxEdge *= cur_node.f->visual_scale; box.MaxEdge *= cur_node.f->visual_scale;
} }
box.MinEdge += cur_node.origin; box.MinEdge += cur_node.origin;
box.MaxEdge += cur_node.origin; box.MaxEdge += cur_node.origin;
if (!txc) {
generateCuboidTextureCoords(box, texture_coord_buf);
txc = texture_coord_buf;
}
if (data->m_smooth_lighting) { if (data->m_smooth_lighting) {
LightInfo lights[8]; LightInfo lights[8];
for (int j = 0; j < 8; ++j) { 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. 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); cur_node.origin = intToFloat(cur_node.p, BS);
auto box = aabb3f(v3f(-0.5 * BS), v3f(0.5 * BS)); auto box = aabb3f(v3f(-0.5 * BS), v3f(0.5 * BS));
f32 texture_coord_buf[24];
box.MinEdge += cur_node.origin; box.MinEdge += cur_node.origin;
box.MaxEdge += cur_node.origin; box.MaxEdge += cur_node.origin;
generateCuboidTextureCoords(box, texture_coord_buf);
if (data->m_smooth_lighting) { if (data->m_smooth_lighting) {
LightPair lights[6][4]; LightPair lights[6][4];
for (int face = 0; face < 6; ++face) { 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]; auto final_lights = lights[face];
for (int j = 0; j < 4; j++) { for (int j = 0; j < 4; j++) {
video::S3DVertex &vertex = vertices[j]; video::S3DVertex &vertex = vertices[j];
@ -471,7 +503,7 @@ void MapblockMeshGenerator::drawSolidNode()
return QuadDiagonal::Diag02; return QuadDiagonal::Diag02;
}); });
} else { } 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); video::SColor color = encode_light(lights[face], cur_node.f->light_source);
if (!cur_node.f->light_source) if (!cur_node.f->light_source)
applyFacesShading(color, vertices[0].Normal); applyFacesShading(color, vertices[0].Normal);
@ -952,7 +984,10 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
if (edge_invisible) if (edge_invisible)
continue; 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++) { for (int face = 0; face < 6; face++) {
@ -996,16 +1031,17 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
float vlev = (param2 / 63.0f) * 2.0f - 1.0f; float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
TileSpec tile; TileSpec tile;
getSpecialTile(0, &tile); getSpecialTile(0, &tile);
drawAutoLightedCuboid( aabb3f box(
aabb3f( -(nb[5] ? g : b),
-(nb[5] ? g : b), -(nb[4] ? g : b),
-(nb[4] ? g : b), -(nb[3] ? g : b),
-(nb[3] ? g : b), (nb[2] ? g : b),
(nb[2] ? g : b), (nb[1] ? g : b) * vlev,
(nb[1] ? g : b) * vlev, (nb[0] ? g : b)
(nb[0] ? g : b) );
), f32 txc[24];
tile); generateCuboidTextureCoords(box, txc);
drawAutoLightedCuboid(box, tile, txc);
} }
} }
@ -1649,7 +1685,10 @@ void MapblockMeshGenerator::drawNodeboxNode()
for (auto &box : boxes) { for (auto &box : boxes) {
u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors); 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) { if (cur_node.f->mesh_ptr) {
// clone and rotate mesh // clone and rotate mesh
mesh = cloneMesh(cur_node.f->mesh_ptr); mesh = cloneStaticMesh(cur_node.f->mesh_ptr);
bool modified = true; bool modified = true;
if (facedir) if (facedir)
rotateMeshBy6dFacedir(mesh, facedir); rotateMeshBy6dFacedir(mesh, facedir);

View file

@ -86,7 +86,7 @@ private:
template <typename Fn> template <typename Fn>
void drawCuboid(const aabb3f &box, const TileSpec *tiles, int tilecount, void drawCuboid(const aabb3f &box, const TileSpec *tiles, int tilecount,
const f32 *txc, u8 mask, Fn &&face_lighter); 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 &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); 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; u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const;

View file

@ -65,6 +65,8 @@
#include "client/sound/sound_openal.h" #include "client/sound/sound_openal.h"
#endif #endif
#include <csignal>
class NodeDugEvent : public MtEvent class NodeDugEvent : public MtEvent
{ {
public: public:
@ -561,7 +563,7 @@ public:
Game(); Game();
~Game(); ~Game();
bool startup(bool *kill, bool startup(volatile std::sig_atomic_t *kill,
InputHandler *input, InputHandler *input,
RenderingEngine *rendering_engine, RenderingEngine *rendering_engine,
const GameStartData &game_params, const GameStartData &game_params,
@ -793,14 +795,14 @@ private:
This class does take ownership/responsibily for cleaning up etc of any of This class does take ownership/responsibily for cleaning up etc of any of
these items (e.g. device) these items (e.g. device)
*/ */
IrrlichtDevice *device; IrrlichtDevice *device;
RenderingEngine *m_rendering_engine; RenderingEngine *m_rendering_engine;
video::IVideoDriver *driver; video::IVideoDriver *driver;
scene::ISceneManager *smgr; scene::ISceneManager *smgr;
bool *kill; volatile std::sig_atomic_t *kill;
std::string *error_message; std::string *error_message;
bool *reconnect_requested; bool *reconnect_requested;
PausedNodesList paused_animated_nodes; PausedNodesList paused_animated_nodes;
bool simple_singleplayer_mode; bool simple_singleplayer_mode;
/* End 'cache' */ /* End 'cache' */
@ -932,7 +934,7 @@ Game::~Game()
m_rendering_engine->finalize(); m_rendering_engine->finalize();
} }
bool Game::startup(bool *kill, bool Game::startup(volatile std::sig_atomic_t *kill,
InputHandler *input, InputHandler *input,
RenderingEngine *rendering_engine, RenderingEngine *rendering_engine,
const GameStartData &start_data, const GameStartData &start_data,
@ -1114,8 +1116,12 @@ void Game::run()
void Game::shutdown() void Game::shutdown()
{ {
// Clear text when exiting. // Delete text and menus first
m_game_ui->clearText(); m_game_ui->clearText();
m_game_formspec.reset();
while (g_menumgr.menuCount() > 0) {
g_menumgr.deleteFront();
}
if (g_touchcontrols) if (g_touchcontrols)
g_touchcontrols->hide(); g_touchcontrols->hide();
@ -1126,11 +1132,6 @@ void Game::shutdown()
sky.reset(); sky.reset();
/* cleanup menus */
while (g_menumgr.menuCount() > 0) {
g_menumgr.deleteFront();
}
// only if the shutdown progress bar isn't shown yet // only if the shutdown progress bar isn't shown yet
if (m_shutdown_progress == 0.0f) if (m_shutdown_progress == 0.0f)
showOverlayMessage(N_("Shutting down..."), 0, 0); 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, InputHandler *input,
RenderingEngine *rendering_engine, RenderingEngine *rendering_engine,
const GameStartData &start_data, const GameStartData &start_data,

View file

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

View file

@ -31,16 +31,6 @@ struct TextDestNodeMetadata : public TextDest
m_p = p; m_p = p;
m_client = client; 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) void gotText(const StringMap &fields)
{ {
m_client->sendNodemetaFields(m_p, "", fields); m_client->sendNodemetaFields(m_p, "", fields);
@ -217,10 +207,11 @@ void GameFormSpec::deleteFormspec()
} }
} }
GameFormSpec::~GameFormSpec() { void GameFormSpec::reset()
{
if (m_formspec) if (m_formspec)
m_formspec->quitMenu(); m_formspec->quitMenu();
this->deleteFormspec(); deleteFormspec();
} }
bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname) 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); void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input);
~GameFormSpec(); ~GameFormSpec() { reset(); }
void showFormSpec(const std::string &formspec, const std::string &formname); void showFormSpec(const std::string &formspec, const std::string &formname);
void showCSMFormSpec(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(); void disableDebugView();
bool handleCallbacks(); bool handleCallbacks();
void reset();
#ifdef __ANDROID__ #ifdef __ANDROID__
// Returns false if no formspec open // Returns false if no formspec open

View file

@ -3,15 +3,16 @@
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> // Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "mesh.h" #include "mesh.h"
#include "IMeshBuffer.h"
#include "SSkinMeshBuffer.h"
#include "debug.h" #include "debug.h"
#include "log.h" #include "log.h"
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include <IAnimatedMesh.h> #include <IAnimatedMesh.h>
#include <SAnimatedMesh.h>
#include <IAnimatedMeshSceneNode.h> #include <IAnimatedMeshSceneNode.h>
#include "S3DVertex.h" #include "S3DVertex.h"
#include "SMesh.h" #include <SMesh.h>
#include "SMeshBuffer.h" #include "SMeshBuffer.h"
inline static void applyShadeFactor(video::SColor& color, float factor) inline static void applyShadeFactor(video::SColor& color, float factor)
@ -95,11 +96,23 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
mesh->addMeshBuffer(buf); mesh->addMeshBuffer(buf);
buf->drop(); buf->drop();
} }
scaleMesh(mesh, scale); // also recalculates bounding box
return mesh;
}
scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh); template<typename F>
mesh->drop(); inline static void transformMeshBuffer(scene::IMeshBuffer *buf,
scaleMesh(anim_mesh, scale); // also recalculates bounding box const F &transform_vertex)
return anim_mesh; {
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) void scaleMesh(scene::IMesh *mesh, v3f scale)
@ -112,14 +125,9 @@ void scaleMesh(scene::IMesh *mesh, v3f scale)
u32 mc = mesh->getMeshBufferCount(); u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) { for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType()); transformMeshBuffer(buf, [scale](video::S3DVertex *vertex) {
u32 vertex_count = buf->getVertexCount(); vertex->Pos *= scale;
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();
// calculate total bounding box // calculate total bounding box
if (j == 0) if (j == 0)
@ -140,14 +148,9 @@ void translateMesh(scene::IMesh *mesh, v3f vec)
u32 mc = mesh->getMeshBufferCount(); u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; j++) { for (u32 j = 0; j < mc; j++) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
const u32 stride = getVertexPitchFromType(buf->getVertexType()); transformMeshBuffer(buf, [vec](video::S3DVertex *vertex) {
u32 vertex_count = buf->getVertexCount(); vertex->Pos += vec;
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();
// calculate total bounding box // calculate total bounding box
if (j == 0) if (j == 0)
@ -330,44 +333,40 @@ bool checkMeshNormals(scene::IMesh *mesh)
return true; 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) scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{ {
switch (mesh_buffer->getVertexType()) { switch (mesh_buffer->getVertexType()) {
case video::EVT_STANDARD: { case video::EVT_STANDARD:
video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices(); return cloneMeshBuffer<video::S3DVertex, scene::SMeshBuffer>(mesh_buffer);
u16 *indices = mesh_buffer->getIndices(); case video::EVT_2TCOORDS:
scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer(); return cloneMeshBuffer<video::S3DVertex2TCoords, scene::SMeshBufferLightMap>(mesh_buffer);
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, case video::EVT_TANGENTS:
mesh_buffer->getIndexCount()); return cloneMeshBuffer<video::S3DVertexTangents, scene::SMeshBufferTangents>(mesh_buffer);
return cloned_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); sanity_check(false);
return NULL; return NULL;
} }
scene::SMesh* cloneMesh(scene::IMesh *src_mesh) scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh)
{ {
scene::SMesh* dst_mesh = new scene::SMesh(); scene::SMesh* dst_mesh = new scene::SMesh();
for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { 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); scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer);
/* /// Clone a mesh. For an animated mesh, this will clone the static pose.
Clone the mesh. scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh);
*/
scene::SMesh* cloneMesh(scene::IMesh *src_mesh);
/* /*
Convert nodeboxes to mesh. Each tile goes into a different buffer. 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]; const TileLayer *layer = &tile.layers[layernum];
if (layer->empty()) if (layer->empty())
continue; continue;
append(*layer, vertices, numVertices, indices, numIndices, layernum, append(*layer, vertices, numVertices, indices, numIndices, layernum);
tile.world_aligned);
} }
} }
void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices, void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices,
u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum, u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum)
bool use_scale)
{ {
PreMeshBuffer &p = findBuffer(layer, layernum, numVertices); PreMeshBuffer &p = findBuffer(layer, layernum, numVertices);
f32 scale = 1.0f;
if (use_scale)
scale = 1.0f / layer.scale;
u32 vertex_count = p.vertices.size(); u32 vertex_count = p.vertices.size();
for (u32 i = 0; i < numVertices; i++) { for (u32 i = 0; i < numVertices; i++) {
p.vertices.emplace_back(vertices[i].Pos + offset, vertices[i].Normal, 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, m_bounding_radius_sq = std::max(m_bounding_radius_sq,
(vertices[i].Pos - m_center_pos).getLengthSQ()); (vertices[i].Pos - m_center_pos).getLengthSQ());
} }

View file

@ -55,7 +55,7 @@ private:
void append(const TileLayer &material, void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices, const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, const u16 *indices, u32 numIndices,
u8 layernum, bool use_scale = false); u8 layernum);
PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices); 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); 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, void ShadowRenderer::renderShadowMap(video::ITexture *target,

View file

@ -191,8 +191,6 @@ struct TileSpec
bool world_aligned = false; bool world_aligned = false;
//! Tile rotation. //! Tile rotation.
TileRotation rotation = TileRotation::None; TileRotation rotation = TileRotation::None;
//! This much light does the tile emit.
u8 emissive_light = 0;
//! The first is base texture, the second is overlay. //! The first is base texture, the second is overlay.
TileLayer layers[MAX_TILE_LAYERS]; 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); dim = core::dimension2d<u32>(dim.Width, frame_height);
} }
scene::IMesh *original = g_extrusion_mesh_cache->create(dim); scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
scene::SMesh *mesh = cloneMesh(original); scene::SMesh *mesh = cloneStaticMesh(original);
original->drop(); original->drop();
//set texture //set texture
mesh->getMeshBuffer(0)->getMaterial().setTexture(0, mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
@ -639,7 +639,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
// get mesh // get mesh
core::dimension2d<u32> dim = texture->getSize(); core::dimension2d<u32> dim = texture->getSize();
scene::IMesh *original = g_extrusion_mesh_cache->create(dim); scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
scene::SMesh *mesh = cloneMesh(original); scene::SMesh *mesh = cloneStaticMesh(original);
original->drop(); original->drop();
//set texture //set texture

View file

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

View file

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

View file

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

View file

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

View file

@ -77,6 +77,9 @@
return; \ return; \
} }
// Element ID of the "Proceed" button shown for sizeless formspecs
constexpr s32 ID_PROCEED_BTN = 257;
/* /*
GUIFormSpecMenu 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)))); 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, GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
rect, m_tsrc); 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) { if (spec.fname == m_focused_element) {
Environment->setFocus(e); 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") if (!str_initial_selection.empty() && str_initial_selection != "0")
e->setSelected(stoi(str_initial_selection)); 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_tables.emplace_back(spec, e);
m_fields.push_back(spec); m_fields.push_back(spec);
} }
@ -2998,7 +3001,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
gui::IGUIElement *focused_element = Environment->getFocus(); gui::IGUIElement *focused_element = Environment->getFocus();
if (focused_element && focused_element->getParent() == this) { if (focused_element && focused_element->getParent() == this) {
s32 focused_id = focused_element->getID(); s32 focused_id = focused_element->getID();
if (focused_id > 257) { if (focused_id > ID_PROCEED_BTN) {
for (const GUIFormSpecMenu::FieldSpec &field : m_fields) { for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
if (field.fid == focused_id) { if (field.fid == focused_id) {
m_focused_element = field.fname; m_focused_element = field.fname;
@ -3308,7 +3311,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
size.X / 2 - 70, pos.Y, size.X / 2 - 70, pos.Y,
size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2 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()); wstrgettext("Proceed").c_str());
} }
} }
@ -4031,12 +4034,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
if (m_joystick->wasKeyDown(KeyType::ESC)) { if (m_joystick->wasKeyDown(KeyType::ESC)) {
tryClose(); tryClose();
} else if (m_joystick->wasKeyDown(KeyType::JUMP)) { } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
if (m_allowclose) { trySubmitClose();
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
} }
} }
return handled; return handled;
@ -4053,7 +4051,16 @@ void GUIFormSpecMenu::tryClose()
quitMenu(); quitMenu();
} else { } else {
acceptInput(quit_mode_try); 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; break;
} }
if (current_keys_pending.key_enter) { if (current_keys_pending.key_enter) {
if (m_allowclose) { trySubmitClose();
acceptInput(quit_mode_accept);
quitMenu();
} else {
acceptInput(quit_mode_try);
}
} else { } else {
acceptInput(); acceptInput();
} }
@ -4819,12 +4821,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
if (event.EventType == EET_GUI_EVENT) { if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED const s32 caller_id = event.GUIEvent.Caller->getID();
&& isVisible()) { bool close_on_enter;
// find the element that was clicked
switch (event.GUIEvent.EventType) {
case gui::EGET_TAB_CHANGED:
if (!isVisible())
break;
// find the element that was clicked
for (GUIFormSpecMenu::FieldSpec &s : m_fields) { for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if ((s.ftype == f_TabHeader) && if (s.ftype == f_TabHeader &&
(s.fid == event.GUIEvent.Caller->getID())) { s.fid == caller_id) {
if (!s.sound.empty() && m_sound_manager) if (!s.sound.empty() && m_sound_manager)
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f)); m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
s.send = true; s.send = true;
@ -4833,26 +4841,26 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
return true; return true;
} }
} }
} break;
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
&& isVisible()) { case gui::EGET_ELEMENT_FOCUS_LOST:
if (!isVisible())
break;
if (!canTakeFocus(event.GUIEvent.Element)) { if (!canTakeFocus(event.GUIEvent.Element)) {
infostream<<"GUIFormSpecMenu: Not allowing focus change." infostream<<"GUIFormSpecMenu: Not allowing focus change."
<<std::endl; <<std::endl;
// Returning true disables focus change // Returning true disables focus change
return true; return true;
} }
} break;
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();
if (caller_id == 257) { case gui::EGET_BUTTON_CLICKED:
acceptInput(quit_mode_accept); case gui::EGET_CHECKBOX_CHANGED:
m_text_dst->gotText(L"ExitButton"); case gui::EGET_COMBO_BOX_CHANGED:
quitMenu(); case gui::EGET_SCROLL_BAR_CHANGED:
if (caller_id == ID_PROCEED_BTN) {
trySubmitClose();
return true; return true;
} }
@ -4882,7 +4890,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
if (s.is_exit) { if (s.is_exit) {
acceptInput(quit_mode_accept); acceptInput(quit_mode_accept);
m_text_dst->gotText(L"ExitButton");
quitMenu(); quitMenu();
return true; return true;
} }
@ -4923,60 +4930,57 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s.send = false; s.send = false;
} }
} }
}
if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
// move scroll_containers // move scroll_containers
for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers) for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
c.second->onScrollEvent(event.GUIEvent.Caller); c.second->onScrollEvent(event.GUIEvent.Caller);
} }
break;
if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { case gui::EGET_EDITBOX_ENTER:
if (event.GUIEvent.Caller->getID() > 257) { if (caller_id <= ID_PROCEED_BTN)
bool close_on_enter = true; break;
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if (s.ftype == f_Unknown &&
s.fid == event.GUIEvent.Caller->getID()) {
current_field_enter_pending = s.fname;
auto it = field_close_on_enter.find(s.fname);
if (it != field_close_on_enter.end())
close_on_enter = (*it).second;
break; close_on_enter = true;
} for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
if (s.ftype == f_Unknown &&
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())
close_on_enter = (*it).second;
break;
} }
}
current_keys_pending.key_enter = true; current_keys_pending.key_enter = true;
if (close_on_enter) { if (close_on_enter)
if (m_allowclose) { trySubmitClose();
acceptInput(quit_mode_accept); else
quitMenu(); acceptInput();
} else { return true;
acceptInput(quit_mode_try);
} case gui::EGET_TABLE_CHANGED:
} else { 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 == caller_id) {
s.send = true;
acceptInput(); acceptInput();
s.send = false;
} }
return true;
} }
} return true;
if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { default:
int current_id = event.GUIEvent.Caller->getID(); break;
if (current_id > 257) {
// 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)) {
s.send = true;
acceptInput();
s.send=false;
}
}
return true;
}
} }
} }

View file

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

View file

@ -85,7 +85,7 @@ public:
void setTextList(const std::vector<std::string> &content, void setTextList(const std::vector<std::string> &content,
bool transparent); 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 // Adds empty strings to end of content if there is an incomplete row
void setTable(const TableOptions &options, void setTable(const TableOptions &options,
const TableColumns &columns, const TableColumns &columns,

View file

@ -59,6 +59,8 @@ public:
if(!m_stack.empty()) { if(!m_stack.empty()) {
m_stack.back()->setVisible(true); m_stack.back()->setVisible(true);
guienv->setFocus(m_stack.back()); 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; return false;
} }
ChatInterface iface; ChatInterface iface;
bool &kill = *porting::signal_handler_killstatus(); volatile auto &kill = *porting::signal_handler_killstatus();
try { try {
// Create server // Create server
@ -1181,7 +1181,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
server.start(); server.start();
// Run server // Run server
bool &kill = *porting::signal_handler_killstatus(); volatile auto &kill = *porting::signal_handler_killstatus();
dedicated_server_loop(server, kill); dedicated_server_loop(server, kill);
} catch (const ModError &e) { } catch (const ModError &e) {
@ -1226,7 +1226,7 @@ static bool migrate_map_database(const GameParams &game_params, const Settings &
u32 count = 0; u32 count = 0;
u64 last_update_time = 0; u64 last_update_time = 0;
bool &kill = *porting::signal_handler_killstatus(); volatile auto &kill = *porting::signal_handler_killstatus();
std::vector<v3s16> blocks; std::vector<v3s16> blocks;
old_db->listAllLoadableBlocks(blocks); old_db->listAllLoadableBlocks(blocks);
@ -1280,7 +1280,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting
u32 count = 0; u32 count = 0;
u64 last_update_time = 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 u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE;
const s16 map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); 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; 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); addArea(block_area_nodes);
for(s32 z=p_min.Z; z<=p_max.Z; z++) for(s32 z=p_min.Z; z<=p_max.Z; z++)
for(s32 y=p_min.Y; y<=p_max.Y; y++) for(s32 y=p_min.Y; y<=p_max.Y; y++)
for(s32 x=p_min.X; x<=p_max.X; x++) for(s32 x=p_min.X; x<=p_max.X; x++)
{ {
u8 flags = 0;
MapBlock *block;
v3s16 p(x,y,z); 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; continue;
bool block_data_inexistent = false; MapBlock *block = m_map->getBlockNoCreateNoEx(p);
{ if (block) {
TimeTaker timer2("emerge load", &emerge_load_time); block->copyTo(*this);
} else {
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)) { if (load_if_inexistent && !blockpos_over_max_limit(p)) {
block = m_map->emergeBlock(p, true); block = m_map->emergeBlock(p, true);
assert(block);
block->copyTo(*this); block->copyTo(*this);
} else { } else {
flags |= VMANIP_BLOCK_DATA_INEXIST;
// Mark area inexistent // Mark area inexistent
VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
setFlags(a, VOXELFLAG_NO_DATA); setFlags(a, VOXELFLAG_NO_DATA);
} }
} }
m_loaded_blocks[p] = flags;
} }
if (all_new) if (all_new)
m_is_dirty = false; 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, void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
bool overwrite_generated) const bool overwrite_generated) const
{ {
@ -825,16 +852,27 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
return; return;
assert(m_map); assert(m_map);
/* size_t nload = 0;
Copy data of all blocks
*/ // Copy all the blocks with data back to the map
assert(!m_loaded_blocks.empty()); const auto loaded_blocks = getCoveredBlocks();
for (auto &loaded_block : m_loaded_blocks) { for (auto &it : loaded_blocks) {
v3s16 p = loaded_block.first; if (!it.second)
continue;
v3s16 p = it.first;
MapBlock *block = m_map->getBlockNoCreateNoEx(p); MapBlock *block = m_map->getBlockNoCreateNoEx(p);
bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST); if (!block) {
if (!existed || (block == NULL) || if (!blockpos_over_max_limit(p)) {
(!overwrite_generated && block->isGenerated())) 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; continue;
block->copyFrom(*this); block->copyFrom(*this);
@ -844,6 +882,10 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
if(modified_blocks) if(modified_blocks)
(*modified_blocks)[p] = block; (*modified_blocks)[p] = block;
} }
if (nload > 0) {
verbosestream << "blitBackAll: " << nload << " blocks had to be loaded for writing" << std::endl;
}
} }
MMVManip *MMVManip::clone() const MMVManip *MMVManip::clone() const
@ -860,11 +902,7 @@ MMVManip *MMVManip::clone() const
ret->m_flags = new u8[size]; ret->m_flags = new u8[size];
memcpy(ret->m_flags, m_flags, size * sizeof(u8)); memcpy(ret->m_flags, m_flags, size * sizeof(u8));
} }
ret->m_is_dirty = m_is_dirty; 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; return ret;
} }

View file

@ -307,16 +307,29 @@ public:
MMVManip(Map *map); MMVManip(Map *map);
virtual ~MMVManip() = default; virtual ~MMVManip() = default;
virtual void clear() /*
{ Loads specified area from map and *adds* it to the area already
VoxelManipulator::clear(); contained in the VManip.
m_loaded_blocks.clear(); */
}
void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
bool load_if_inexistent = true); 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, void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
bool overwrite_generated = true) const; bool overwrite_generated = true) const;
@ -339,13 +352,4 @@ protected:
// may be null // may be null
Map *m_map = nullptr; 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_sendThread->stop();
m_receiveThread->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 // wait for threads to finish
m_sendThread->wait(); m_sendThread->wait();
m_receiveThread->wait(); m_receiveThread->wait();

View file

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

View file

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

View file

@ -13,6 +13,7 @@
#include "client/texturesource.h" #include "client/texturesource.h"
#include "client/tile.h" #include "client/tile.h"
#include <IMeshManipulator.h> #include <IMeshManipulator.h>
#include <SMesh.h>
#include <SkinnedMesh.h> #include <SkinnedMesh.h>
#endif #endif
#include "log.h" #include "log.h"
@ -959,23 +960,37 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
palette = tsrc->getPalette(palette_name); palette = tsrc->getPalette(palette_name);
if (drawtype == NDT_MESH && !mesh.empty()) { if (drawtype == NDT_MESH && !mesh.empty()) {
// Read the mesh and apply scale // Note: By freshly reading, we get an unencumbered mesh.
mesh_ptr = client->getMesh(mesh); if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
if (mesh_ptr) { bool apply_bs = false;
v3f scale = v3f(BS) * visual_scale; if (auto *skinned_mesh = dynamic_cast<scene::SkinnedMesh *>(src_mesh)) {
scaleMesh(mesh_ptr, scale); // 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); recalculateBoundingBox(mesh_ptr);
if (!checkMeshNormals(mesh_ptr)) { if (!checkMeshNormals(mesh_ptr)) {
// TODO this should be done consistently when the mesh is loaded
infostream << "ContentFeatures: recalculating normals for mesh " infostream << "ContentFeatures: recalculating normals for mesh "
<< mesh << std::endl; << mesh << std::endl;
meshmanip->recalculateNormals(mesh_ptr, true, false); 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; enum NodeDrawType drawtype;
std::string mesh; std::string mesh;
#if CHECK_CLIENT_BUILD() #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; video::SColor minimap_color;
#endif #endif
float visual_scale; // Misc. scale parameter float visual_scale; // Misc. scale parameter

View file

@ -60,6 +60,7 @@
#include "util/string.h" #include "util/string.h"
#include "util/tracy_wrapper.h" #include "util/tracy_wrapper.h"
#include <vector> #include <vector>
#include <csignal>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <signal.h> #include <signal.h>
@ -81,31 +82,28 @@ namespace porting
Signal handler (grabs Ctrl-C on POSIX systems) 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; return &g_killed;
} }
#if !defined(_WIN32) // POSIX #if !defined(_WIN32) // POSIX
#define STDERR_FILENO 2
static void signal_handler(int sig) static void signal_handler(int sig)
{ {
if (!g_killed) { if (!g_killed) {
if (sig == SIGINT) { if (sig == SIGINT) {
dstream << "INFO: signal_handler(): " const char *dbg_text{"INFO: signal_handler(): "
<< "Ctrl-C pressed, shutting down." << std::endl; "Ctrl-C pressed, shutting down.\n"};
write(STDERR_FILENO, dbg_text, strlen(dbg_text));
} else if (sig == SIGTERM) { } else if (sig == SIGTERM) {
dstream << "INFO: signal_handler(): " const char *dbg_text{"INFO: signal_handler(): "
<< "got SIGTERM, shutting down." << std::endl; "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; g_killed = true;
} else { } else {
(void)signal(sig, SIG_DFL); (void)signal(sig, SIG_DFL);

View file

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

View file

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

View file

@ -18,6 +18,8 @@ extern "C" {
#include <cmath> #include <cmath>
#include "common/c_types.h" #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 { \ #define CHECK_TYPE(index, name, type) do { \
int t = lua_type(L, (index)); \ int t = lua_type(L, (index)); \
@ -28,6 +30,16 @@ extern "C" {
} \ } \
} while(0) } 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 {\ #define CHECK_FLOAT(value, name) do {\
if (std::isnan(value) || std::isinf(value)) { \ if (std::isnan(value) || std::isinf(value)) { \
throw LuaError("Invalid float value for '" name \ throw LuaError("Invalid float value for '" name \
@ -35,7 +47,13 @@ extern "C" {
} \ } \
} while (0) } 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) #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) #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) 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); CHECK_POS_TAB(index);
lua_pushvalue(L, index); lua_pushvalue(L, index);
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR); 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 read_v2s16(lua_State *L, int index)
{ {
v2s16 p; return v2s16::from(read_v2f(L, index));
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;
} }
void push_v2s16(lua_State *L, v2s16 p) void push_v2s16(lua_State *L, v2s16 p)
{ {
lua_createtable(L, 0, 2); push_v2s32(L, v2s32::from(p));
lua_pushinteger(L, p.X);
lua_setfield(L, -2, "x");
lua_pushinteger(L, p.Y);
lua_setfield(L, -2, "y");
} }
void push_v2s32(lua_State *L, v2s32 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 read_v2s32(lua_State *L, int index)
{ {
v2s32 p; return v2s32::from(read_v2f(L, index));
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;
} }
v2f read_v2f(lua_State *L, int index) v2f read_v2f(lua_State *L, int index)
@ -143,9 +142,11 @@ v2f read_v2f(lua_State *L, int index)
v2f p; v2f p;
CHECK_POS_TAB(index); CHECK_POS_TAB(index);
lua_getfield(L, index, "x"); lua_getfield(L, index, "x");
CHECK_POS_COORD2(-1, "x");
p.X = lua_tonumber(L, -1); p.X = lua_tonumber(L, -1);
lua_pop(L, 1); lua_pop(L, 1);
lua_getfield(L, index, "y"); lua_getfield(L, index, "y");
CHECK_POS_COORD2(-1, "y");
p.Y = lua_tonumber(L, -1); p.Y = lua_tonumber(L, -1);
lua_pop(L, 1); lua_pop(L, 1);
return p; return p;
@ -170,30 +171,20 @@ v2f check_v2f(lua_State *L, int index)
v3f read_v3f(lua_State *L, int index) v3f read_v3f(lua_State *L, int index)
{ {
read_v3_aux(L, index); return v3f::from(read_v3d(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);
} }
v3f check_v3f(lua_State *L, int index) v3f check_v3f(lua_State *L, int index)
{ {
read_v3_aux(L, index); return v3f::from(check_v3d(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);
} }
v3d read_v3d(lua_State *L, int index) v3d read_v3d(lua_State *L, int index)
{ {
read_v3_aux(L, 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 x = lua_tonumber(L, -3);
double y = lua_tonumber(L, -2); double y = lua_tonumber(L, -2);
double z = lua_tonumber(L, -1); 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 y = lua_tonumber(L, -2);
double z = lua_tonumber(L, -1); double z = lua_tonumber(L, -1);
lua_pop(L, 3); lua_pop(L, 3);
CHECK_FLOAT(x, "x");
CHECK_FLOAT(y, "y");
CHECK_FLOAT(z, "z");
return v3d(x, y, 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)); 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); video::SColor color(0);
CHECK_TYPE(index, "ARGB color", LUA_TTABLE); CHECK_TYPE(index, "ARGB color", LUA_TTABLE);
lua_getfield(L, index, "a"); lua_getfield(L, index, "a");
color.setAlpha(lua_isnumber(L, -1) ? clamp_col(lua_tonumber(L, -1)) : 0xFF); color.setAlpha(lua_isnumber(L, -1) ? clamp_col(lua_tonumber(L, -1)) : 0xFF);
lua_pop(L, 1); lua_pop(L, 1);
lua_getfield(L, index, "r"); lua_getfield(L, index, "r");
CHECK_NOT_NIL(-1, "color component R");
color.setRed(clamp_col(lua_tonumber(L, -1))); color.setRed(clamp_col(lua_tonumber(L, -1)));
lua_pop(L, 1); lua_pop(L, 1);
lua_getfield(L, index, "g"); lua_getfield(L, index, "g");
CHECK_NOT_NIL(-1, "color component G");
color.setGreen(clamp_col(lua_tonumber(L, -1))); color.setGreen(clamp_col(lua_tonumber(L, -1)));
lua_pop(L, 1); lua_pop(L, 1);
lua_getfield(L, index, "b"); lua_getfield(L, index, "b");
CHECK_NOT_NIL(-1, "color component B");
color.setBlue(clamp_col(lua_tonumber(L, -1))); color.setBlue(clamp_col(lua_tonumber(L, -1)));
lua_pop(L, 1); lua_pop(L, 1);
return color; return color;

View file

@ -74,13 +74,23 @@ v2f check_v2f(lua_State *L, int index);
v3f check_v3f(lua_State *L, int index); v3f check_v3f(lua_State *L, int index);
v3s16 check_v3s16(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); v3f read_v3f(lua_State *L, int index);
/// @warning relaxed type-checking, prefer `check_v2f`.
v2f read_v2f(lua_State *L, int index); v2f read_v2f(lua_State *L, int index);
/// @warning relaxed type-checking
v2s16 read_v2s16(lua_State *L, int index); v2s16 read_v2s16(lua_State *L, int index);
/// @warning relaxed type-checking
v2s32 read_v2s32(lua_State *L, int index); 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); video::SColor read_ARGB8(lua_State *L, int index);
bool read_color(lua_State *L, int index, video::SColor *color); 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. * 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); 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); std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale);
size_t read_stringlist(lua_State *L, int index, size_t read_stringlist(lua_State *L, int index,
std::vector<std::string> *result); std::vector<std::string> *result);

View file

@ -38,7 +38,6 @@ public:
static struct EnumString es_ContentParamType[]; static struct EnumString es_ContentParamType[];
static struct EnumString es_ContentParamType2[]; static struct EnumString es_ContentParamType2[];
static struct EnumString es_LiquidType[]; static struct EnumString es_LiquidType[];
static struct EnumString es_LiquidMoveType[];
static struct EnumString es_NodeBoxType[]; static struct EnumString es_NodeBoxType[];
static struct EnumString es_TextureAlphaMode[]; 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 // get_node_raw(x, y, z) -> content, param1, param2, pos_ok
int ModApiEnv::l_get_node_raw(lua_State *L) int ModApiEnv::l_get_node_raw(lua_State *L)
{ {
GET_ENV_PTR; GET_PLAIN_ENV_PTR;
// pos v3s16 pos;
// mirrors implementation of read_v3s16 (with the exact same rounding) // mirrors the implementation of read_v3s16 (with the exact same rounding)
double x = lua_tonumber(L, 1); {
double y = lua_tonumber(L, 2); if (lua_isnoneornil(L, 1))
double z = lua_tonumber(L, 3); log_deprecated(L, "X position is nil", 1, true);
v3s16 pos = doubleToInt(v3d(x, y, z), 1.0); if (lua_isnoneornil(L, 2))
// Do it 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);
pos = doubleToInt(v3d(x, y, z), 1.0);
}
bool pos_ok; bool pos_ok;
MapNode n = env->getMap().getNode(pos, &pos_ok); MapNode n = env->getMap().getNode(pos, &pos_ok);
// Return node and pos_ok // Return node and pos_ok

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