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:
commit
c94c466d3d
121 changed files with 2791 additions and 2948 deletions
14
.github/workflows/linux.yml
vendored
14
.github/workflows/linux.yml
vendored
|
@ -132,16 +132,16 @@ jobs:
|
|||
run: |
|
||||
./util/test_multiplayer.sh
|
||||
|
||||
# Build with prometheus-cpp (server-only)
|
||||
clang_11_prometheus:
|
||||
name: "clang_11 (PROMETHEUS=1)"
|
||||
runs-on: ubuntu-22.04
|
||||
# Build with prometheus-cpp (server-only), also runs on ARM64
|
||||
clang_prometheus_arm:
|
||||
name: "clang (with Prometheus, ARM64)"
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: |
|
||||
source ./util/ci/common.sh
|
||||
install_linux_deps clang-11
|
||||
install_linux_deps --headless clang libluajit-5.1-dev
|
||||
|
||||
- name: Build prometheus-cpp
|
||||
run: ./util/ci/build_prometheus_cpp.sh
|
||||
|
@ -150,8 +150,8 @@ jobs:
|
|||
run: |
|
||||
./util/ci/build.sh
|
||||
env:
|
||||
CC: clang-11
|
||||
CXX: clang++-11
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0"
|
||||
|
||||
- name: Test
|
||||
|
|
|
@ -14,7 +14,7 @@ set(CLANG_MINIMUM_VERSION "7.0.1")
|
|||
|
||||
# You should not need to edit these manually, use util/bump_version.sh
|
||||
set(VERSION_MAJOR 5)
|
||||
set(VERSION_MINOR 12)
|
||||
set(VERSION_MINOR 13)
|
||||
set(VERSION_PATCH 0)
|
||||
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
project.ext.set("versionMajor", 5) // Version Major
|
||||
project.ext.set("versionMinor", 12) // Version Minor
|
||||
project.ext.set("versionMinor", 13) // Version Minor
|
||||
project.ext.set("versionPatch", 0) // Version Patch
|
||||
// ^ keep in sync with cmake
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ end
|
|||
local translation_file_header = [[
|
||||
// This file is automatically generated
|
||||
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
|
||||
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
|
||||
// To update it, refer to the bottom of builtin/common/settings/init.lua
|
||||
|
||||
fake_function() {]]
|
||||
|
||||
|
@ -110,15 +110,15 @@ local function create_translation_file(settings)
|
|||
local result = { translation_file_header }
|
||||
for _, entry in ipairs(settings) do
|
||||
if entry.type == "category" then
|
||||
insert(result, sprintf("\tgettext(%q);", entry.name))
|
||||
insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.name))
|
||||
else
|
||||
if entry.readable_name then
|
||||
insert(result, sprintf("\tgettext(%q);", entry.readable_name))
|
||||
insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.readable_name))
|
||||
end
|
||||
if entry.comment ~= "" then
|
||||
local comment_escaped = entry.comment:gsub("\n", "\\n")
|
||||
comment_escaped = comment_escaped:gsub("\"", "\\\"")
|
||||
insert(result, "\tgettext(\"" .. comment_escaped .. "\");")
|
||||
insert(result, "\t/* xgettext:no-c-format */ gettext(\"" .. comment_escaped .. "\");")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,12 +19,14 @@ function meta:__newindex(name, value)
|
|||
return
|
||||
end
|
||||
local info = getinfo(2, "Sl")
|
||||
local desc = ("%s:%d"):format(info.short_src, info.currentline)
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
|
||||
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
|
||||
:format(name, desc))
|
||||
warned[warn_key] = true
|
||||
if info ~= nil then
|
||||
local desc = ("%s:%d"):format(info.short_src, info.currentline)
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
|
||||
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
|
||||
:format(name, desc))
|
||||
warned[warn_key] = true
|
||||
end
|
||||
end
|
||||
declared[name] = true
|
||||
end
|
||||
|
@ -35,6 +37,9 @@ function meta:__index(name)
|
|||
return
|
||||
end
|
||||
local info = getinfo(2, "Sl")
|
||||
if info == nil then
|
||||
return
|
||||
end
|
||||
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
|
||||
if not warned[warn_key] and info.what ~= "C" then
|
||||
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
|
||||
|
|
|
@ -118,7 +118,7 @@ function ui.update()
|
|||
|
||||
if (active_toplevel_ui_elements > 1) then
|
||||
core.log("warning", "more than one active ui "..
|
||||
"element, self most likely isn't intended")
|
||||
"element, this most likely isn't intended")
|
||||
end
|
||||
|
||||
if (active_toplevel_ui_elements == 0) then
|
||||
|
@ -166,6 +166,10 @@ end
|
|||
--------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------
|
||||
core.button_handler = function(fields)
|
||||
if fields["try_quit"] and not fields["key_enter"] then
|
||||
core.event_handler("MenuQuit")
|
||||
return
|
||||
end
|
||||
if fields["btn_reconnect_yes"] then
|
||||
gamedata.reconnect_requested = false
|
||||
gamedata.errormessage = nil
|
||||
|
|
|
@ -170,14 +170,16 @@ function contentdb.get_package_by_id(id)
|
|||
end
|
||||
|
||||
|
||||
function contentdb.calculate_package_id(type, author, name)
|
||||
local id = author:lower() .. "/"
|
||||
local function strip_game_suffix(type, name)
|
||||
if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
|
||||
id = id .. name:sub(1, #name - 5)
|
||||
return name:sub(1, #name - 5)
|
||||
else
|
||||
id = id .. name
|
||||
return name
|
||||
end
|
||||
return id
|
||||
end
|
||||
|
||||
function contentdb.calculate_package_id(type, author, name)
|
||||
return author:lower() .. "/" .. strip_game_suffix(type, name)
|
||||
end
|
||||
|
||||
|
||||
|
@ -427,7 +429,7 @@ function contentdb.set_packages_from_api(packages)
|
|||
-- We currently don't support name changing
|
||||
local suffix = "/" .. package.name
|
||||
if alias:sub(-#suffix) == suffix then
|
||||
contentdb.aliases[alias:lower()] = package.id
|
||||
contentdb.aliases[strip_game_suffix(packages.type, alias:lower())] = package.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -47,22 +47,20 @@
|
|||
],
|
||||
"#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py",
|
||||
"contributors": [
|
||||
"JosiahWI",
|
||||
"Erich Schubert",
|
||||
"wrrrzr",
|
||||
"1F616EMO",
|
||||
"red-001 <red-001@outlook.ie>",
|
||||
"veprogames",
|
||||
"paradust7",
|
||||
"AFCMS",
|
||||
"siliconsniffer",
|
||||
"Wuzzy",
|
||||
"Zemtzov7"
|
||||
"JosiahWI",
|
||||
"veprogames",
|
||||
"Miguel P.L",
|
||||
"AFCMS"
|
||||
],
|
||||
"previous_contributors": [
|
||||
"Ælla Chiana Moskopp (erle) <erle@dieweltistgarnichtso.net> [Logo]",
|
||||
"numzero",
|
||||
"red-001 <red-001@outlook.ie>",
|
||||
"Giuseppe Bilotta",
|
||||
"HybridDog",
|
||||
"ClobberXD",
|
||||
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
|
||||
"MirceaKitsune <mirceakitsune@gmail.com>",
|
||||
|
@ -75,6 +73,7 @@
|
|||
"stujones11",
|
||||
"Rogier <rogier777@gmail.com>",
|
||||
"Gregory Currie (gregorycu)",
|
||||
"paradust7",
|
||||
"JacobF",
|
||||
"Jeija <jeija@mesecons.net>"
|
||||
]
|
||||
|
|
|
@ -33,13 +33,13 @@ end
|
|||
|
||||
local function buttonhandler(this, fields)
|
||||
if fields.reconfigure then
|
||||
local parent = this.parent
|
||||
|
||||
close_dialog(this)
|
||||
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
||||
local dlg = create_settings_dlg("controls_keyboard_and_mouse")
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:set_parent(parent)
|
||||
parent:hide()
|
||||
dlg:show()
|
||||
|
||||
return true
|
||||
|
@ -74,7 +74,7 @@ local function create_rebind_keys_dlg()
|
|||
return dlg
|
||||
end
|
||||
|
||||
function migrate_keybindings()
|
||||
function migrate_keybindings(parent)
|
||||
-- Show migration dialog if the user upgraded from an earlier version
|
||||
-- and this has not yet been shown before, *or* if keys settings had to be changed
|
||||
if core.is_first_run then
|
||||
|
@ -95,14 +95,14 @@ function migrate_keybindings()
|
|||
end
|
||||
|
||||
if not has_migration then
|
||||
return
|
||||
return parent
|
||||
end
|
||||
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
||||
local dlg = create_rebind_keys_dlg()
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:set_parent(parent)
|
||||
parent:hide()
|
||||
dlg:show()
|
||||
ui.update()
|
||||
|
||||
return dlg
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
local SETTING_NAME = "no_mtg_notification"
|
||||
|
||||
function check_reinstall_mtg()
|
||||
function check_reinstall_mtg(parent)
|
||||
-- used to be in minetest.conf
|
||||
if core.settings:get_bool(SETTING_NAME) then
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
|
@ -19,14 +19,14 @@ function check_reinstall_mtg()
|
|||
end
|
||||
|
||||
if cache_settings:get_bool(SETTING_NAME) then
|
||||
return
|
||||
return parent
|
||||
end
|
||||
|
||||
local games = core.get_games()
|
||||
for _, game in ipairs(games) do
|
||||
if game.id == "minetest" then
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
return
|
||||
return parent
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,16 +40,16 @@ function check_reinstall_mtg()
|
|||
end
|
||||
if not mtg_world_found then
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
return
|
||||
return parent
|
||||
end
|
||||
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
||||
local dlg = create_reinstall_mtg_dlg()
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:set_parent(parent)
|
||||
parent:hide()
|
||||
dlg:show()
|
||||
ui.update()
|
||||
|
||||
return dlg
|
||||
end
|
||||
|
||||
local function get_formspec(dialogdata)
|
||||
|
@ -74,22 +74,22 @@ end
|
|||
|
||||
local function buttonhandler(this, fields)
|
||||
if fields.reinstall then
|
||||
local parent = this.parent
|
||||
|
||||
-- Don't set "no_mtg_notification" here so that the dialog will be shown
|
||||
-- again if downloading MTG fails for whatever reason.
|
||||
this:delete()
|
||||
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
||||
local dlg = create_contentdb_dlg(nil, "minetest/minetest")
|
||||
dlg:set_parent(maintab)
|
||||
maintab:hide()
|
||||
dlg:set_parent(parent)
|
||||
parent:hide()
|
||||
dlg:show()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.dismiss then
|
||||
cache_settings:set_bool("no_mtg_notification", true)
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
|
|
@ -112,8 +112,12 @@ local function init_globals()
|
|||
tv_main:show()
|
||||
ui.update()
|
||||
|
||||
check_reinstall_mtg()
|
||||
migrate_keybindings()
|
||||
-- synchronous, chain parents to only show one at a time
|
||||
local parent = tv_main
|
||||
parent = migrate_keybindings(parent)
|
||||
check_reinstall_mtg(parent)
|
||||
|
||||
-- asynchronous, will only be shown if we're still on "maintab"
|
||||
check_new_version()
|
||||
end
|
||||
|
||||
|
|
|
@ -25,3 +25,4 @@ This list is largely advisory and items may be reevaluated once the time comes.
|
|||
* remove built-in knockback and related functions entirely
|
||||
* remove `safe` parameter from `core.serialize`, always enforce `safe = true`.
|
||||
possibly error when `loadstring` calls are encountered in `core.deserialize`.
|
||||
* introduce strict type checking for all instances of `v3s16` / `v3f` read from Lua
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
Luanti Lua Client Modding API Reference 5.12.0
|
||||
Luanti Lua Client Modding API Reference 5.13.0
|
||||
==============================================
|
||||
|
||||
**WARNING**: if you're looking for the `minetest` namespace (e.g. `minetest.something`),
|
||||
it's now called `core` due to the renaming of Luanti (formerly Minetest).
|
||||
`minetest` will keep existing as an alias, so that old code won't break.
|
||||
|
||||
Note that `core` has already existed since version 0.4.10, so you can use it
|
||||
safely without breaking backwards compatibility.
|
||||
|
||||
* More information at <http://www.luanti.org/>
|
||||
* Luanti Documentation: <https://docs.luanti.org/>
|
||||
* Additional documentation: <https://docs.luanti.org/>
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
|
228
doc/lua_api.md
228
doc/lua_api.md
|
@ -5,9 +5,12 @@ Luanti Lua Modding API Reference
|
|||
it's now called `core` due to the renaming of Luanti (formerly Minetest).
|
||||
`minetest` will keep existing as an alias, so that old code won't break.
|
||||
|
||||
Note that `core` has already existed since version 0.4.10, so you can use it
|
||||
safely without breaking backwards compatibility.
|
||||
|
||||
* More information at <http://www.luanti.org/>
|
||||
* Luanti Documentation: <https://docs.luanti.org/>
|
||||
* (Unofficial) Minetest Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
|
||||
* Additional documentation: <https://docs.luanti.org/>
|
||||
* (Unofficial) Luanti Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
|
||||
* Modding tools: <https://github.com/luanti-org/modtools>
|
||||
|
||||
Introduction
|
||||
|
@ -312,6 +315,9 @@ due to their space savings.
|
|||
|
||||
Bone weights should be normalized, e.g. using ["normalize all" in Blender](https://docs.blender.org/manual/en/4.2/grease_pencil/modes/weight_paint/weights_menu.html#normalize-all).
|
||||
|
||||
Note that nodes using matrix transforms must not be animated.
|
||||
This also extends to bone overrides, which must not be applied to them.
|
||||
|
||||
You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator)
|
||||
to check whether a model is a valid glTF file.
|
||||
|
||||
|
@ -4581,11 +4587,13 @@ and offset the noise variation.
|
|||
|
||||
The final fractal value noise variation is created as follows:
|
||||
|
||||
```
|
||||
noise = offset + scale * (octave1 +
|
||||
octave2 * persistence +
|
||||
octave3 * persistence ^ 2 +
|
||||
octave4 * persistence ^ 3 +
|
||||
...)
|
||||
```
|
||||
|
||||
Noise Parameters
|
||||
----------------
|
||||
|
@ -4699,11 +4707,13 @@ with restraint.
|
|||
The absolute value of each octave's noise variation is used when combining the
|
||||
octaves. The final value noise variation is created as follows:
|
||||
|
||||
```
|
||||
noise = offset + scale * (abs(octave1) +
|
||||
abs(octave2) * persistence +
|
||||
abs(octave3) * persistence ^ 2 +
|
||||
abs(octave4) * persistence ^ 3 +
|
||||
...)
|
||||
```
|
||||
|
||||
### Format example
|
||||
|
||||
|
@ -4998,7 +5008,8 @@ A VoxelManip object can be created any time using either:
|
|||
If the optional position parameters are present for either of these routines,
|
||||
the specified region will be pre-loaded into the VoxelManip object on creation.
|
||||
Otherwise, the area of map you wish to manipulate must first be loaded into the
|
||||
VoxelManip object using `VoxelManip:read_from_map()`.
|
||||
VoxelManip object using `VoxelManip:read_from_map()`, or an empty one created
|
||||
with `VoxelManip:initialize()`.
|
||||
|
||||
Note that `VoxelManip:read_from_map()` returns two position vectors. The region
|
||||
formed by these positions indicate the minimum and maximum (respectively)
|
||||
|
@ -5009,14 +5020,14 @@ be queried any time after loading map data with `VoxelManip:get_emerged_area()`.
|
|||
Now that the VoxelManip object is populated with map data, your mod can fetch a
|
||||
copy of this data using either of two methods. `VoxelManip:get_node_at()`,
|
||||
which retrieves an individual node in a MapNode formatted table at the position
|
||||
requested is the simplest method to use, but also the slowest.
|
||||
requested. This is the simplest method to use, but also the slowest.
|
||||
|
||||
Nodes in a VoxelManip object may also be read in bulk to a flat array table
|
||||
using:
|
||||
|
||||
* `VoxelManip:get_data()` for node content (in Content ID form, see section
|
||||
[Content IDs]),
|
||||
* `VoxelManip:get_light_data()` for node light levels, and
|
||||
* `VoxelManip:get_light_data()` for node param (usually light levels), and
|
||||
* `VoxelManip:get_param2_data()` for the node type-dependent "param2" values.
|
||||
|
||||
See section [Flat array format] for more details.
|
||||
|
@ -5031,17 +5042,16 @@ internal state unless otherwise explicitly stated.
|
|||
Once the bulk data has been edited to your liking, the internal VoxelManip
|
||||
state can be set using:
|
||||
|
||||
* `VoxelManip:set_data()` for node content (in Content ID form, see section
|
||||
[Content IDs]),
|
||||
* `VoxelManip:set_light_data()` for node light levels, and
|
||||
* `VoxelManip:set_param2_data()` for the node type-dependent `param2` values.
|
||||
* `VoxelManip:set_data()` or
|
||||
* `VoxelManip:set_light_data()` or
|
||||
* `VoxelManip:set_param2_data()`
|
||||
|
||||
The parameter to each of the above three functions can use any table at all in
|
||||
the same flat array format as produced by `get_data()` etc. and is not required
|
||||
to be a table retrieved from `get_data()`.
|
||||
|
||||
Once the internal VoxelManip state has been modified to your liking, the
|
||||
changes can be committed back to the map by calling `VoxelManip:write_to_map()`
|
||||
changes can be committed back to the map by calling `VoxelManip:write_to_map()`.
|
||||
|
||||
### Flat array format
|
||||
|
||||
|
@ -5173,15 +5183,22 @@ inside the VoxelManip.
|
|||
Methods
|
||||
-------
|
||||
|
||||
* `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object
|
||||
* `read_from_map(p1, p2)`: Loads a part of the map into the VoxelManip object
|
||||
containing the region formed by `p1` and `p2`.
|
||||
* returns actual emerged `pmin`, actual emerged `pmax`
|
||||
* returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned)
|
||||
* Note that calling this multiple times will *add* to the area loaded in the
|
||||
VoxelManip, and not reset it.
|
||||
* `initialize(p1, p2, [node])`: Clears and resizes the VoxelManip object to
|
||||
comprise the region formed by `p1` and `p2`.
|
||||
* **No data** is read from the map, so you can use this to treat `VoxelManip`
|
||||
objects as general containers of node data.
|
||||
* `node`: if present the data will be filled with this node; if not it will
|
||||
be uninitialized
|
||||
* returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned)
|
||||
* (introduced in 5.13.0)
|
||||
* `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to
|
||||
the map.
|
||||
* **important**: data must be set using `VoxelManip:set_data()` before
|
||||
calling this.
|
||||
* **important**: you should call `set_data()` before this, or nothing will change.
|
||||
* if `light` is true, then lighting is automatically recalculated.
|
||||
The default value is true.
|
||||
If `light` is false, no light calculations happen, and you should correct
|
||||
|
@ -5242,6 +5259,15 @@ Methods
|
|||
where the engine will keep the map and the VM in sync automatically.
|
||||
* Note: this doesn't do what you think it does and is subject to removal. Don't use it!
|
||||
* `get_emerged_area()`: Returns actual emerged minimum and maximum positions.
|
||||
* "Emerged" does not imply that this region was actually loaded from the map,
|
||||
if `initialize()` has been used.
|
||||
* `close()`: Frees the data buffers associated with the VoxelManip object.
|
||||
It will become empty.
|
||||
* Since Lua's garbage collector is not aware of the potentially significant
|
||||
memory behind a VoxelManip, frequent VoxelManip usage can cause the server to
|
||||
run out of RAM. Therefore it's recommend to call this method once you're done
|
||||
with the VoxelManip.
|
||||
* (introduced in 5.13.0)
|
||||
|
||||
`VoxelArea`
|
||||
-----------
|
||||
|
@ -6562,13 +6588,10 @@ Environment access
|
|||
* The actual seed used is the noiseparams seed plus the world seed.
|
||||
* `core.get_value_noise(seeddiff, octaves, persistence, spread)`
|
||||
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
|
||||
* Return world-specific value noise
|
||||
* `core.get_perlin(noiseparams)`
|
||||
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
|
||||
* Return world-specific value noise (was not Perlin noise)
|
||||
* Deprecated: renamed to `core.get_value_noise` in version 5.12.0.
|
||||
* `core.get_perlin(seeddiff, octaves, persistence, spread)`
|
||||
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
|
||||
* Return world-specific value noise (was not Perlin noise)
|
||||
* Deprecated: renamed to `core.get_value_noise` in version 5.12.0.
|
||||
* `core.get_voxel_manip([pos1, pos2])`
|
||||
* Return voxel manipulator object.
|
||||
* Loads the manipulator from the map if positions are passed.
|
||||
|
@ -9061,78 +9084,6 @@ offering very strong randomness.
|
|||
* `get_state()`: return generator state encoded in string
|
||||
* `set_state(state_string)`: restore generator state from encoded string
|
||||
|
||||
`ValueNoise`
|
||||
-------------
|
||||
|
||||
A value noise generator.
|
||||
It can be created via `ValueNoise()` or `core.get_value_noise()`.
|
||||
For legacy reasons, it can also be created via `PerlinNoise()` or `core.get_perlin()`,
|
||||
but the implemented noise is not Perlin noise.
|
||||
For `core.get_value_noise()`, the actual seed used is the noiseparams seed
|
||||
plus the world seed, to create world-specific noise.
|
||||
|
||||
* `ValueNoise(noiseparams)
|
||||
* `ValueNoise(seed, octaves, persistence, spread)` (Deprecated)
|
||||
* `PerlinNoise(noiseparams)` (Deprecated)
|
||||
* `PerlinNoise(seed, octaves, persistence, spread)` (Deprecated)
|
||||
|
||||
* `core.get_value_noise(noiseparams)`
|
||||
* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (Deprecated)
|
||||
* `core.get_perlin(noiseparams)` (Deprecated)
|
||||
* `core.get_perlin(seeddiff, octaves, persistence, spread)` (Deprecated)
|
||||
|
||||
### Methods
|
||||
|
||||
* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}`
|
||||
* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}`
|
||||
|
||||
`ValueNoiseMap`
|
||||
----------------
|
||||
|
||||
A fast, bulk noise generator.
|
||||
|
||||
It can be created via `ValueNoiseMap(noiseparams, size)` or
|
||||
`core.get_value_noise_map(noiseparams, size)`.
|
||||
For legacy reasons, it can also be created via `PerlinNoiseMap(noiseparams, size)`
|
||||
or `core.get_perlin_map(noiseparams, size)`, but it is not Perlin noise.
|
||||
For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed
|
||||
plus the world seed, to create world-specific noise.
|
||||
|
||||
Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted
|
||||
for 2D noise, and it must be larger than 1 for 3D noise (otherwise
|
||||
`nil` is returned).
|
||||
|
||||
For each of the functions with an optional `buffer` parameter: If `buffer` is
|
||||
not nil, this table will be used to store the result instead of creating a new
|
||||
table.
|
||||
|
||||
### Methods
|
||||
|
||||
* `get_2d_map(pos)`: returns a `<size.x>` times `<size.y>` 2D array of 2D noise
|
||||
with values starting at `pos={x=,y=}`
|
||||
* `get_3d_map(pos)`: returns a `<size.x>` times `<size.y>` times `<size.z>`
|
||||
3D array of 3D noise with values starting at `pos={x=,y=,z=}`.
|
||||
* `get_2d_map_flat(pos, buffer)`: returns a flat `<size.x * size.y>` element
|
||||
array of 2D noise with values starting at `pos={x=,y=}`
|
||||
* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise
|
||||
* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result
|
||||
is stored internally.
|
||||
* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result
|
||||
is stored internally.
|
||||
* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array,
|
||||
returns a slice of the most recently computed noise results. The result slice
|
||||
begins at coordinates `slice_offset` and takes a chunk of `slice_size`.
|
||||
E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer
|
||||
offset y = 20:
|
||||
`noisevals = noise:get_map_slice({y=20}, {y=2})`
|
||||
It is important to note that `slice_offset` offset coordinates begin at 1,
|
||||
and are relative to the starting position of the most recently calculated
|
||||
noise.
|
||||
To grab a single vertical column of noise starting at map coordinates
|
||||
x = 1023, y=1000, z = 1000:
|
||||
`noise:calc_3d_map({x=1000, y=1000, z=1000})`
|
||||
`noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})`
|
||||
|
||||
`PlayerMetaRef`
|
||||
---------------
|
||||
|
||||
|
@ -9184,14 +9135,17 @@ end
|
|||
The map is loaded as the ray advances. If the map is modified after the
|
||||
`Raycast` is created, the changes may or may not have an effect on the object.
|
||||
|
||||
It can be created via `Raycast(pos1, pos2, objects, liquids)` or
|
||||
`core.raycast(pos1, pos2, objects, liquids)` where:
|
||||
It can be created via `Raycast(pos1, pos2, objects, liquids, pointabilities)`
|
||||
or `core.raycast(pos1, pos2, objects, liquids, pointabilities)` where:
|
||||
|
||||
* `pos1`: start of the ray
|
||||
* `pos2`: end of the ray
|
||||
* `objects`: if false, only nodes will be returned. Default is true.
|
||||
* `objects`: if false, only nodes will be returned. Default is `true`.
|
||||
* `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be
|
||||
returned. Default is false.
|
||||
returned. Default is `false`.
|
||||
* `pointabilities`: Allows overriding the `pointable` property of
|
||||
nodes and objects. Uses the same format as the `pointabilities` property
|
||||
of item definitions. Default is `nil`.
|
||||
|
||||
### Limitations
|
||||
|
||||
|
@ -9307,6 +9261,81 @@ to restrictions of JSON.
|
|||
|
||||
* All methods in MetaDataRef
|
||||
|
||||
`ValueNoise`
|
||||
-------------
|
||||
|
||||
A value noise generator.
|
||||
It can be created via `ValueNoise()` or `core.get_value_noise()`.
|
||||
For `core.get_value_noise()`, the actual seed used is the noiseparams seed
|
||||
plus the world seed, to create world-specific noise.
|
||||
|
||||
* `ValueNoise(noiseparams)`
|
||||
* `ValueNoise(seed, octaves, persistence, spread)` (deprecated)
|
||||
* `core.get_value_noise(noiseparams)`
|
||||
* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (deprecated)
|
||||
|
||||
These were previously called `PerlinNoise()` and `core.get_perlin()`, but the
|
||||
implemented noise was not Perlin noise. They were renamed in 5.12.0. The old
|
||||
names still exist as aliases.
|
||||
|
||||
### Methods
|
||||
|
||||
* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}`
|
||||
* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}`
|
||||
|
||||
`ValueNoiseMap`
|
||||
----------------
|
||||
|
||||
A fast, bulk noise generator.
|
||||
|
||||
It can be created via `ValueNoiseMap(noiseparams, size)` or
|
||||
`core.get_value_noise_map(noiseparams, size)`.
|
||||
For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed
|
||||
plus the world seed, to create world-specific noise.
|
||||
|
||||
These were previously called `PerlinNoiseMap()` and `core.get_perlin_map()`,
|
||||
but the implemented noise was not Perlin noise. They were renamed in 5.12.0.
|
||||
The old names still exist as aliases.
|
||||
|
||||
Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted
|
||||
for 2D noise, and it must be larger than 1 for 3D noise (otherwise
|
||||
`nil` is returned).
|
||||
|
||||
For each of the functions with an optional `buffer` parameter: If `buffer` is
|
||||
not nil, this table will be used to store the result instead of creating a new
|
||||
table.
|
||||
|
||||
### Methods
|
||||
|
||||
* `get_2d_map(pos)`: returns a `<size.x>` times `<size.y>` 2D array of 2D noise
|
||||
with values starting at `pos={x=,y=}`
|
||||
* `get_3d_map(pos)`: returns a `<size.x>` times `<size.y>` times `<size.z>`
|
||||
3D array of 3D noise with values starting at `pos={x=,y=,z=}`.
|
||||
* `get_2d_map_flat(pos, buffer)`: returns a flat `<size.x * size.y>` element
|
||||
array of 2D noise with values starting at `pos={x=,y=}`
|
||||
* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise
|
||||
* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result
|
||||
is stored internally.
|
||||
* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result
|
||||
is stored internally.
|
||||
* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array,
|
||||
returns a slice of the most recently computed noise results. The result slice
|
||||
begins at coordinates `slice_offset` and takes a chunk of `slice_size`.
|
||||
E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer
|
||||
offset `y = 20`:
|
||||
```lua
|
||||
noisevals = noise:get_map_slice({y=20}, {y=2})
|
||||
```
|
||||
It is important to note that `slice_offset` offset coordinates begin at 1,
|
||||
and are relative to the starting position of the most recently calculated
|
||||
noise.
|
||||
To grab a single vertical column of noise starting at map coordinates
|
||||
`x = 1023, y=1000, z = 1000`:
|
||||
```lua
|
||||
noise:calc_3d_map({x=1000, y=1000, z=1000})
|
||||
noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -10172,9 +10201,12 @@ Used by `core.register_node`.
|
|||
mesh = "",
|
||||
-- File name of mesh when using "mesh" drawtype
|
||||
-- The center of the node is the model origin.
|
||||
-- For legacy reasons, models in OBJ format use a scale of 1 node = 1 unit;
|
||||
-- all other model file formats use a scale of 1 node = 10 units,
|
||||
-- consistent with the scale used for entities.
|
||||
-- For legacy reasons, this uses a different scale depending on the mesh:
|
||||
-- 1. For glTF models: 10 units = 1 node (consistent with the scale for entities).
|
||||
-- 2. For obj models: 1 unit = 1 node.
|
||||
-- 3. For b3d and x models: 1 unit = 1 node if static, otherwise 10 units = 1 node.
|
||||
-- Using static glTF or obj models is recommended.
|
||||
-- You can use the `visual_scale` multiplier to achieve the expected scale.
|
||||
|
||||
selection_box = {
|
||||
-- see [Node boxes] for possibilities
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Luanti Lua Mainmenu API Reference 5.12.0
|
||||
Luanti Lua Mainmenu API Reference 5.13.0
|
||||
========================================
|
||||
|
||||
Introduction
|
||||
|
@ -23,8 +23,8 @@ Callbacks
|
|||
* `core.button_handler(fields)`: called when a button is pressed.
|
||||
* `fields` = `{name1 = value1, name2 = value2, ...}`
|
||||
* `core.event_handler(event)`
|
||||
* `event`: `"MenuQuit"`, `"KeyEnter"`, `"ExitButton"`, `"EditBoxEnter"` or
|
||||
`"FullscreenChange"`
|
||||
* `event`: `"MenuQuit"` (derived from `quit`) or `"FullscreenChange"`
|
||||
The main menu may issue custom events, such as `"Refresh"` (server list).
|
||||
* `core.on_before_close()`: called before the menu is closed, either to exit or
|
||||
to join a game
|
||||
|
||||
|
|
|
@ -12,4 +12,10 @@ Jordach (CC BY-SA 3.0):
|
|||
|
||||
Zeg9 (CC BY-SA 3.0):
|
||||
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
|
||||
|
|
BIN
games/devtest/mods/testentities/models/testentities_cool_guy.png
Normal file
BIN
games/devtest/mods/testentities/models/testentities_cool_guy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
2
games/devtest/mods/testentities/models/testentities_cool_guy.x
Executable file
2
games/devtest/mods/testentities/models/testentities_cool_guy.x
Executable file
File diff suppressed because one or more lines are too long
|
@ -102,6 +102,19 @@ core.register_entity("testentities:lava_flan", {
|
|||
end,
|
||||
})
|
||||
|
||||
core.register_entity("testentities:cool_guy", {
|
||||
initial_properties = {
|
||||
visual = "mesh",
|
||||
mesh = "testentities_cool_guy.x",
|
||||
textures = {
|
||||
"testentities_cool_guy.png"
|
||||
},
|
||||
},
|
||||
on_activate = function(self)
|
||||
self.object:set_animation({x = 0, y = 29}, 30, 0, true)
|
||||
end,
|
||||
})
|
||||
|
||||
-- Advanced visual tests
|
||||
|
||||
-- An entity for testing animated and yaw-modulated sprites
|
||||
|
|
|
@ -67,18 +67,6 @@ local function test_dynamic_media(cb, player)
|
|||
end
|
||||
unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true})
|
||||
|
||||
local function test_v3f_metatable(player)
|
||||
assert(vector.check(player:get_pos()))
|
||||
end
|
||||
unittests.register("test_v3f_metatable", test_v3f_metatable, {player=true})
|
||||
|
||||
local function test_v3s16_metatable(player, pos)
|
||||
local node = core.get_node(pos)
|
||||
local found_pos = core.find_node_near(pos, 0, node.name, true)
|
||||
assert(vector.check(found_pos))
|
||||
end
|
||||
unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true})
|
||||
|
||||
local function test_clear_meta(_, pos)
|
||||
local ref = core.get_meta(pos)
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ namespace scene
|
|||
//! Interface for an animated mesh.
|
||||
/** There are already simple implementations of this interface available so
|
||||
you don't have to implement this interface on your own if you need to:
|
||||
You might want to use irr::scene::SAnimatedMesh, irr::scene::SMesh,
|
||||
irr::scene::SMeshBuffer etc. */
|
||||
You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc.
|
||||
*/
|
||||
class IAnimatedMesh : public IMesh
|
||||
{
|
||||
public:
|
||||
|
@ -34,22 +34,8 @@ public:
|
|||
scene node the mesh is instantiated in.*/
|
||||
virtual void setAnimationSpeed(f32 fps) = 0;
|
||||
|
||||
//! Returns the IMesh interface for a frame.
|
||||
/** \param frame: Frame number, >= 0, <= getMaxFrameNumber()
|
||||
Linear interpolation is used if this is between two frames.
|
||||
\return Returns the animated mesh for the given frame */
|
||||
virtual IMesh *getMesh(f32 frame) = 0;
|
||||
|
||||
//! Returns the type of the animated mesh.
|
||||
/** In most cases it is not necessary to use this method.
|
||||
This is useful for making a safe downcast. For example,
|
||||
if getMeshType() returns EAMT_MD2 it's safe to cast the
|
||||
IAnimatedMesh to IAnimatedMeshMD2.
|
||||
\returns Type of the mesh. */
|
||||
E_ANIMATED_MESH_TYPE getMeshType() const override
|
||||
{
|
||||
return EAMT_UNKNOWN;
|
||||
}
|
||||
//! Returns the type of the animated mesh. Useful for safe downcasts.
|
||||
E_ANIMATED_MESH_TYPE getMeshType() const = 0;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -12,35 +12,8 @@ namespace irr
|
|||
{
|
||||
namespace scene
|
||||
{
|
||||
enum E_JOINT_UPDATE_ON_RENDER
|
||||
{
|
||||
//! do nothing
|
||||
EJUOR_NONE = 0,
|
||||
|
||||
//! get joints positions from the mesh (for attached nodes, etc)
|
||||
EJUOR_READ,
|
||||
|
||||
//! control joint positions in the mesh (eg. ragdolls, or set the animation from animateJoints() )
|
||||
EJUOR_CONTROL
|
||||
};
|
||||
|
||||
class IAnimatedMeshSceneNode;
|
||||
|
||||
//! Callback interface for catching events of ended animations.
|
||||
/** Implement this interface and use
|
||||
IAnimatedMeshSceneNode::setAnimationEndCallback to be able to
|
||||
be notified if an animation playback has ended.
|
||||
**/
|
||||
class IAnimationEndCallBack : public virtual IReferenceCounted
|
||||
{
|
||||
public:
|
||||
//! Will be called when the animation playback has ended.
|
||||
/** See IAnimatedMeshSceneNode::setAnimationEndCallback for
|
||||
more information.
|
||||
\param node: Node of which the animation has ended. */
|
||||
virtual void OnAnimationEnd(IAnimatedMeshSceneNode *node) = 0;
|
||||
};
|
||||
|
||||
//! Scene node capable of displaying an animated mesh.
|
||||
class IAnimatedMeshSceneNode : public ISceneNode
|
||||
{
|
||||
|
@ -120,11 +93,10 @@ public:
|
|||
/** When true the animations are played looped */
|
||||
virtual bool getLoopMode() const = 0;
|
||||
|
||||
//! Sets a callback interface which will be called if an animation playback has ended.
|
||||
/** Set this to 0 to disable the callback again.
|
||||
Please note that this will only be called when in non looped
|
||||
mode, see IAnimatedMeshSceneNode::setLoopMode(). */
|
||||
virtual void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) = 0;
|
||||
//! Will be called right after the joints have been animated,
|
||||
//! but before the transforms have been propagated recursively to children.
|
||||
virtual void setOnAnimateCallback(
|
||||
const std::function<void(f32 dtime)> &cb) = 0;
|
||||
|
||||
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
|
||||
/** In this way it is possible to change the materials a mesh
|
||||
|
@ -139,20 +111,15 @@ public:
|
|||
virtual void setMesh(IAnimatedMesh *mesh) = 0;
|
||||
|
||||
//! Returns the current mesh
|
||||
virtual IAnimatedMesh *getMesh(void) = 0;
|
||||
|
||||
//! Set how the joints should be updated on render
|
||||
virtual void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) = 0;
|
||||
virtual IAnimatedMesh *getMesh() = 0;
|
||||
|
||||
//! Sets the transition time in seconds
|
||||
/** Note: This needs to enable joints, and setJointmode set to
|
||||
EJUOR_CONTROL. You must call animateJoints(), or the mesh will
|
||||
not animate. */
|
||||
/** Note: You must call animateJoints(), or the mesh will not animate. */
|
||||
virtual void setTransitionTime(f32 Time) = 0;
|
||||
|
||||
//! animates the joints in the mesh based on the current frame.
|
||||
/** Also takes in to account transitions. */
|
||||
virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0;
|
||||
virtual void animateJoints() = 0;
|
||||
|
||||
//! render mesh ignoring its transformation.
|
||||
/** Culling is unaffected. */
|
||||
|
|
|
@ -11,85 +11,41 @@ namespace irr
|
|||
namespace scene
|
||||
{
|
||||
|
||||
//! Enumeration for different bone animation modes
|
||||
enum E_BONE_ANIMATION_MODE
|
||||
{
|
||||
//! The bone is usually animated, unless it's parent is not animated
|
||||
EBAM_AUTOMATIC = 0,
|
||||
|
||||
//! The bone is animated by the skin, if it's parent is not animated then animation will resume from this bone onward
|
||||
EBAM_ANIMATED,
|
||||
|
||||
//! The bone is not animated by the skin
|
||||
EBAM_UNANIMATED,
|
||||
|
||||
//! Not an animation mode, just here to count the available modes
|
||||
EBAM_COUNT
|
||||
|
||||
};
|
||||
|
||||
enum E_BONE_SKINNING_SPACE
|
||||
{
|
||||
//! local skinning, standard
|
||||
EBSS_LOCAL = 0,
|
||||
|
||||
//! global skinning
|
||||
EBSS_GLOBAL,
|
||||
|
||||
EBSS_COUNT
|
||||
};
|
||||
|
||||
//! Names for bone animation modes
|
||||
const c8 *const BoneAnimationModeNames[] = {
|
||||
"automatic",
|
||||
"animated",
|
||||
"unanimated",
|
||||
0,
|
||||
};
|
||||
|
||||
//! Interface for bones used for skeletal animation.
|
||||
/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */
|
||||
class IBoneSceneNode : public ISceneNode
|
||||
{
|
||||
public:
|
||||
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) :
|
||||
ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {}
|
||||
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
|
||||
s32 id = -1, u32 boneIndex = 0,
|
||||
const std::optional<std::string> &boneName = std::nullopt)
|
||||
:
|
||||
ISceneNode(parent, mgr, id),
|
||||
BoneIndex(boneIndex)
|
||||
{
|
||||
setName(boneName);
|
||||
}
|
||||
|
||||
//! Get the index of the bone
|
||||
virtual u32 getBoneIndex() const = 0;
|
||||
//! Returns the index of the bone
|
||||
u32 getBoneIndex() const
|
||||
{
|
||||
return BoneIndex;
|
||||
}
|
||||
|
||||
//! Sets the animation mode of the bone.
|
||||
/** \return True if successful. (Unused) */
|
||||
virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0;
|
||||
//! returns the axis aligned bounding box of this node
|
||||
const core::aabbox3d<f32> &getBoundingBox() const override
|
||||
{
|
||||
return Box;
|
||||
}
|
||||
|
||||
//! Gets the current animation mode of the bone
|
||||
virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0;
|
||||
const u32 BoneIndex;
|
||||
|
||||
//! Get the axis aligned bounding box of this node
|
||||
const core::aabbox3d<f32> &getBoundingBox() const override = 0;
|
||||
|
||||
//! Returns the relative transformation of the scene node.
|
||||
// virtual core::matrix4 getRelativeTransformation() const = 0;
|
||||
|
||||
//! The animation method.
|
||||
void OnAnimate(u32 timeMs) override = 0;
|
||||
// Bogus box; bone scene nodes are not rendered anyways.
|
||||
static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};
|
||||
|
||||
//! The render method.
|
||||
/** Does nothing as bones are not visible. */
|
||||
void render() override {}
|
||||
|
||||
//! How the relative transformation of the bone is used
|
||||
virtual void setSkinningSpace(E_BONE_SKINNING_SPACE space) = 0;
|
||||
|
||||
//! How the relative transformation of the bone is used
|
||||
virtual E_BONE_SKINNING_SPACE getSkinningSpace() const = 0;
|
||||
|
||||
//! Updates the absolute position based on the relative and the parents position
|
||||
virtual void updateAbsolutePositionOfAllChildren() = 0;
|
||||
|
||||
s32 positionHint;
|
||||
s32 scaleHint;
|
||||
s32 rotationHint;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE
|
|||
//! Unknown animated mesh type.
|
||||
EAMT_UNKNOWN = 0,
|
||||
|
||||
//! Quake 2 MD2 model file
|
||||
EAMT_MD2,
|
||||
|
||||
//! Quake 3 MD3 model file
|
||||
EAMT_MD3,
|
||||
|
||||
//! Maya .obj static model
|
||||
EAMT_OBJ,
|
||||
|
||||
//! Quake 3 .bsp static Map
|
||||
EAMT_BSP,
|
||||
|
||||
//! 3D Studio .3ds file
|
||||
EAMT_3DS,
|
||||
|
||||
//! My3D Mesh, the file format by Zhuck Dimitry
|
||||
EAMT_MY3D,
|
||||
|
||||
//! Pulsar LMTools .lmts file. This Irrlicht loader was written by Jonas Petersen
|
||||
EAMT_LMTS,
|
||||
|
||||
//! Cartography Shop .csm file. This loader was created by Saurav Mohapatra.
|
||||
EAMT_CSM,
|
||||
|
||||
//! .oct file for Paul Nette's FSRad or from Murphy McCauley's Blender .oct exporter.
|
||||
/** The oct file format contains 3D geometry and lightmaps and
|
||||
can be loaded directly by Irrlicht */
|
||||
EAMT_OCT,
|
||||
|
||||
//! Halflife MDL model file
|
||||
EAMT_MDL_HALFLIFE,
|
||||
|
||||
//! generic skinned mesh
|
||||
EAMT_SKINNED,
|
||||
|
||||
|
@ -119,9 +87,7 @@ public:
|
|||
virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
|
||||
|
||||
//! Returns the type of the meshes.
|
||||
/** This is useful for making a safe downcast. For example,
|
||||
if getMeshType() returns EAMT_MD2 it's safe to cast the
|
||||
IMesh to IAnimatedMeshMD2.
|
||||
/** This is useful for making a safe downcast.
|
||||
Note: It's no longer just about animated meshes, that name has just historical reasons.
|
||||
\returns Type of the mesh */
|
||||
virtual E_ANIMATED_MESH_TYPE getMeshType() const
|
||||
|
|
|
@ -66,26 +66,6 @@ public:
|
|||
IReferenceCounted::drop() for more information. */
|
||||
virtual SMesh *createMeshCopy(IMesh *mesh) const = 0;
|
||||
|
||||
//! Get amount of polygons in mesh.
|
||||
/** \param mesh Input mesh
|
||||
\return Number of polygons in mesh. */
|
||||
virtual s32 getPolyCount(IMesh *mesh) const = 0;
|
||||
|
||||
//! Get amount of polygons in mesh.
|
||||
/** \param mesh Input mesh
|
||||
\return Number of polygons in mesh. */
|
||||
virtual s32 getPolyCount(IAnimatedMesh *mesh) const = 0;
|
||||
|
||||
//! Create a new AnimatedMesh and adds the mesh to it
|
||||
/** \param mesh Input mesh
|
||||
\param type The type of the animated mesh to create.
|
||||
\return Newly created animated mesh with mesh as its only
|
||||
content. When you don't need the animated mesh anymore, you
|
||||
should call IAnimatedMesh::drop(). See
|
||||
IReferenceCounted::drop() for more information. */
|
||||
virtual IAnimatedMesh *createAnimatedMesh(IMesh *mesh,
|
||||
scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) const = 0;
|
||||
|
||||
//! Apply a manipulator on the Meshbuffer
|
||||
/** \param func A functor defining the mesh manipulation.
|
||||
\param buffer The Meshbuffer to apply the manipulator to.
|
||||
|
|
|
@ -32,7 +32,7 @@ public:
|
|||
|
||||
//! Get the currently defined mesh for display.
|
||||
/** \return Pointer to mesh which is displayed by this node. */
|
||||
virtual IMesh *getMesh(void) = 0;
|
||||
virtual IMesh *getMesh() = 0;
|
||||
|
||||
//! Sets if the scene node should not copy the materials of the mesh but use them directly.
|
||||
/** In this way it is possible to change the materials of a mesh
|
||||
|
|
|
@ -94,16 +94,12 @@ public:
|
|||
\param timeMs Current time in milliseconds. */
|
||||
virtual void OnAnimate(u32 timeMs)
|
||||
{
|
||||
if (IsVisible) {
|
||||
// update absolute position
|
||||
updateAbsolutePosition();
|
||||
if (!IsVisible && Children.empty())
|
||||
return;
|
||||
|
||||
// perform the post render process on all children
|
||||
|
||||
ISceneNodeList::iterator it = Children.begin();
|
||||
for (; it != Children.end(); ++it)
|
||||
(*it)->OnAnimate(timeMs);
|
||||
}
|
||||
updateAbsolutePosition();
|
||||
for (auto *child : Children)
|
||||
child->OnAnimate(timeMs);
|
||||
}
|
||||
|
||||
//! Renders the node.
|
||||
|
|
|
@ -172,7 +172,7 @@ public:
|
|||
\return Pointer to the texture, or 0 if the texture
|
||||
could not be loaded. This pointer should not be dropped. See
|
||||
IReferenceCounted::drop() for more information. */
|
||||
virtual ITexture *getTexture(const io::path &filename) = 0;
|
||||
[[deprecated]] virtual ITexture *getTexture(const io::path &filename) = 0;
|
||||
|
||||
//! Get access to a named texture.
|
||||
/** Loads the texture from disk if it is not
|
||||
|
@ -184,7 +184,7 @@ public:
|
|||
\return Pointer to the texture, or 0 if the texture
|
||||
could not be loaded. This pointer should not be dropped. See
|
||||
IReferenceCounted::drop() for more information. */
|
||||
virtual ITexture *getTexture(io::IReadFile *file) = 0;
|
||||
[[deprecated]] virtual ITexture *getTexture(io::IReadFile *file) = 0;
|
||||
|
||||
//! Returns amount of textures currently loaded
|
||||
/** \return Amount of textures currently loaded */
|
||||
|
|
|
@ -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
|
|
@ -5,7 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "IMesh.h"
|
||||
#include "IAnimatedMesh.h"
|
||||
#include "IMeshBuffer.h"
|
||||
#include "aabbox3d.h"
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace irr
|
|||
namespace scene
|
||||
{
|
||||
//! Simple implementation of the IMesh interface.
|
||||
struct SMesh final : public IMesh
|
||||
struct SMesh final : public IAnimatedMesh
|
||||
{
|
||||
//! constructor
|
||||
SMesh() {}
|
||||
|
@ -134,6 +134,15 @@ struct SMesh final : public IMesh
|
|||
|
||||
//! The bounding box of this mesh
|
||||
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
|
||||
|
||||
// Implement animated mesh interface as a static mesh.
|
||||
// Slightly hacky: Eventually should be consolidated with SSkinnedMesh,
|
||||
// with all the animation-related parts behind an optional.
|
||||
|
||||
virtual f32 getMaxFrameNumber() const override { return 0.0f; }
|
||||
virtual f32 getAnimationSpeed() const override { return 0.0f; }
|
||||
virtual void setAnimationSpeed(f32 fps) override {}
|
||||
E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; }
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -8,11 +8,18 @@
|
|||
#include "ISceneManager.h"
|
||||
#include "SMeshBuffer.h"
|
||||
#include "SSkinMeshBuffer.h"
|
||||
#include "aabbox3d.h"
|
||||
#include "irrMath.h"
|
||||
#include "irrTypes.h"
|
||||
#include "matrix4.h"
|
||||
#include "quaternion.h"
|
||||
#include "vector3d.h"
|
||||
#include "Transform.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
|
@ -26,12 +33,20 @@ class ISceneManager;
|
|||
class SkinnedMesh : public IAnimatedMesh
|
||||
{
|
||||
public:
|
||||
|
||||
enum class SourceFormat {
|
||||
B3D,
|
||||
X,
|
||||
GLTF,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
//! constructor
|
||||
SkinnedMesh() :
|
||||
SkinnedMesh(SourceFormat src_format) :
|
||||
EndFrame(0.f), FramesPerSecond(25.f),
|
||||
LastAnimatedFrame(-1), SkinnedLastFrame(false),
|
||||
HasAnimation(false), PreparedForSkinning(false),
|
||||
AnimateNormals(true), HardwareSkinning(false)
|
||||
AnimateNormals(true),
|
||||
SrcFormat(src_format)
|
||||
{
|
||||
SkinningBuffers = &LocalBuffers;
|
||||
}
|
||||
|
@ -39,6 +54,10 @@ public:
|
|||
//! destructor
|
||||
virtual ~SkinnedMesh();
|
||||
|
||||
//! The source (file) format the mesh was loaded from.
|
||||
//! Important for legacy reasons pertaining to different mesh loader behavior.
|
||||
SourceFormat getSourceFormat() const { return SrcFormat; }
|
||||
|
||||
//! If the duration is 0, it is a static (=non animated) mesh.
|
||||
f32 getMaxFrameNumber() const override;
|
||||
|
||||
|
@ -51,14 +70,12 @@ public:
|
|||
The actual speed is set in the scene node the mesh is instantiated in.*/
|
||||
void setAnimationSpeed(f32 fps) override;
|
||||
|
||||
//! returns the animated mesh for the given frame
|
||||
IMesh *getMesh(f32) override;
|
||||
//! Turns the given array of local matrices into an array of global matrices
|
||||
//! by multiplying with respective parent matrices.
|
||||
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const;
|
||||
|
||||
//! Animates joints based on frame input
|
||||
void animateMesh(f32 frame);
|
||||
|
||||
//! Performs a software skin on this mesh based of joint positions
|
||||
void skinMesh();
|
||||
//! Performs a software skin on this mesh based on the given joint matrices
|
||||
void skinMesh(const std::vector<core::matrix4> &animated_transforms);
|
||||
|
||||
//! returns amount of mesh buffers.
|
||||
u32 getMeshBufferCount() const override;
|
||||
|
@ -76,14 +93,15 @@ public:
|
|||
|
||||
void setTextureSlot(u32 meshbufNr, u32 textureSlot);
|
||||
|
||||
//! returns an axis aligned bounding box
|
||||
//! Returns bounding box of the mesh *in static pose*.
|
||||
const core::aabbox3d<f32> &getBoundingBox() const override {
|
||||
return BoundingBox;
|
||||
// TODO ideally we shouldn't be forced to implement this
|
||||
return StaticPoseBox;
|
||||
}
|
||||
|
||||
//! set user axis aligned bounding box
|
||||
//! Set bounding box of the mesh *in static pose*.
|
||||
void setBoundingBox(const core::aabbox3df &box) override {
|
||||
BoundingBox = box;
|
||||
StaticPoseBox = box;
|
||||
}
|
||||
|
||||
//! set the hardware mapping hint, for driver
|
||||
|
@ -127,28 +145,15 @@ public:
|
|||
return !HasAnimation;
|
||||
}
|
||||
|
||||
//! Allows to enable hardware skinning.
|
||||
/* This feature is not implemented in Irrlicht yet */
|
||||
bool setHardwareSkinning(bool on);
|
||||
|
||||
//! Refreshes vertex data cached in joints such as positions and normals
|
||||
void refreshJointCache();
|
||||
|
||||
//! Moves the mesh into static position.
|
||||
void resetAnimation();
|
||||
|
||||
void updateBoundingBox();
|
||||
|
||||
//! Recovers the joints from the mesh
|
||||
void recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes);
|
||||
|
||||
//! Transfers the joint data to the mesh
|
||||
void transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes);
|
||||
|
||||
//! Creates an array of joints from this mesh as children of node
|
||||
void addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
|
||||
IAnimatedMeshSceneNode *node,
|
||||
ISceneManager *smgr);
|
||||
std::vector<IBoneSceneNode *> addJoints(
|
||||
IAnimatedMeshSceneNode *node, ISceneManager *smgr);
|
||||
|
||||
//! A vertex weight
|
||||
struct SWeight
|
||||
|
@ -223,7 +228,7 @@ public:
|
|||
|
||||
static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) {
|
||||
core::quaternion result;
|
||||
result.slerp(from, to, time, 0.001f);
|
||||
result.slerp(from, to, time);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -275,15 +280,14 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
void updateTransform(f32 frame,
|
||||
core::vector3df &t, core::quaternion &r, core::vector3df &s) const
|
||||
void updateTransform(f32 frame, core::Transform &transform) const
|
||||
{
|
||||
if (auto pos = position.get(frame))
|
||||
t = *pos;
|
||||
transform.translation = *pos;
|
||||
if (auto rot = rotation.get(frame))
|
||||
r = *rot;
|
||||
transform.rotation = *rot;
|
||||
if (auto scl = scale.get(frame))
|
||||
s = *scl;
|
||||
transform.scale = *scl;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
|
@ -296,16 +300,34 @@ public:
|
|||
//! Joints
|
||||
struct SJoint
|
||||
{
|
||||
SJoint() : GlobalSkinningSpace(false) {}
|
||||
SJoint() {}
|
||||
|
||||
//! The name of this joint
|
||||
std::optional<std::string> Name;
|
||||
|
||||
//! Local matrix of this joint
|
||||
core::matrix4 LocalMatrix;
|
||||
//! Local transformation to be set by loaders. Mutated by animation.
|
||||
using VariantTransform = std::variant<core::Transform, core::matrix4>;
|
||||
VariantTransform transform{core::Transform{}};
|
||||
|
||||
VariantTransform animate(f32 frame) const {
|
||||
if (keys.empty())
|
||||
return transform;
|
||||
|
||||
if (std::holds_alternative<core::matrix4>(transform)) {
|
||||
// .x lets animations override matrix transforms entirely,
|
||||
// which is what we implement here.
|
||||
// .gltf does not allow animation of nodes using matrix transforms.
|
||||
// Note that a decomposition into a TRS transform need not exist!
|
||||
core::Transform trs;
|
||||
keys.updateTransform(frame, trs);
|
||||
return {trs};
|
||||
}
|
||||
|
||||
auto trs = std::get<core::Transform>(transform);
|
||||
keys.updateTransform(frame, trs);
|
||||
return {trs};
|
||||
}
|
||||
|
||||
//! List of child joints
|
||||
std::vector<SJoint *> Children;
|
||||
|
||||
//! List of attached meshes
|
||||
std::vector<u32> AttachedMeshes;
|
||||
|
@ -316,42 +338,49 @@ public:
|
|||
//! Skin weights
|
||||
std::vector<SWeight> Weights;
|
||||
|
||||
//! Bounding box of all affected vertices, in local space
|
||||
core::aabbox3df LocalBoundingBox{{0, 0, 0}};
|
||||
|
||||
//! Unnecessary for loaders, will be overwritten on finalize
|
||||
core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data.
|
||||
core::matrix4 GlobalAnimatedMatrix;
|
||||
core::matrix4 LocalAnimatedMatrix;
|
||||
|
||||
//! These should be set by loaders.
|
||||
core::vector3df Animatedposition;
|
||||
core::vector3df Animatedscale;
|
||||
core::quaternion Animatedrotation;
|
||||
|
||||
// The .x and .gltf formats pre-calculate this
|
||||
std::optional<core::matrix4> GlobalInversedMatrix;
|
||||
private:
|
||||
//! Internal members used by SkinnedMesh
|
||||
friend class SkinnedMesh;
|
||||
|
||||
bool GlobalSkinningSpace;
|
||||
void setParent(SJoint *parent) {
|
||||
ParentJointID = parent ? parent->JointID : std::optional<u16>{};
|
||||
}
|
||||
|
||||
u16 JointID; // TODO refactor away: pointers -> IDs (problem: .x loader abuses SJoint)
|
||||
std::optional<u16> ParentJointID;
|
||||
};
|
||||
|
||||
//! Animates joints based on frame input
|
||||
std::vector<SJoint::VariantTransform> animateMesh(f32 frame);
|
||||
|
||||
//! Calculates a bounding box given an animation in the form of global joint transforms.
|
||||
core::aabbox3df calculateBoundingBox(
|
||||
const std::vector<core::matrix4> &global_transforms);
|
||||
|
||||
void recalculateBaseBoundingBoxes();
|
||||
|
||||
const std::vector<SJoint *> &getAllJoints() const {
|
||||
return AllJoints;
|
||||
}
|
||||
|
||||
protected:
|
||||
void checkForAnimation();
|
||||
bool checkForAnimation() const;
|
||||
|
||||
void topoSortJoints();
|
||||
|
||||
void prepareForSkinning();
|
||||
|
||||
void calculateStaticBoundingBox();
|
||||
void calculateJointBoundingBoxes();
|
||||
void calculateBufferBoundingBoxes();
|
||||
|
||||
void normalizeWeights();
|
||||
|
||||
void buildAllLocalAnimatedMatrices();
|
||||
|
||||
void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0);
|
||||
|
||||
void calculateGlobalMatrices(SJoint *Joint, SJoint *ParentJoint);
|
||||
|
||||
void skinJoint(SJoint *Joint, SJoint *ParentJoint);
|
||||
|
||||
void calculateTangents(core::vector3df &normal,
|
||||
core::vector3df &tangent, core::vector3df &binormal,
|
||||
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
|
||||
|
@ -363,31 +392,33 @@ protected:
|
|||
//! Mapping from meshbuffer number to bindable texture slot
|
||||
std::vector<u32> TextureSlots;
|
||||
|
||||
//! Joints, topologically sorted (parents come before their children).
|
||||
std::vector<SJoint *> AllJoints;
|
||||
std::vector<SJoint *> RootJoints;
|
||||
|
||||
// bool can't be used here because std::vector<bool>
|
||||
// doesn't allow taking a reference to individual elements.
|
||||
std::vector<std::vector<char>> Vertices_Moved;
|
||||
|
||||
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
|
||||
//! Bounding box of just the static parts of the mesh
|
||||
core::aabbox3df StaticPartsBox{{0, 0, 0}};
|
||||
|
||||
//! Bounding box of the mesh in static pose
|
||||
core::aabbox3df StaticPoseBox{{0, 0, 0}};
|
||||
|
||||
f32 EndFrame;
|
||||
f32 FramesPerSecond;
|
||||
|
||||
f32 LastAnimatedFrame;
|
||||
bool SkinnedLastFrame;
|
||||
|
||||
bool HasAnimation;
|
||||
bool PreparedForSkinning;
|
||||
bool AnimateNormals;
|
||||
bool HardwareSkinning;
|
||||
|
||||
SourceFormat SrcFormat;
|
||||
};
|
||||
|
||||
// Interface for mesh loaders
|
||||
class SkinnedMeshBuilder : public SkinnedMesh {
|
||||
public:
|
||||
SkinnedMeshBuilder() : SkinnedMesh() {}
|
||||
SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {}
|
||||
|
||||
//! loaders should call this after populating the mesh
|
||||
// returns *this, so do not try to drop the mesh builder instance
|
||||
|
|
42
irr/include/Transform.h
Normal file
42
irr/include/Transform.h
Normal 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
|
|
@ -180,7 +180,7 @@ public:
|
|||
linear interpolation.
|
||||
*/
|
||||
quaternion &slerp(quaternion q1, quaternion q2,
|
||||
f32 time, f32 threshold = .05f);
|
||||
f32 time, f32 threshold = .001f);
|
||||
|
||||
//! Set this quaternion to represent a rotation from angle and axis.
|
||||
/** Axis must be unit length.
|
||||
|
|
|
@ -3,9 +3,13 @@
|
|||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#include "CAnimatedMeshSceneNode.h"
|
||||
#include "CBoneSceneNode.h"
|
||||
#include "IVideoDriver.h"
|
||||
#include "ISceneManager.h"
|
||||
#include "S3DVertex.h"
|
||||
#include "Transform.h"
|
||||
#include "irrTypes.h"
|
||||
#include "matrix4.h"
|
||||
#include "os.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "IDummyTransformationSceneNode.h"
|
||||
|
@ -17,6 +21,9 @@
|
|||
#include "IFileSystem.h"
|
||||
#include "quaternion.h"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
|
@ -30,13 +37,13 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
|
|||
const core::vector3df &rotation,
|
||||
const core::vector3df &scale) :
|
||||
IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale),
|
||||
Mesh(0),
|
||||
Mesh(nullptr),
|
||||
StartFrame(0), EndFrame(0), FramesPerSecond(0.025f),
|
||||
CurrentFrameNr(0.f), LastTimeMs(0),
|
||||
TransitionTime(0), Transiting(0.f), TransitingBlend(0.f),
|
||||
JointMode(EJUOR_NONE), JointsUsed(false),
|
||||
JointsUsed(false),
|
||||
Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false),
|
||||
LoopCallBack(0), PassCount(0)
|
||||
PassCount(0)
|
||||
{
|
||||
setMesh(mesh);
|
||||
}
|
||||
|
@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
|
|||
//! destructor
|
||||
CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode()
|
||||
{
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->drop();
|
||||
if (Mesh)
|
||||
Mesh->drop();
|
||||
}
|
||||
|
@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
|
|||
if (FramesPerSecond > 0.f) { // forwards...
|
||||
if (CurrentFrameNr > EndFrame)
|
||||
CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame);
|
||||
} else // backwards...
|
||||
{
|
||||
} else { // backwards...
|
||||
if (CurrentFrameNr < StartFrame)
|
||||
CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame);
|
||||
}
|
||||
|
@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
|
|||
|
||||
CurrentFrameNr += timeMs * FramesPerSecond;
|
||||
if (FramesPerSecond > 0.f) { // forwards...
|
||||
if (CurrentFrameNr > EndFrame) {
|
||||
CurrentFrameNr = EndFrame;
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->OnAnimationEnd(this);
|
||||
}
|
||||
} else // backwards...
|
||||
{
|
||||
if (CurrentFrameNr < StartFrame) {
|
||||
CurrentFrameNr = StartFrame;
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->OnAnimationEnd(this);
|
||||
}
|
||||
CurrentFrameNr = std::min(CurrentFrameNr, EndFrame);
|
||||
} else { // backwards...
|
||||
CurrentFrameNr = std::max(CurrentFrameNr, StartFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode()
|
|||
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
|
||||
{
|
||||
if (Mesh->getMeshType() != EAMT_SKINNED) {
|
||||
return Mesh->getMesh(getFrameNr());
|
||||
} else {
|
||||
// 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;
|
||||
return Mesh;
|
||||
}
|
||||
|
||||
// As multiple scene nodes may be sharing the same skinned mesh, we have to
|
||||
// re-animate it every frame to ensure that this node gets the mesh that it needs.
|
||||
|
||||
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.
|
||||
|
@ -201,7 +176,28 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs)
|
|||
buildFrameNr(timeMs - LastTimeMs);
|
||||
LastTimeMs = timeMs;
|
||||
|
||||
// This needs to be done on animate, which is called recursively *before*
|
||||
// anything is rendered so that the transformations of children are up to date
|
||||
animateJoints();
|
||||
|
||||
// Copy old transforms *before* bone overrides have been applied.
|
||||
// TODO if there are no bone overrides or no animation blending, this is unnecessary.
|
||||
copyOldTransforms();
|
||||
|
||||
if (OnAnimateCallback)
|
||||
OnAnimateCallback(timeMs / 1000.0f);
|
||||
|
||||
IAnimatedMeshSceneNode::OnAnimate(timeMs);
|
||||
|
||||
if (auto *skinnedMesh = dynamic_cast<SkinnedMesh*>(Mesh)) {
|
||||
for (u16 i = 0; i < PerJoint.SceneNodes.size(); ++i)
|
||||
PerJoint.GlobalMatrices[i] = PerJoint.SceneNodes[i]->getRelativeTransformation();
|
||||
assert(PerJoint.GlobalMatrices.size() == skinnedMesh->getJointCount());
|
||||
skinnedMesh->calculateGlobalMatrices(PerJoint.GlobalMatrices);
|
||||
Box = skinnedMesh->calculateBoundingBox(PerJoint.GlobalMatrices);
|
||||
} else {
|
||||
Box = Mesh->getBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
//! renders the node.
|
||||
|
@ -218,15 +214,7 @@ void CAnimatedMeshSceneNode::render()
|
|||
++PassCount;
|
||||
|
||||
scene::IMesh *m = getMeshForCurrentFrame();
|
||||
|
||||
if (m) {
|
||||
Box = m->getBoundingBox();
|
||||
} else {
|
||||
#ifdef _DEBUG
|
||||
os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
assert(m);
|
||||
|
||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
||||
|
||||
|
@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render()
|
|||
if (DebugDataVisible & scene::EDS_SKELETON) {
|
||||
if (Mesh->getMeshType() == EAMT_SKINNED) {
|
||||
// draw skeleton
|
||||
|
||||
for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) {
|
||||
for (const auto *childJoint : joint->Children) {
|
||||
driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(),
|
||||
childJoint->GlobalAnimatedMatrix.getTranslation(),
|
||||
const auto &joints = (static_cast<SkinnedMesh *>(Mesh))->getAllJoints();
|
||||
for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) {
|
||||
const auto translation = PerJoint.GlobalMatrices[i].getTranslation();
|
||||
if (auto pjid = joints[i]->ParentJointID) {
|
||||
const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation();
|
||||
driver->draw3DLine(parent_translation, translation,
|
||||
video::SColor(255, 51, 66, 255));
|
||||
}
|
||||
}
|
||||
|
@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (JointChildSceneNodes.size() <= *number) {
|
||||
if (PerJoint.SceneNodes.size() <= *number) {
|
||||
os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return JointChildSceneNodes[*number];
|
||||
return PerJoint.SceneNodes[*number];
|
||||
}
|
||||
|
||||
//! Returns a pointer to a child node, which has the same transformation as
|
||||
|
@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID)
|
|||
|
||||
checkJoints();
|
||||
|
||||
if (JointChildSceneNodes.size() <= jointID) {
|
||||
if (PerJoint.SceneNodes.size() <= jointID) {
|
||||
os::Printer::log("Joint not loaded into node", ELL_WARNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return JointChildSceneNodes[jointID];
|
||||
return PerJoint.SceneNodes[jointID];
|
||||
}
|
||||
|
||||
//! Gets joint count.
|
||||
|
@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child)
|
|||
{
|
||||
if (ISceneNode::removeChild(child)) {
|
||||
if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created
|
||||
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) {
|
||||
if (JointChildSceneNodes[i] == child) {
|
||||
JointChildSceneNodes[i] = 0; // remove link to child
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||
if (PerJoint.SceneNodes[i] == child) {
|
||||
PerJoint.SceneNodes[i] = 0; // remove link to child
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const
|
|||
return Looping;
|
||||
}
|
||||
|
||||
//! Sets a callback interface which will be called if an animation
|
||||
//! playback has ended. Set this to 0 to disable the callback again.
|
||||
void CAnimatedMeshSceneNode::setAnimationEndCallback(IAnimationEndCallBack *callback)
|
||||
{
|
||||
if (callback == LoopCallBack)
|
||||
return;
|
||||
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->drop();
|
||||
|
||||
LoopCallBack = callback;
|
||||
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->grab();
|
||||
}
|
||||
|
||||
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
|
||||
void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly)
|
||||
{
|
||||
|
@ -525,18 +498,15 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
|
|||
// get materials and bounding box
|
||||
Box = Mesh->getBoundingBox();
|
||||
|
||||
IMesh *m = Mesh->getMesh(0);
|
||||
if (m) {
|
||||
Materials.clear();
|
||||
Materials.reallocate(m->getMeshBufferCount());
|
||||
Materials.clear();
|
||||
Materials.reallocate(Mesh->getMeshBufferCount());
|
||||
|
||||
for (u32 i = 0; i < m->getMeshBufferCount(); ++i) {
|
||||
IMeshBuffer *mb = m->getMeshBuffer(i);
|
||||
if (mb)
|
||||
Materials.push_back(mb->getMaterial());
|
||||
else
|
||||
Materials.push_back(video::SMaterial());
|
||||
}
|
||||
for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) {
|
||||
IMeshBuffer *mb = Mesh->getMeshBuffer(i);
|
||||
if (mb)
|
||||
Materials.push_back(mb->getMaterial());
|
||||
else
|
||||
Materials.push_back(video::SMaterial());
|
||||
}
|
||||
|
||||
// clean up joint nodes
|
||||
|
@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition()
|
|||
IAnimatedMeshSceneNode::updateAbsolutePosition();
|
||||
}
|
||||
|
||||
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
|
||||
void CAnimatedMeshSceneNode::setJointMode(E_JOINT_UPDATE_ON_RENDER mode)
|
||||
{
|
||||
checkJoints();
|
||||
JointMode = mode;
|
||||
}
|
||||
|
||||
//! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2)
|
||||
//! Sets the transition time in seconds (note: This needs to enable joints)
|
||||
//! you must call animateJoints(), or the mesh will not animate
|
||||
void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
||||
{
|
||||
|
@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
|||
if (TransitionTime == ttime)
|
||||
return;
|
||||
TransitionTime = ttime;
|
||||
if (ttime != 0)
|
||||
setJointMode(EJUOR_CONTROL);
|
||||
else
|
||||
setJointMode(EJUOR_NONE);
|
||||
}
|
||||
|
||||
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
||||
|
@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable)
|
|||
RenderFromIdentity = enable;
|
||||
}
|
||||
|
||||
//! updates the joint positions of this mesh
|
||||
void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions)
|
||||
void CAnimatedMeshSceneNode::addJoints()
|
||||
{
|
||||
if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) {
|
||||
checkJoints();
|
||||
const f32 frame = getFrameNr(); // old?
|
||||
const auto &joints = static_cast<SkinnedMesh*>(Mesh)->getAllJoints();
|
||||
PerJoint.setN(joints.size());
|
||||
PerJoint.SceneNodes.clear();
|
||||
PerJoint.SceneNodes.reserve(joints.size());
|
||||
for (size_t i = 0; i < joints.size(); ++i) {
|
||||
const auto *joint = joints[i];
|
||||
ISceneNode *parent = this;
|
||||
if (joint->ParentJointID)
|
||||
parent = PerJoint.SceneNodes.at(*joint->ParentJointID); // exists because of topo. order
|
||||
assert(parent);
|
||||
const auto *matrix = std::get_if<core::matrix4>(&joint->transform);
|
||||
PerJoint.SceneNodes.push_back(new CBoneSceneNode(
|
||||
parent, SceneManager, 0, i, joint->Name,
|
||||
matrix ? core::Transform{} : std::get<core::Transform>(joint->transform),
|
||||
matrix ? *matrix : std::optional<core::matrix4>{}));
|
||||
}
|
||||
}
|
||||
|
||||
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
||||
|
||||
skinnedMesh->animateMesh(frame);
|
||||
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
|
||||
|
||||
//-----------------------------------------
|
||||
// Transition
|
||||
//-----------------------------------------
|
||||
|
||||
if (Transiting != 0.f) {
|
||||
// Init additional matrices
|
||||
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
|
||||
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
|
||||
PretransitingSave.push_back(core::matrix4());
|
||||
}
|
||||
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
|
||||
//------Position------
|
||||
|
||||
JointChildSceneNodes[n]->setPosition(
|
||||
core::lerp(
|
||||
PretransitingSave[n].getTranslation(),
|
||||
JointChildSceneNodes[n]->getPosition(),
|
||||
TransitingBlend));
|
||||
|
||||
//------Rotation------
|
||||
|
||||
// Code is slow, needs to be fixed up
|
||||
|
||||
const core::quaternion RotationStart(PretransitingSave[n].getRotationRadians());
|
||||
const core::quaternion RotationEnd(JointChildSceneNodes[n]->getRotation() * core::DEGTORAD);
|
||||
|
||||
core::quaternion QRotation;
|
||||
QRotation.slerp(RotationStart, RotationEnd, TransitingBlend);
|
||||
|
||||
core::vector3df tmpVector;
|
||||
QRotation.toEuler(tmpVector);
|
||||
tmpVector *= core::RADTODEG; // convert from radians back to degrees
|
||||
JointChildSceneNodes[n]->setRotation(tmpVector);
|
||||
|
||||
//------Scale------
|
||||
|
||||
// JointChildSceneNodes[n]->setScale(
|
||||
// core::lerp(
|
||||
// PretransitingSave[n].getScale(),
|
||||
// JointChildSceneNodes[n]->getScale(),
|
||||
// TransitingBlend));
|
||||
}
|
||||
void CAnimatedMeshSceneNode::updateJointSceneNodes(
|
||||
const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms)
|
||||
{
|
||||
for (size_t i = 0; i < transforms.size(); ++i) {
|
||||
const auto &transform = transforms[i];
|
||||
auto *node = static_cast<CBoneSceneNode*>(PerJoint.SceneNodes[i]);
|
||||
if (const auto *trs = std::get_if<core::Transform>(&transform)) {
|
||||
node->setTransform(*trs);
|
||||
// .x lets animations override matrix transforms entirely.
|
||||
node->Matrix = std::nullopt;
|
||||
} else {
|
||||
node->Matrix = std::get<core::matrix4>(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CalculateAbsolutePositions) {
|
||||
//---slow---
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
|
||||
if (JointChildSceneNodes[n]->getParent() == this) {
|
||||
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option
|
||||
}
|
||||
//! updates the joint positions of this mesh
|
||||
void CAnimatedMeshSceneNode::animateJoints()
|
||||
{
|
||||
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
||||
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()
|
||||
{
|
||||
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
||||
return;
|
||||
|
||||
if (!JointsUsed) {
|
||||
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i)
|
||||
removeChild(JointChildSceneNodes[i]);
|
||||
JointChildSceneNodes.clear();
|
||||
|
||||
// Create joints for SkinnedMesh
|
||||
((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager);
|
||||
((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes);
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i)
|
||||
removeChild(PerJoint.SceneNodes[i]);
|
||||
addJoints();
|
||||
|
||||
JointsUsed = true;
|
||||
JointMode = EJUOR_READ;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void CAnimatedMeshSceneNode::copyOldTransforms()
|
||||
{
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||
if (!PerJoint.SceneNodes[i]->Matrix) {
|
||||
PerJoint.PreTransSaves[i] = PerJoint.SceneNodes[i]->getTransform();
|
||||
} else {
|
||||
PerJoint.PreTransSaves[i] = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CAnimatedMeshSceneNode::beginTransition()
|
||||
{
|
||||
if (!JointsUsed)
|
||||
return;
|
||||
|
||||
if (TransitionTime != 0) {
|
||||
// Check the array is big enough
|
||||
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
|
||||
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
|
||||
PretransitingSave.push_back(core::matrix4());
|
||||
}
|
||||
|
||||
// Copy the position of joints
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n)
|
||||
PretransitingSave[n] = JointChildSceneNodes[n]->getRelativeTransformation();
|
||||
|
||||
Transiting = core::reciprocal((f32)TransitionTime);
|
||||
}
|
||||
TransitingBlend = 0.f;
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager)
|
||||
{
|
||||
if (!newParent)
|
||||
|
@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *
|
|||
newNode->EndFrame = EndFrame;
|
||||
newNode->FramesPerSecond = FramesPerSecond;
|
||||
newNode->CurrentFrameNr = CurrentFrameNr;
|
||||
newNode->JointMode = JointMode;
|
||||
newNode->JointsUsed = JointsUsed;
|
||||
newNode->TransitionTime = TransitionTime;
|
||||
newNode->Transiting = Transiting;
|
||||
newNode->TransitingBlend = TransitingBlend;
|
||||
newNode->Looping = Looping;
|
||||
newNode->ReadOnlyMaterials = ReadOnlyMaterials;
|
||||
newNode->LoopCallBack = LoopCallBack;
|
||||
if (newNode->LoopCallBack)
|
||||
newNode->LoopCallBack->grab();
|
||||
newNode->PassCount = PassCount;
|
||||
newNode->JointChildSceneNodes = JointChildSceneNodes;
|
||||
newNode->PretransitingSave = PretransitingSave;
|
||||
newNode->PerJoint.SceneNodes = PerJoint.SceneNodes;
|
||||
newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves;
|
||||
newNode->RenderFromIdentity = RenderFromIdentity;
|
||||
|
||||
return newNode;
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "CBoneSceneNode.h"
|
||||
#include "IAnimatedMeshSceneNode.h"
|
||||
#include "IAnimatedMesh.h"
|
||||
|
||||
#include "SkinnedMesh.h"
|
||||
#include "Transform.h"
|
||||
#include "matrix4.h"
|
||||
|
||||
namespace irr
|
||||
|
@ -54,9 +57,11 @@ public:
|
|||
//! returns the current loop mode
|
||||
bool getLoopMode() const override;
|
||||
|
||||
//! Sets a callback interface which will be called if an animation
|
||||
//! playback has ended. Set this to 0 to disable the callback again.
|
||||
void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) override;
|
||||
void setOnAnimateCallback(
|
||||
const std::function<void(f32 dtime)> &cb) override
|
||||
{
|
||||
OnAnimateCallback = cb;
|
||||
}
|
||||
|
||||
//! sets the speed with which the animation is played
|
||||
//! NOTE: setMesh will also change this value and set it to the default speed of the mesh
|
||||
|
@ -117,15 +122,16 @@ public:
|
|||
//! updates the absolute position based on the relative and the parents position
|
||||
void updateAbsolutePosition() override;
|
||||
|
||||
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
|
||||
void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) override;
|
||||
|
||||
//! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2)
|
||||
//! Sets the transition time in seconds (note: This needs to enable joints)
|
||||
//! you must call animateJoints(), or the mesh will not animate
|
||||
void setTransitionTime(f32 Time) override;
|
||||
|
||||
void updateJointSceneNodes(const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms);
|
||||
|
||||
//! updates the joint positions of this mesh
|
||||
void animateJoints(bool CalculateAbsolutePositions = true) override;
|
||||
void animateJoints() override;
|
||||
|
||||
void addJoints();
|
||||
|
||||
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
||||
void setRenderFromIdentity(bool On) override;
|
||||
|
@ -142,6 +148,7 @@ private:
|
|||
|
||||
void buildFrameNr(u32 timeMs);
|
||||
void checkJoints();
|
||||
void copyOldTransforms();
|
||||
void beginTransition();
|
||||
|
||||
core::array<video::SMaterial> Materials;
|
||||
|
@ -158,19 +165,30 @@ private:
|
|||
f32 Transiting; // is mesh transiting (plus cache of TransitionTime)
|
||||
f32 TransitingBlend; // 0-1, calculated on buildFrameNr
|
||||
|
||||
// 0-unused, 1-get joints only, 2-set joints only, 3-move and set
|
||||
E_JOINT_UPDATE_ON_RENDER JointMode;
|
||||
bool JointsUsed;
|
||||
|
||||
bool Looping;
|
||||
bool ReadOnlyMaterials;
|
||||
bool RenderFromIdentity;
|
||||
|
||||
IAnimationEndCallBack *LoopCallBack;
|
||||
s32 PassCount;
|
||||
std::function<void(f32)> OnAnimateCallback;
|
||||
|
||||
std::vector<IBoneSceneNode *> JointChildSceneNodes;
|
||||
core::array<core::matrix4> PretransitingSave;
|
||||
struct PerJointData {
|
||||
std::vector<CBoneSceneNode *> SceneNodes;
|
||||
std::vector<core::matrix4> GlobalMatrices;
|
||||
std::vector<std::optional<core::Transform>> PreTransSaves;
|
||||
void setN(u16 n) {
|
||||
SceneNodes.clear();
|
||||
SceneNodes.resize(n);
|
||||
GlobalMatrices.clear();
|
||||
GlobalMatrices.resize(n);
|
||||
PreTransSaves.clear();
|
||||
PreTransSaves.resize(n);
|
||||
}
|
||||
};
|
||||
|
||||
PerJointData PerJoint;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -48,7 +48,7 @@ IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file)
|
|||
return 0;
|
||||
|
||||
B3DFile = file;
|
||||
AnimatedMesh = new scene::SkinnedMeshBuilder();
|
||||
AnimatedMesh = new scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D);
|
||||
ShowWarning = true; // If true a warning is issued if too many textures are used
|
||||
VerticesStart = 0;
|
||||
|
||||
|
@ -143,31 +143,25 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint)
|
|||
os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
|
||||
f32 position[3], scale[3], rotation[4];
|
||||
core::Transform transform;
|
||||
{
|
||||
f32 t[3], s[3], r[4];
|
||||
|
||||
readFloats(position, 3);
|
||||
readFloats(scale, 3);
|
||||
readFloats(rotation, 4);
|
||||
readFloats(t, 3);
|
||||
readFloats(s, 3);
|
||||
readFloats(r, 4);
|
||||
|
||||
joint->Animatedposition = core::vector3df(position[0], position[1], position[2]);
|
||||
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]);
|
||||
joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]);
|
||||
|
||||
// Build LocalMatrix:
|
||||
|
||||
core::matrix4 positionMatrix;
|
||||
positionMatrix.setTranslation(joint->Animatedposition);
|
||||
core::matrix4 scaleMatrix;
|
||||
scaleMatrix.setScale(joint->Animatedscale);
|
||||
core::matrix4 rotationMatrix;
|
||||
joint->Animatedrotation.getMatrix_transposed(rotationMatrix);
|
||||
|
||||
joint->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix;
|
||||
joint->transform = transform = {
|
||||
{t[0], t[1], t[2]},
|
||||
{r[1], r[2], r[3], r[0]},
|
||||
{s[0], s[1], s[2]},
|
||||
};
|
||||
}
|
||||
|
||||
if (inJoint)
|
||||
joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix;
|
||||
joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix();
|
||||
else
|
||||
joint->GlobalMatrix = joint->LocalMatrix;
|
||||
joint->GlobalMatrix = transform.buildMatrix();
|
||||
|
||||
while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats
|
||||
{
|
||||
|
|
|
@ -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
|
|
@ -7,6 +7,8 @@
|
|||
// Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
|
||||
|
||||
#include "IBoneSceneNode.h"
|
||||
#include "Transform.h"
|
||||
#include "matrix4.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
@ -21,49 +23,48 @@ public:
|
|||
//! constructor
|
||||
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
|
||||
s32 id = -1, u32 boneIndex = 0,
|
||||
const std::optional<std::string> &boneName = std::nullopt);
|
||||
|
||||
//! Returns the index of the bone
|
||||
u32 getBoneIndex() const override;
|
||||
|
||||
//! Sets the animation mode of the bone. Returns true if successful.
|
||||
bool setAnimationMode(E_BONE_ANIMATION_MODE mode) override;
|
||||
|
||||
//! Gets the current animation mode of the bone
|
||||
E_BONE_ANIMATION_MODE getAnimationMode() const override;
|
||||
|
||||
//! returns the axis aligned bounding box of this node
|
||||
const core::aabbox3d<f32> &getBoundingBox() const override;
|
||||
|
||||
/*
|
||||
//! Returns the relative transformation of the scene node.
|
||||
//core::matrix4 getRelativeTransformation() const override;
|
||||
*/
|
||||
|
||||
void OnAnimate(u32 timeMs) override;
|
||||
|
||||
void updateAbsolutePositionOfAllChildren() override;
|
||||
|
||||
//! How the relative transformation of the bone is used
|
||||
void setSkinningSpace(E_BONE_SKINNING_SPACE space) override
|
||||
const std::optional<std::string> &boneName = std::nullopt,
|
||||
const core::Transform &transform = {},
|
||||
const std::optional<core::matrix4> &matrix = std::nullopt) :
|
||||
IBoneSceneNode(parent, mgr, id, boneIndex, boneName),
|
||||
Matrix(matrix)
|
||||
{
|
||||
SkinningSpace = space;
|
||||
setTransform(transform);
|
||||
}
|
||||
|
||||
E_BONE_SKINNING_SPACE getSkinningSpace() const override
|
||||
void setTransform(const core::Transform &transform)
|
||||
{
|
||||
return SkinningSpace;
|
||||
setPosition(transform.translation);
|
||||
{
|
||||
core::vector3df euler;
|
||||
auto rot = transform.rotation;
|
||||
// Invert to be consistent with setRotationDegrees
|
||||
rot.makeInverse();
|
||||
rot.toEuler(euler);
|
||||
setRotation(euler * core::RADTODEG);
|
||||
}
|
||||
setScale(transform.scale);
|
||||
}
|
||||
|
||||
private:
|
||||
void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node);
|
||||
core::Transform getTransform() const
|
||||
{
|
||||
return {
|
||||
getPosition(),
|
||||
core::quaternion(getRotation() * core::DEGTORAD).makeInverse(),
|
||||
getScale()
|
||||
};
|
||||
}
|
||||
|
||||
u32 BoneIndex;
|
||||
core::matrix4 getRelativeTransformation() const override
|
||||
{
|
||||
if (Matrix)
|
||||
return *Matrix;
|
||||
return IBoneSceneNode::getRelativeTransformation();
|
||||
}
|
||||
|
||||
core::aabbox3d<f32> Box{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
E_BONE_ANIMATION_MODE AnimationMode;
|
||||
E_BONE_SKINNING_SPACE SkinningSpace;
|
||||
//! Some file formats alternatively let bones specify a transformation matrix.
|
||||
//! If this is set, it overrides the TRS properties.
|
||||
std::optional<core::matrix4> Matrix;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -347,7 +347,8 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file)
|
|||
const char *filename = file->getFileName().c_str();
|
||||
try {
|
||||
tiniergltf::GlTF model = parseGLTF(file);
|
||||
irr_ptr<SkinnedMeshBuilder> mesh(new SkinnedMeshBuilder());
|
||||
irr_ptr<SkinnedMeshBuilder> mesh(new SkinnedMeshBuilder(
|
||||
SkinnedMesh::SourceFormat::GLTF));
|
||||
MeshExtractor extractor(std::move(model), mesh.get());
|
||||
try {
|
||||
extractor.load();
|
||||
|
@ -538,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes
|
|||
mat[i] = static_cast<f32>(m[i]);
|
||||
mat = convertHandedness(mat);
|
||||
|
||||
// Decompose the matrix into translation, scale, and rotation.
|
||||
joint->Animatedposition = mat.getTranslation();
|
||||
|
||||
auto scale = mat.getScale();
|
||||
joint->Animatedscale = scale;
|
||||
joint->Animatedrotation = mat.getRotationRadians(scale);
|
||||
// Invert the rotation because it is applied using `getMatrix_transposed`,
|
||||
// which again inverts.
|
||||
joint->Animatedrotation.makeInverse();
|
||||
|
||||
// Note: "When a node is targeted for animation [...],
|
||||
// only TRS properties MAY be present; matrix MUST NOT be present."
|
||||
// Thus we MUST NOT do any decomposition, which in general need not exist.
|
||||
joint->transform = mat;
|
||||
return mat;
|
||||
}
|
||||
|
||||
static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint)
|
||||
{
|
||||
const auto &trans = trs.translation;
|
||||
const auto &rot = trs.rotation;
|
||||
const auto &scale = trs.scale;
|
||||
core::matrix4 transMat;
|
||||
joint->Animatedposition = convertHandedness(core::vector3df(trans[0], trans[1], trans[2]));
|
||||
transMat.setTranslation(joint->Animatedposition);
|
||||
core::matrix4 rotMat;
|
||||
joint->Animatedrotation = convertHandedness(core::quaternion(rot[0], rot[1], rot[2], rot[3]));
|
||||
core::quaternion(joint->Animatedrotation).getMatrix_transposed(rotMat);
|
||||
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]);
|
||||
core::matrix4 scaleMat;
|
||||
scaleMat.setScale(joint->Animatedscale);
|
||||
return transMat * rotMat * scaleMat;
|
||||
const auto &t = trs.translation;
|
||||
const auto &r = trs.rotation;
|
||||
const auto &s = trs.scale;
|
||||
core::Transform transform{
|
||||
convertHandedness(core::vector3df(t[0], t[1], t[2])),
|
||||
convertHandedness(core::quaternion(r[0], r[1], r[2], r[3])),
|
||||
core::vector3df(s[0], s[1], s[2]),
|
||||
};
|
||||
joint->transform = transform;
|
||||
return transform.buildMatrix();
|
||||
}
|
||||
|
||||
static core::matrix4 loadTransform(std::optional<std::variant<tiniergltf::Node::Matrix, tiniergltf::Node::TRS>> transform,
|
||||
|
@ -583,8 +575,7 @@ void SelfType::MeshExtractor::loadNode(
|
|||
const auto &node = m_gltf_model.nodes->at(nodeIdx);
|
||||
auto *joint = m_irr_model->addJoint(parent);
|
||||
const core::matrix4 transform = loadTransform(node.transform, joint);
|
||||
joint->LocalMatrix = transform;
|
||||
joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix;
|
||||
joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform;
|
||||
if (node.name.has_value()) {
|
||||
joint->Name = node.name->c_str();
|
||||
}
|
||||
|
@ -641,7 +632,6 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
|
|||
{
|
||||
const auto &anim = m_gltf_model.animations->at(animIdx);
|
||||
for (const auto &channel : anim.channels) {
|
||||
|
||||
const auto &sampler = anim.samplers.at(channel.sampler);
|
||||
|
||||
bool interpolate = ([&]() {
|
||||
|
@ -662,6 +652,11 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
|
|||
throw std::runtime_error("no animated node");
|
||||
|
||||
auto *joint = m_loaded_nodes.at(*channel.target.node);
|
||||
if (std::holds_alternative<core::matrix4>(joint->transform)) {
|
||||
warn("nodes using matrix transforms must not be animated");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (channel.target.path) {
|
||||
case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: {
|
||||
const auto outputAccessor = Accessor<core::vector3df>::make(m_gltf_model, sampler.output);
|
||||
|
|
|
@ -320,7 +320,6 @@ set(IRRMESHLOADER
|
|||
|
||||
add_library(IRRMESHOBJ OBJECT
|
||||
SkinnedMesh.cpp
|
||||
CBoneSceneNode.cpp
|
||||
CMeshSceneNode.cpp
|
||||
CAnimatedMeshSceneNode.cpp
|
||||
${IRRMESHLOADER}
|
||||
|
|
|
@ -35,7 +35,7 @@ void CMeshCache::removeMesh(const IMesh *const mesh)
|
|||
if (!mesh)
|
||||
return;
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) {
|
||||
if (Meshes[i].Mesh == mesh) {
|
||||
Meshes[i].Mesh->drop();
|
||||
Meshes.erase(i);
|
||||
return;
|
||||
|
@ -53,7 +53,7 @@ u32 CMeshCache::getMeshCount() const
|
|||
s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const
|
||||
{
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh))
|
||||
if (Meshes[i].Mesh == mesh)
|
||||
return (s32)i;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ const io::SNamedPath &CMeshCache::getMeshName(const IMesh *const mesh) const
|
|||
return emptyNamedPath;
|
||||
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh))
|
||||
if (Meshes[i].Mesh == mesh)
|
||||
return Meshes[i].NamedPath;
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ bool CMeshCache::renameMesh(u32 index, const io::path &name)
|
|||
bool CMeshCache::renameMesh(const IMesh *const mesh, const io::path &name)
|
||||
{
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) {
|
||||
if (Meshes[i].Mesh == mesh) {
|
||||
Meshes[i].NamedPath.setPath(name);
|
||||
Meshes.sort();
|
||||
return true;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "SkinnedMesh.h"
|
||||
#include "SMesh.h"
|
||||
#include "CMeshBuffer.h"
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "os.h"
|
||||
|
||||
#include <cassert>
|
||||
|
@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const
|
|||
return clone;
|
||||
}
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 CMeshManipulator::getPolyCount(scene::IMesh *mesh) const
|
||||
{
|
||||
if (!mesh)
|
||||
return 0;
|
||||
|
||||
s32 trianglecount = 0;
|
||||
|
||||
for (u32 g = 0; g < mesh->getMeshBufferCount(); ++g)
|
||||
trianglecount += mesh->getMeshBuffer(g)->getIndexCount() / 3;
|
||||
|
||||
return trianglecount;
|
||||
}
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 CMeshManipulator::getPolyCount(scene::IAnimatedMesh *mesh) const
|
||||
{
|
||||
if (mesh && mesh->getMaxFrameNumber() != 0)
|
||||
return getPolyCount(mesh->getMesh(0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//! create a new AnimatedMesh and adds the mesh to it
|
||||
IAnimatedMesh *CMeshManipulator::createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const
|
||||
{
|
||||
return new SAnimatedMesh(mesh, type);
|
||||
}
|
||||
|
||||
} // end namespace scene
|
||||
} // end namespace irr
|
||||
|
|
|
@ -31,15 +31,6 @@ public:
|
|||
|
||||
//! Clones a static IMesh into a modifiable SMesh.
|
||||
SMesh *createMeshCopy(scene::IMesh *mesh) const override;
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 getPolyCount(scene::IMesh *mesh) const override;
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 getPolyCount(scene::IAnimatedMesh *mesh) const override;
|
||||
|
||||
//! create a new AnimatedMesh and adds the mesh to it
|
||||
IAnimatedMesh *createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const override;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh
|
|||
else if (node->getType() == scene::ESNT_MESH)
|
||||
mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh();
|
||||
else
|
||||
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh()->getMesh(0);
|
||||
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh();
|
||||
if (!mesh)
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "IVideoDriver.h"
|
||||
#include "SMesh.h"
|
||||
#include "SMeshBuffer.h"
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "IReadFile.h"
|
||||
#include "fast_atof.h"
|
||||
#include "coreutil.h"
|
||||
|
@ -272,23 +271,19 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file)
|
|||
}
|
||||
}
|
||||
|
||||
// Create the Animated mesh if there's anything in the mesh
|
||||
SAnimatedMesh *animMesh = 0;
|
||||
if (0 != mesh->getMeshBufferCount()) {
|
||||
mesh->recalculateBoundingBox();
|
||||
animMesh = new SAnimatedMesh();
|
||||
animMesh->Type = EAMT_OBJ;
|
||||
animMesh->addMesh(mesh);
|
||||
animMesh->recalculateBoundingBox();
|
||||
}
|
||||
|
||||
// Clean up the allocate obj file contents
|
||||
delete[] buf;
|
||||
// more cleaning up
|
||||
cleanUp();
|
||||
mesh->drop();
|
||||
|
||||
return animMesh;
|
||||
// Nothing in the mesh
|
||||
if (mesh->getMeshBufferCount() == 0) {
|
||||
mesh->drop();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mesh->recalculateBoundingBox();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
//! Read RGB color
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "CSceneManager.h"
|
||||
#include "IVideoDriver.h"
|
||||
#include "IFileSystem.h"
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "CMeshCache.h"
|
||||
#include "IGUIEnvironment.h"
|
||||
#include "IMaterialRenderer.h"
|
||||
|
@ -762,7 +761,7 @@ ISceneManager *CSceneManager::createNewSceneManager(bool cloneContent)
|
|||
//! Get a skinned mesh, which is not available as header-only code
|
||||
SkinnedMesh *CSceneManager::createSkinnedMesh()
|
||||
{
|
||||
return new SkinnedMesh();
|
||||
return new SkinnedMesh(SkinnedMesh::SourceFormat::OTHER);
|
||||
}
|
||||
|
||||
// creates a scenemanager
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "CXMeshFileLoader.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "Transform.h"
|
||||
#include "os.h"
|
||||
|
||||
#include "fast_atof.h"
|
||||
|
@ -54,7 +55,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file)
|
|||
u32 time = os::Timer::getRealTime();
|
||||
#endif
|
||||
|
||||
AnimatedMesh = new SkinnedMeshBuilder();
|
||||
AnimatedMesh = new SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X);
|
||||
|
||||
SkinnedMesh *res = nullptr;
|
||||
if (load(file)) {
|
||||
|
@ -513,6 +514,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
|||
if (n.has_value()) {
|
||||
JointID = *n;
|
||||
joint = AnimatedMesh->getAllJoints()[JointID];
|
||||
joint->setParent(Parent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -527,8 +529,6 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
|||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
if (Parent)
|
||||
Parent->Children.push_back(joint);
|
||||
}
|
||||
|
||||
// Now inside a frame.
|
||||
|
@ -552,12 +552,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
|||
if (!parseDataObjectFrame(joint))
|
||||
return false;
|
||||
} else if (objectName == "FrameTransformMatrix") {
|
||||
if (!parseDataObjectTransformationMatrix(joint->LocalMatrix))
|
||||
core::matrix4 matrix;
|
||||
if (!parseDataObjectTransformationMatrix(matrix))
|
||||
return false;
|
||||
|
||||
// joint->LocalAnimatedMatrix
|
||||
// joint->LocalAnimatedMatrix.makeInverse();
|
||||
// joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix;
|
||||
joint->transform = matrix;
|
||||
} else if (objectName == "Mesh") {
|
||||
/*
|
||||
frame.Meshes.push_back(SXMesh());
|
||||
|
|
|
@ -7,7 +7,15 @@
|
|||
#include "CBoneSceneNode.h"
|
||||
#include "IAnimatedMeshSceneNode.h"
|
||||
#include "SSkinMeshBuffer.h"
|
||||
#include "Transform.h"
|
||||
#include "aabbox3d.h"
|
||||
#include "irrMath.h"
|
||||
#include "matrix4.h"
|
||||
#include "os.h"
|
||||
#include "vector3d.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
|
@ -48,183 +56,77 @@ void SkinnedMesh::setAnimationSpeed(f32 fps)
|
|||
FramesPerSecond = fps;
|
||||
}
|
||||
|
||||
//! returns the animated mesh based
|
||||
IMesh *SkinnedMesh::getMesh(f32 frame)
|
||||
{
|
||||
// animate(frame,startFrameLoop, endFrameLoop);
|
||||
if (frame == -1)
|
||||
return this;
|
||||
// Keyframe Animation
|
||||
|
||||
animateMesh(frame);
|
||||
skinMesh();
|
||||
return this;
|
||||
|
||||
using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
|
||||
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;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Keyframe Animation
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
//! Animates joints based on frame input
|
||||
void SkinnedMesh::animateMesh(f32 frame)
|
||||
core::aabbox3df SkinnedMesh::calculateBoundingBox(
|
||||
const std::vector<core::matrix4> &global_transforms)
|
||||
{
|
||||
if (!HasAnimation || LastAnimatedFrame == frame)
|
||||
return;
|
||||
|
||||
LastAnimatedFrame = frame;
|
||||
SkinnedLastFrame = false;
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
// The joints can be animated here with no input from their
|
||||
// parents, but for setAnimationMode extra checks are needed
|
||||
// to their parents
|
||||
joint->keys.updateTransform(frame,
|
||||
joint->Animatedposition,
|
||||
joint->Animatedrotation,
|
||||
joint->Animatedscale);
|
||||
assert(global_transforms.size() == AllJoints.size());
|
||||
core::aabbox3df result = StaticPartsBox;
|
||||
// skeletal animation
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
auto box = AllJoints[i]->LocalBoundingBox;
|
||||
global_transforms[i].transformBoxEx(box);
|
||||
result.addInternalBox(box);
|
||||
}
|
||||
|
||||
// Note:
|
||||
// LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for
|
||||
// one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
|
||||
// a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
|
||||
|
||||
//----------------
|
||||
// Temp!
|
||||
buildAllLocalAnimatedMatrices();
|
||||
//-----------------
|
||||
|
||||
updateBoundingBox();
|
||||
}
|
||||
|
||||
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;
|
||||
// rigid animation
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
for (u32 j : AllJoints[i]->AttachedMeshes) {
|
||||
auto box = (*SkinningBuffers)[j]->BoundingBox;
|
||||
global_transforms[i].transformBoxEx(box);
|
||||
result.addInternalBox(box);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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)
|
||||
if (!HasAnimation)
|
||||
return;
|
||||
|
||||
//----------------
|
||||
// This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
|
||||
buildAllGlobalAnimatedMatrices();
|
||||
//-----------------
|
||||
|
||||
SkinnedLastFrame = true;
|
||||
if (!HardwareSkinning) {
|
||||
// rigid animation
|
||||
for (auto *joint : AllJoints) {
|
||||
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||
Buffer->Transformation = joint->GlobalAnimatedMatrix;
|
||||
}
|
||||
// rigid animation
|
||||
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||
auto *joint = AllJoints[i];
|
||||
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||
Buffer->Transformation = global_matrices[i];
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (joint->Weights.size()) {
|
||||
// Find this joints pull on vertices...
|
||||
// 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 (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.
|
||||
core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value();
|
||||
core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value();
|
||||
|
||||
core::vector3df thisVertexMove, thisNormalMove;
|
||||
|
||||
auto &buffersUsed = *SkinningBuffers;
|
||||
|
||||
// Skin Vertices Positions and Normals...
|
||||
// Skin Vertices, Positions and Normals
|
||||
for (const auto &weight : joint->Weights) {
|
||||
// Pull this vertex...
|
||||
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
|
||||
|
@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
|
|||
|
||||
//*(weight._Pos) += thisVertexMove * weight.strength;
|
||||
}
|
||||
|
||||
buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
|
||||
}
|
||||
}
|
||||
|
||||
// Skin all children
|
||||
for (auto *childJoint : joint->Children)
|
||||
skinJoint(childJoint, joint);
|
||||
for (auto *buffer : *SkinningBuffers)
|
||||
buffer->setDirty(EBT_VERTEX);
|
||||
}
|
||||
|
||||
//! Gets joint count.
|
||||
|
@ -310,7 +209,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
|
|||
if (LocalBuffers[i]->getMaterial() == material)
|
||||
return LocalBuffers[i];
|
||||
}
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
|
||||
|
@ -337,29 +236,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
|
|||
LocalBuffers[i]->setDirty(buffer);
|
||||
}
|
||||
|
||||
//! (This feature is not implemented in irrlicht yet)
|
||||
bool SkinnedMesh::setHardwareSkinning(bool on)
|
||||
{
|
||||
if (HardwareSkinning != on) {
|
||||
if (on) {
|
||||
|
||||
// set mesh to static pose...
|
||||
for (auto *joint : AllJoints) {
|
||||
for (const auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos;
|
||||
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
|
||||
LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HardwareSkinning = on;
|
||||
}
|
||||
return HardwareSkinning;
|
||||
}
|
||||
|
||||
void SkinnedMesh::refreshJointCache()
|
||||
{
|
||||
// copy cache from the mesh...
|
||||
|
@ -384,113 +260,192 @@ void SkinnedMesh::resetAnimation()
|
|||
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
|
||||
}
|
||||
}
|
||||
SkinnedLastFrame = false;
|
||||
LastAnimatedFrame = -1;
|
||||
}
|
||||
|
||||
void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint)
|
||||
//! Turns the given array of local matrices into an array of global matrices
|
||||
//! by multiplying with respective parent matrices.
|
||||
void SkinnedMesh::calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const
|
||||
{
|
||||
if (!joint && parentJoint) // bit of protection from endless loops
|
||||
return;
|
||||
|
||||
// Go through the root bones
|
||||
if (!joint) {
|
||||
for (auto *rootJoint : RootJoints)
|
||||
calculateGlobalMatrices(rootJoint, nullptr);
|
||||
return;
|
||||
// Note that the joints are topologically sorted.
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
if (auto parent_id = AllJoints[i]->ParentJointID) {
|
||||
matrices[i] = matrices[*parent_id] * matrices[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!parentJoint)
|
||||
joint->GlobalMatrix = joint->LocalMatrix;
|
||||
else
|
||||
joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
|
||||
|
||||
joint->LocalAnimatedMatrix = joint->LocalMatrix;
|
||||
joint->GlobalAnimatedMatrix = joint->GlobalMatrix;
|
||||
|
||||
if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated
|
||||
joint->GlobalInversedMatrix = joint->GlobalMatrix;
|
||||
joint->GlobalInversedMatrix->makeInverse(); // slow
|
||||
}
|
||||
|
||||
for (auto *childJoint : joint->Children)
|
||||
calculateGlobalMatrices(childJoint, joint);
|
||||
SkinnedLastFrame = false;
|
||||
}
|
||||
|
||||
void SkinnedMesh::checkForAnimation()
|
||||
bool SkinnedMesh::checkForAnimation() const
|
||||
{
|
||||
// Check for animation...
|
||||
HasAnimation = false;
|
||||
for (auto *joint : AllJoints) {
|
||||
if (!joint->keys.empty()) {
|
||||
HasAnimation = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// meshes with weights, are still counted as animated for ragdolls, etc
|
||||
if (!HasAnimation) {
|
||||
for (auto *joint : AllJoints) {
|
||||
if (joint->Weights.size()) {
|
||||
HasAnimation = true;
|
||||
break;
|
||||
// meshes with weights are animatable
|
||||
for (auto *joint : AllJoints) {
|
||||
if (!joint->Weights.empty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
EndFrame = 0.0f;
|
||||
for (const auto *joint : AllJoints) {
|
||||
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
|
||||
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
||||
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
||||
Vertices_Moved[i][j] = false;
|
||||
|
||||
// For skinning: cache weight values for speed
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
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) {
|
||||
PreparedForSkinning = true;
|
||||
normalizeWeights();
|
||||
|
||||
// check for bugs:
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
for (auto *joint : AllJoints) {
|
||||
joint->keys.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
void SkinnedMesh::calculateStaticBoundingBox()
|
||||
{
|
||||
std::vector<std::vector<bool>> animated(getMeshBufferCount());
|
||||
for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
|
||||
animated[mb] = std::vector<bool>(getMeshBuffer(mb)->getVertexCount());
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
animated[buffer_id][vertex_id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (u16 mb = 0; mb < getMeshBufferCount(); mb++) {
|
||||
for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) {
|
||||
if (!animated[mb][v]) {
|
||||
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
|
||||
if (!first) {
|
||||
StaticPartsBox.addInternalPoint(pos);
|
||||
} else {
|
||||
StaticPartsBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An array used in skinning
|
||||
|
||||
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
||||
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
||||
Vertices_Moved[i][j] = false;
|
||||
|
||||
// For skinning: cache weight values for speed
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
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;
|
||||
|
||||
// weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
|
||||
void SkinnedMesh::calculateJointBoundingBoxes()
|
||||
{
|
||||
for (auto *joint : AllJoints) {
|
||||
bool first = true;
|
||||
for (auto &weight : joint->Weights) {
|
||||
if (weight.strength < 1e-6)
|
||||
continue;
|
||||
auto pos = weight.StaticPos;
|
||||
joint->GlobalInversedMatrix.value().transformVect(pos);
|
||||
if (!first) {
|
||||
joint->LocalBoundingBox.addInternalPoint(pos);
|
||||
} else {
|
||||
joint->LocalBoundingBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -498,98 +453,44 @@ SkinnedMesh *SkinnedMeshBuilder::finalize()
|
|||
{
|
||||
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
|
||||
|
||||
// Make sure we recalc the next frame
|
||||
LastAnimatedFrame = -1;
|
||||
SkinnedLastFrame = false;
|
||||
|
||||
// calculate bounding box
|
||||
for (auto *buffer : LocalBuffers) {
|
||||
buffer->recalculateBoundingBox();
|
||||
}
|
||||
|
||||
if (AllJoints.size() || RootJoints.size()) {
|
||||
// populate AllJoints or RootJoints, depending on which is empty
|
||||
if (RootJoints.empty()) {
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
|
||||
bool foundParent = false;
|
||||
for (const auto *parentJoint : AllJoints) {
|
||||
for (const auto *childJoint : parentJoint->Children) {
|
||||
if (childJoint == joint)
|
||||
foundParent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundParent)
|
||||
RootJoints.push_back(joint);
|
||||
}
|
||||
} else {
|
||||
AllJoints = RootJoints;
|
||||
}
|
||||
}
|
||||
|
||||
// Set array sizes...
|
||||
topoSortJoints();
|
||||
|
||||
// Set array sizes
|
||||
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
||||
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
|
||||
}
|
||||
|
||||
checkForAnimation();
|
||||
prepareForSkinning();
|
||||
|
||||
if (HasAnimation) {
|
||||
for (auto *joint : AllJoints) {
|
||||
joint->keys.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Needed for animation and skinning...
|
||||
|
||||
calculateGlobalMatrices(0, 0);
|
||||
|
||||
// rigid animation for non animated meshes
|
||||
std::vector<core::matrix4> matrices;
|
||||
matrices.reserve(AllJoints.size());
|
||||
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) {
|
||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||
Buffer->Transformation = joint->GlobalAnimatedMatrix;
|
||||
Buffer->Transformation = matrices[i];
|
||||
}
|
||||
}
|
||||
|
||||
// calculate bounding box
|
||||
if (LocalBuffers.empty())
|
||||
BoundingBox.reset(0, 0, 0);
|
||||
else {
|
||||
irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
|
||||
LocalBuffers[0]->Transformation.transformBoxEx(bb);
|
||||
BoundingBox.reset(bb);
|
||||
|
||||
for (u32 j = 1; j < LocalBuffers.size(); ++j) {
|
||||
bb = LocalBuffers[j]->BoundingBox;
|
||||
LocalBuffers[j]->Transformation.transformBoxEx(bb);
|
||||
|
||||
BoundingBox.addInternalBox(bb);
|
||||
}
|
||||
}
|
||||
recalculateBaseBoundingBoxes();
|
||||
StaticPoseBox = calculateBoundingBox(matrices);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void SkinnedMesh::updateBoundingBox()
|
||||
{
|
||||
if (!SkinningBuffers)
|
||||
return;
|
||||
|
||||
BoundingBox.reset(0, 0, 0);
|
||||
|
||||
for (auto *buffer : *SkinningBuffers) {
|
||||
buffer->recalculateBoundingBox();
|
||||
core::aabbox3df bb = buffer->BoundingBox;
|
||||
buffer->Transformation.transformBoxEx(bb);
|
||||
|
||||
BoundingBox.addInternalBox(bb);
|
||||
}
|
||||
}
|
||||
|
||||
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
|
||||
{
|
||||
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
||||
|
@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
|||
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||
{
|
||||
SJoint *joint = new SJoint;
|
||||
joint->setParent(parent);
|
||||
|
||||
joint->JointID = AllJoints.size();
|
||||
AllJoints.push_back(joint);
|
||||
if (!parent) {
|
||||
// Add root joints to array in finalize()
|
||||
} else {
|
||||
// Set parent (Be careful of the mesh loader also setting the parent)
|
||||
parent->Children.push_back(joint);
|
||||
}
|
||||
|
||||
return joint;
|
||||
}
|
||||
|
@ -684,73 +581,6 @@ void SkinnedMesh::normalizeWeights()
|
|||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes)
|
||||
{
|
||||
for (u32 i = 0; i < AllJoints.size(); ++i) {
|
||||
IBoneSceneNode *node = jointChildSceneNodes[i];
|
||||
SJoint *joint = AllJoints[i];
|
||||
node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
|
||||
node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
|
||||
node->setScale(joint->LocalAnimatedMatrix.getScale());
|
||||
|
||||
node->updateAbsolutePosition();
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes)
|
||||
{
|
||||
for (u32 i = 0; i < AllJoints.size(); ++i) {
|
||||
const IBoneSceneNode *const node = jointChildSceneNodes[i];
|
||||
SJoint *joint = AllJoints[i];
|
||||
|
||||
joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
|
||||
joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
|
||||
joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
|
||||
|
||||
joint->GlobalSkinningSpace = (node->getSkinningSpace() == EBSS_GLOBAL);
|
||||
}
|
||||
// Make sure we recalc the next frame
|
||||
LastAnimatedFrame = -1;
|
||||
SkinnedLastFrame = false;
|
||||
}
|
||||
|
||||
void SkinnedMesh::addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
|
||||
IAnimatedMeshSceneNode *node, ISceneManager *smgr)
|
||||
{
|
||||
// Create new joints
|
||||
for (u32 i = 0; i < AllJoints.size(); ++i) {
|
||||
jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name));
|
||||
}
|
||||
|
||||
// Match up parents
|
||||
for (u32 i = 0; i < jointChildSceneNodes.size(); ++i) {
|
||||
const SJoint *const joint = AllJoints[i]; // should be fine
|
||||
|
||||
s32 parentID = -1;
|
||||
|
||||
for (u32 j = 0; (parentID == -1) && (j < AllJoints.size()); ++j) {
|
||||
if (i != j) {
|
||||
const SJoint *const parentTest = AllJoints[j];
|
||||
for (u32 n = 0; n < parentTest->Children.size(); ++n) {
|
||||
if (parentTest->Children[n] == joint) {
|
||||
parentID = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IBoneSceneNode *bone = jointChildSceneNodes[i];
|
||||
if (parentID != -1)
|
||||
bone->setParent(jointChildSceneNodes[parentID]);
|
||||
else
|
||||
bone->setParent(node);
|
||||
|
||||
bone->drop();
|
||||
}
|
||||
SkinnedLastFrame = false;
|
||||
}
|
||||
|
||||
void SkinnedMesh::convertMeshToTangents()
|
||||
{
|
||||
// now calculate tangents
|
||||
|
|
|
@ -916,12 +916,7 @@ struct Node {
|
|||
std::optional<std::size_t> skin;
|
||||
std::optional<std::vector<double>> weights;
|
||||
Node(const Json::Value &o)
|
||||
: transform(Matrix {
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
})
|
||||
: transform(TRS{})
|
||||
{
|
||||
check(o.isObject());
|
||||
if (o.isMember("camera")) {
|
||||
|
|
|
@ -95,366 +95,366 @@
|
|||
### Keybindings
|
||||
|
||||
# Key for moving the player forward.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_forward = KEY_KEY_W
|
||||
# keymap_forward = SYSTEM_SCANCODE_26
|
||||
|
||||
# Key for moving the player backward.
|
||||
# Will also disable autoforward, when active.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_backward = KEY_KEY_S
|
||||
# keymap_backward = SYSTEM_SCANCODE_22
|
||||
|
||||
# Key for moving the player left.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_left = KEY_KEY_A
|
||||
# keymap_left = SYSTEM_SCANCODE_4
|
||||
|
||||
# Key for moving the player right.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_right = KEY_KEY_D
|
||||
# keymap_right = SYSTEM_SCANCODE_7
|
||||
|
||||
# Key for jumping.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_jump = KEY_SPACE
|
||||
# keymap_jump = SYSTEM_SCANCODE_44
|
||||
|
||||
# Key for sneaking.
|
||||
# Also used for climbing down and descending in water if aux1_descends is disabled.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_sneak = KEY_LSHIFT
|
||||
# keymap_sneak = SYSTEM_SCANCODE_225
|
||||
|
||||
# Key for digging, punching or using something.
|
||||
# (Note: The actual meaning might vary on a per-game basis.)
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_dig = KEY_LBUTTON
|
||||
|
||||
# Key for placing an item/block or for using something.
|
||||
# (Note: The actual meaning might vary on a per-game basis.)
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_place = KEY_RBUTTON
|
||||
|
||||
# Key for opening the inventory.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_inventory = KEY_KEY_I
|
||||
# keymap_inventory = SYSTEM_SCANCODE_12
|
||||
|
||||
# Key for moving fast in fast mode.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_aux1 = KEY_KEY_E
|
||||
# keymap_aux1 = SYSTEM_SCANCODE_8
|
||||
|
||||
# Key for opening the chat window.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_chat = KEY_KEY_T
|
||||
# keymap_chat = SYSTEM_SCANCODE_23
|
||||
|
||||
# Key for opening the chat window to type commands.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_cmd = /
|
||||
# keymap_cmd = SYSTEM_SCANCODE_56
|
||||
|
||||
# Key for opening the chat window to type local commands.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_cmd_local = .
|
||||
# keymap_cmd_local = SYSTEM_SCANCODE_55
|
||||
|
||||
# Key for toggling unlimited view range.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_rangeselect =
|
||||
|
||||
# Key for toggling flying.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_freemove = KEY_KEY_K
|
||||
# keymap_freemove = SYSTEM_SCANCODE_14
|
||||
|
||||
# Key for toggling pitch move mode.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_pitchmove =
|
||||
|
||||
# Key for toggling fast mode.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_fastmove = KEY_KEY_J
|
||||
# keymap_fastmove = SYSTEM_SCANCODE_13
|
||||
|
||||
# Key for toggling noclip mode.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_noclip = KEY_KEY_H
|
||||
# keymap_noclip = SYSTEM_SCANCODE_11
|
||||
|
||||
# Key for selecting the next item in the hotbar.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_hotbar_next = KEY_KEY_N
|
||||
# keymap_hotbar_next = SYSTEM_SCANCODE_17
|
||||
|
||||
# Key for selecting the previous item in the hotbar.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_hotbar_previous = KEY_KEY_B
|
||||
# keymap_hotbar_previous = SYSTEM_SCANCODE_5
|
||||
|
||||
# Key for muting the game.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_mute = KEY_KEY_M
|
||||
# keymap_mute = SYSTEM_SCANCODE_16
|
||||
|
||||
# Key for increasing the volume.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_increase_volume =
|
||||
|
||||
# Key for decreasing the volume.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_decrease_volume =
|
||||
|
||||
# Key for toggling autoforward.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_autoforward =
|
||||
|
||||
# Key for toggling cinematic mode.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_cinematic =
|
||||
|
||||
# Key for toggling display of minimap.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_minimap = KEY_KEY_V
|
||||
# keymap_minimap = SYSTEM_SCANCODE_25
|
||||
|
||||
# Key for taking screenshots.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_screenshot = KEY_F12
|
||||
# keymap_screenshot = SYSTEM_SCANCODE_69
|
||||
|
||||
# Key for toggling fullscreen mode.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_fullscreen = KEY_F11
|
||||
# keymap_fullscreen = SYSTEM_SCANCODE_68
|
||||
|
||||
# Key for dropping the currently selected item.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_drop = KEY_KEY_Q
|
||||
# keymap_drop = SYSTEM_SCANCODE_20
|
||||
|
||||
# Key to use view zoom when possible.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_zoom = KEY_KEY_Z
|
||||
# keymap_zoom = SYSTEM_SCANCODE_29
|
||||
|
||||
# Key for toggling the display of the HUD.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_hud = KEY_F1
|
||||
# keymap_toggle_hud = SYSTEM_SCANCODE_58
|
||||
|
||||
# Key for toggling the display of chat.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_chat = KEY_F2
|
||||
# keymap_toggle_chat = SYSTEM_SCANCODE_59
|
||||
|
||||
# Key for toggling the display of the large chat console.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_console = KEY_F10
|
||||
# keymap_console = SYSTEM_SCANCODE_67
|
||||
|
||||
# Key for toggling the display of fog.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_fog = KEY_F3
|
||||
# keymap_toggle_fog = SYSTEM_SCANCODE_60
|
||||
|
||||
# Key for toggling the display of debug info.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_debug = KEY_F5
|
||||
# keymap_toggle_debug = SYSTEM_SCANCODE_62
|
||||
|
||||
# Key for toggling the display of the profiler. Used for development.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_profiler = KEY_F6
|
||||
# keymap_toggle_profiler = SYSTEM_SCANCODE_63
|
||||
|
||||
# Key for toggling the display of mapblock boundaries.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_block_bounds =
|
||||
|
||||
# Key for switching between first- and third-person camera.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_camera_mode = KEY_KEY_C
|
||||
# keymap_camera_mode = SYSTEM_SCANCODE_6
|
||||
|
||||
# Key for increasing the viewing range.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_increase_viewing_range_min = +
|
||||
# keymap_increase_viewing_range_min = SYSTEM_SCANCODE_46
|
||||
|
||||
# Key for decreasing the viewing range.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_decrease_viewing_range_min = -
|
||||
# keymap_decrease_viewing_range_min = SYSTEM_SCANCODE_45
|
||||
|
||||
# Key for selecting the first hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot1 = KEY_KEY_1
|
||||
# keymap_slot1 = SYSTEM_SCANCODE_30
|
||||
|
||||
# Key for selecting the second hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot2 = KEY_KEY_2
|
||||
# keymap_slot2 = SYSTEM_SCANCODE_31
|
||||
|
||||
# Key for selecting the third hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot3 = KEY_KEY_3
|
||||
# keymap_slot3 = SYSTEM_SCANCODE_32
|
||||
|
||||
# Key for selecting the fourth hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot4 = KEY_KEY_4
|
||||
# keymap_slot4 = SYSTEM_SCANCODE_33
|
||||
|
||||
# Key for selecting the fifth hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot5 = KEY_KEY_5
|
||||
# keymap_slot5 = SYSTEM_SCANCODE_34
|
||||
|
||||
# Key for selecting the sixth hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot6 = KEY_KEY_6
|
||||
# keymap_slot6 = SYSTEM_SCANCODE_35
|
||||
|
||||
# Key for selecting the seventh hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot7 = KEY_KEY_7
|
||||
# keymap_slot7 = SYSTEM_SCANCODE_36
|
||||
|
||||
# Key for selecting the eighth hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot8 = KEY_KEY_8
|
||||
# keymap_slot8 = SYSTEM_SCANCODE_37
|
||||
|
||||
# Key for selecting the ninth hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot9 = KEY_KEY_9
|
||||
# keymap_slot9 = SYSTEM_SCANCODE_38
|
||||
|
||||
# Key for selecting the tenth hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot10 = KEY_KEY_0
|
||||
# keymap_slot10 = SYSTEM_SCANCODE_39
|
||||
|
||||
# Key for selecting the 11th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot11 =
|
||||
|
||||
# Key for selecting the 12th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot12 =
|
||||
|
||||
# Key for selecting the 13th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot13 =
|
||||
|
||||
# Key for selecting the 14th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot14 =
|
||||
|
||||
# Key for selecting the 15th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot15 =
|
||||
|
||||
# Key for selecting the 16th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot16 =
|
||||
|
||||
# Key for selecting the 17th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot17 =
|
||||
|
||||
# Key for selecting the 18th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot18 =
|
||||
|
||||
# Key for selecting the 19th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot19 =
|
||||
|
||||
# Key for selecting the 20th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot20 =
|
||||
|
||||
# Key for selecting the 21st hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot21 =
|
||||
|
||||
# Key for selecting the 22nd hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot22 =
|
||||
|
||||
# Key for selecting the 23rd hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot23 =
|
||||
|
||||
# Key for selecting the 24th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot24 =
|
||||
|
||||
# Key for selecting the 25th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot25 =
|
||||
|
||||
# Key for selecting the 26th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot26 =
|
||||
|
||||
# Key for selecting the 27th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot27 =
|
||||
|
||||
# Key for selecting the 28th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot28 =
|
||||
|
||||
# Key for selecting the 29th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot29 =
|
||||
|
||||
# Key for selecting the 30th hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot30 =
|
||||
|
||||
# Key for selecting the 31st hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot31 =
|
||||
|
||||
# Key for selecting the 32nd hotbar slot.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_slot32 =
|
||||
|
||||
|
@ -925,10 +925,6 @@
|
|||
# type: bool
|
||||
# enable_translucent_foliage = false
|
||||
|
||||
# Apply specular shading to nodes.
|
||||
# type: bool
|
||||
# enable_node_specular = false
|
||||
|
||||
# When enabled, liquid reflections are simulated.
|
||||
# type: bool
|
||||
# enable_water_reflections = false
|
||||
|
@ -3554,27 +3550,27 @@
|
|||
### Client Debugging
|
||||
|
||||
# Key for toggling the camera update. Only usable with 'debug' privilege.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_toggle_update_camera =
|
||||
|
||||
# Key for switching to the previous entry in Quicktune.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_quicktune_prev =
|
||||
|
||||
# Key for switching to the next entry in Quicktune.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_quicktune_next =
|
||||
|
||||
# Key for decrementing the selected value in Quicktune.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_quicktune_dec =
|
||||
|
||||
# Key for incrementing the selected value in Quicktune.
|
||||
# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h
|
||||
# See https://docs.luanti.org/for-players/controls/
|
||||
# type: key
|
||||
# keymap_quicktune_inc =
|
||||
|
||||
|
|
|
@ -56,5 +56,3 @@ script: |
|
|||
# Is a backup icon location in case
|
||||
mkdir -p AppDir/usr/share/luanti/misc
|
||||
cp AppDir/usr/share/icons/hicolor/128x128/apps/luanti.png AppDir/usr/share/luanti/misc/luanti-xorg-icon-128.png
|
||||
# Validation issues
|
||||
sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/org.luanti.luanti.desktop
|
||||
|
|
|
@ -7,7 +7,7 @@ Comment[fr]=Plate-forme de jeu multijoueurs à base de blocs
|
|||
Exec=luanti
|
||||
Icon=luanti
|
||||
Terminal=false
|
||||
PrefersNonDefaultGPU=true
|
||||
# Note: don't add PrefersNonDefaultGPU here, see #16095
|
||||
Type=Application
|
||||
Categories=Game;Simulation;
|
||||
StartupNotify=false
|
||||
|
|
|
@ -174,6 +174,6 @@
|
|||
<update_contact>celeron55@gmail.com</update_contact>
|
||||
|
||||
<releases>
|
||||
<release date="2025-02-14" version="5.11.0"/>
|
||||
<release date="2025-05-23" version="5.12.0"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: German (Minetest)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2025-04-25 19:50+0000\n"
|
||||
"Last-Translator: Wuzzy <Wuzzy@disroot.org>\n"
|
||||
"PO-Revision-Date: 2025-05-16 10:31+0000\n"
|
||||
"Last-Translator: sfan5 <sfan5@live.de>\n"
|
||||
"Language-Team: German <https://hosted.weblate.org/projects/minetest/minetest/"
|
||||
"de/>\n"
|
||||
"Language: de\n"
|
||||
|
@ -937,29 +937,27 @@ msgstr "Die Welt „$1“ löschen?"
|
|||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "As a result, your keybindings may have been changed."
|
||||
msgstr ""
|
||||
msgstr "Aufgrund dessen könnte sich Ihre Tastenbelegung geändert haben."
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Check out the key settings or refer to the documentation:"
|
||||
msgstr ""
|
||||
msgstr "Schauen Sie sich die Einstellungen oder die Dokumentation an:"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Schließen"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Keybindings changed"
|
||||
msgstr "Tastenbelegung"
|
||||
msgstr "Geänderte Tastenbelegung"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Open settings"
|
||||
msgstr "Einstellungen"
|
||||
msgstr "Einstellungen öffnen"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "The input handling system was reworked in Luanti 5.12.0."
|
||||
msgstr ""
|
||||
msgstr "Das Steuerungssystem wurde in Luanti 5.12.0 überarbeitet."
|
||||
|
||||
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
|
||||
msgid "Confirm Password"
|
||||
|
@ -4660,7 +4658,7 @@ msgid ""
|
|||
"Instrument global callback functions on registration.\n"
|
||||
"(anything you pass to a core.register_*() function)"
|
||||
msgstr ""
|
||||
"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren\n"
|
||||
"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren.\n"
|
||||
"(alles, was man einer Funktion wie core.register_*() übergibt)."
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: minetest\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2022-04-29 20:12+0000\n"
|
||||
"Last-Translator: JonAnder Oier <jonanderetaoier@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-05-21 10:48+0000\n"
|
||||
"Last-Translator: Josu Igoa <josuigoa@ni.eus>\n"
|
||||
"Language-Team: Basque <https://hosted.weblate.org/projects/minetest/minetest/"
|
||||
"eu/>\n"
|
||||
"Language: eu\n"
|
||||
|
@ -12,7 +12,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.12.1\n"
|
||||
"X-Generator: Weblate 5.12-dev\n"
|
||||
|
||||
#: builtin/client/chatcommands.lua
|
||||
msgid "Clear the out chat queue"
|
||||
|
@ -23,9 +23,8 @@ msgid "Empty command."
|
|||
msgstr "Agindu hutsa."
|
||||
|
||||
#: builtin/client/chatcommands.lua
|
||||
#, fuzzy
|
||||
msgid "Exit to main menu"
|
||||
msgstr "Itzuli menu nagusira"
|
||||
msgstr "Irten menu nagusira"
|
||||
|
||||
#: builtin/client/chatcommands.lua
|
||||
msgid "Invalid command: "
|
||||
|
@ -64,9 +63,8 @@ msgid "Command not available: "
|
|||
msgstr "Komandoa ez dago eskuragarri: "
|
||||
|
||||
#: builtin/common/chatcommands.lua
|
||||
#, fuzzy
|
||||
msgid "Get help for commands (-t: output in chat)"
|
||||
msgstr "Eskuratu laguntza komandoetarako"
|
||||
msgstr "Eskuratu laguntza komandoetarako (-t: irteera txat-ean)"
|
||||
|
||||
#: builtin/common/chatcommands.lua
|
||||
msgid ""
|
||||
|
@ -76,9 +74,8 @@ msgstr ""
|
|||
"zerrendatzeko."
|
||||
|
||||
#: builtin/common/chatcommands.lua
|
||||
#, fuzzy
|
||||
msgid "[all | <cmd>] [-t]"
|
||||
msgstr "[guztia | <cmd>]"
|
||||
msgstr "[all | <cmd>] [-t]"
|
||||
|
||||
#: builtin/common/settings/components.lua
|
||||
msgid "Browse"
|
||||
|
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: French (Minetest)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2025-04-28 18:58+0000\n"
|
||||
"PO-Revision-Date: 2025-05-18 17:31+0000\n"
|
||||
"Last-Translator: waxtatect <piero@live.ie>\n"
|
||||
"Language-Team: French <https://hosted.weblate.org/projects/minetest/minetest/"
|
||||
"fr/>\n"
|
||||
|
@ -938,29 +938,27 @@ msgstr "Supprimer le monde « $1 » ?"
|
|||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "As a result, your keybindings may have been changed."
|
||||
msgstr ""
|
||||
msgstr "Ainsi, il est possible que certaines touches aient été modifiées."
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Check out the key settings or refer to the documentation:"
|
||||
msgstr ""
|
||||
msgstr "Vérifier les paramètres des touches ou consulter la documentation :"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Fermer"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Keybindings changed"
|
||||
msgstr "Raccourcis clavier"
|
||||
msgstr "Modification des raccourcis clavier"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Open settings"
|
||||
msgstr "Paramètres"
|
||||
msgstr "Ouvrir les paramètres"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "The input handling system was reworked in Luanti 5.12.0."
|
||||
msgstr ""
|
||||
msgstr "Le système d'affectation des touches a été remanié dans Luanti 5.12.0."
|
||||
|
||||
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
|
||||
msgid "Confirm Password"
|
||||
|
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: Indonesian (Minetest)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2025-04-25 10:52+0000\n"
|
||||
"PO-Revision-Date: 2025-05-15 23:01+0000\n"
|
||||
"Last-Translator: Linerly <linerly@proton.me>\n"
|
||||
"Language-Team: Indonesian <https://hosted.weblate.org/projects/minetest/"
|
||||
"minetest/id/>\n"
|
||||
|
@ -12,7 +12,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.11.1-dev\n"
|
||||
"X-Generator: Weblate 5.12-dev\n"
|
||||
|
||||
#: builtin/client/chatcommands.lua
|
||||
msgid "Clear the out chat queue"
|
||||
|
@ -930,29 +930,27 @@ msgstr "Hapus dunia \"$1\"?"
|
|||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "As a result, your keybindings may have been changed."
|
||||
msgstr ""
|
||||
msgstr "Oleh sebab itu, pengikatan tombol kamu dapat berubah."
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Check out the key settings or refer to the documentation:"
|
||||
msgstr ""
|
||||
msgstr "Periksa pengaturan tombol atau rujuk pada dokumentasi:"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Tutup"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Keybindings changed"
|
||||
msgstr "Pengikatan tombol"
|
||||
msgstr "Pengikatan tombol berubah"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Open settings"
|
||||
msgstr "Pengaturan"
|
||||
msgstr "Buka pengaturan"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "The input handling system was reworked in Luanti 5.12.0."
|
||||
msgstr ""
|
||||
msgstr "Sistem penanganan masukan telah dikerjakan ulang dalam Luanti 5.12.0."
|
||||
|
||||
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
|
||||
msgid "Confirm Password"
|
||||
|
|
783
po/it/luanti.po
783
po/it/luanti.po
File diff suppressed because it is too large
Load diff
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: Portuguese (Minetest)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2025-05-12 16:41+0000\n"
|
||||
"Last-Translator: Felipe Amaral <contato.feamaral@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-05-18 15:01+0000\n"
|
||||
"Last-Translator: Ian Pedras <ian@pedras.org>\n"
|
||||
"Language-Team: Portuguese <https://hosted.weblate.org/projects/minetest/"
|
||||
"minetest/pt/>\n"
|
||||
"Language: pt\n"
|
||||
|
@ -934,30 +934,29 @@ msgid "Delete World \"$1\"?"
|
|||
msgstr "Eliminar mundo \"$1\"?"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "As a result, your keybindings may have been changed."
|
||||
msgstr ""
|
||||
msgstr "Como resultado, os seus comandos foram mudados."
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Check out the key settings or refer to the documentation:"
|
||||
msgstr ""
|
||||
msgstr "Veja as configurações ou refera a decumentação:"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Fechar"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Keybindings changed"
|
||||
msgstr "Combinações de teclas."
|
||||
msgstr "Combinações de teclas mudadas"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Open settings"
|
||||
msgstr "Definições"
|
||||
msgstr "Abrir Definições"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "The input handling system was reworked in Luanti 5.12.0."
|
||||
msgstr ""
|
||||
msgstr "O sistema de combinações de teclas foi remodelado no Luanti 5.12.0."
|
||||
|
||||
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
|
||||
msgid "Confirm Password"
|
||||
|
@ -2058,7 +2057,7 @@ msgstr "Scroll Lock"
|
|||
#. ~ Key name
|
||||
#: src/client/keycode.cpp
|
||||
msgid "Select"
|
||||
msgstr "Seleccionar"
|
||||
msgstr "Selecionar"
|
||||
|
||||
#: src/client/keycode.cpp
|
||||
msgid "Shift Key"
|
||||
|
@ -2115,14 +2114,14 @@ msgid "Minimap in texture mode"
|
|||
msgstr "Minimapa em modo de textura"
|
||||
|
||||
#: src/client/shader.cpp
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "Failed to compile the \"%s\" shader."
|
||||
msgstr "Falha ao abrir página da web"
|
||||
msgstr "Falha ao compilar o \"%s\" shader."
|
||||
|
||||
#: src/client/shader.cpp
|
||||
#, fuzzy
|
||||
msgid "GLSL is not supported by the driver"
|
||||
msgstr "Som do sistema não é suportado nesta versão"
|
||||
msgstr "GLSL não é suportado pelo seu sistema"
|
||||
|
||||
#. ~ Error when a mod is missing dependencies. Ex: "Mod Title is missing: mod1, mod2, mod3"
|
||||
#: src/content/mod_configuration.cpp
|
||||
|
@ -2168,11 +2167,11 @@ msgstr "Continuar"
|
|||
|
||||
#: src/gui/guiOpenURL.cpp
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
msgstr "Abrir"
|
||||
|
||||
#: src/gui/guiOpenURL.cpp
|
||||
msgid "Open URL?"
|
||||
msgstr ""
|
||||
msgstr "Abrir URL?"
|
||||
|
||||
#: src/gui/guiOpenURL.cpp
|
||||
#, fuzzy
|
||||
|
@ -2207,33 +2206,34 @@ msgstr "Volume do som: %d%%"
|
|||
#: src/gui/touchscreeneditor.cpp
|
||||
#, fuzzy
|
||||
msgid "Add button"
|
||||
msgstr "Roda do Rato"
|
||||
msgstr "Adicionar botão"
|
||||
|
||||
#: src/gui/touchscreeneditor.cpp
|
||||
#, fuzzy
|
||||
msgid "Done"
|
||||
msgstr "Feito!"
|
||||
msgstr "Feito"
|
||||
|
||||
#: src/gui/touchscreeneditor.cpp
|
||||
#, fuzzy
|
||||
msgid "Remove"
|
||||
msgstr "Servidor remoto"
|
||||
msgstr "Remover"
|
||||
|
||||
#: src/gui/touchscreeneditor.cpp
|
||||
#, fuzzy
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
msgstr "Refazer"
|
||||
|
||||
#: src/gui/touchscreeneditor.cpp
|
||||
#, fuzzy
|
||||
msgid "Start dragging a button to add. Tap outside to cancel."
|
||||
msgstr ""
|
||||
msgstr "Começe a arrastar um botão para adicionar. Clique fora para cancelar."
|
||||
|
||||
#: src/gui/touchscreeneditor.cpp
|
||||
msgid "Tap a button to select it. Drag a button to move it."
|
||||
msgstr ""
|
||||
msgstr "Clique num botão para selecioná-lo. Arraste o botão para movê-lo."
|
||||
|
||||
#: src/gui/touchscreeneditor.cpp
|
||||
msgid "Tap outside to deselect."
|
||||
msgstr ""
|
||||
msgstr "Click fora para desselecionar."
|
||||
|
||||
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
|
||||
msgid "Aux1"
|
||||
|
@ -2245,7 +2245,7 @@ msgstr "Mudar camera"
|
|||
|
||||
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
|
||||
msgid "Dig/punch/use"
|
||||
msgstr ""
|
||||
msgstr "Escavar/Attacar/Usar"
|
||||
|
||||
#: src/gui/touchscreenlayout.cpp
|
||||
msgid "Drop"
|
||||
|
@ -2370,27 +2370,32 @@ msgstr ""
|
|||
|
||||
#: src/network/clientpackethandler.cpp
|
||||
msgid "The server is running in singleplayer mode. You cannot connect."
|
||||
msgstr ""
|
||||
msgstr "Este servidor está a correr no modo uni jogador. Não se pode connectar."
|
||||
|
||||
#: src/network/clientpackethandler.cpp
|
||||
msgid "Too many users"
|
||||
msgstr ""
|
||||
msgstr "Demasiados utilizadores"
|
||||
|
||||
#: src/network/clientpackethandler.cpp
|
||||
msgid "Unknown disconnect reason."
|
||||
msgstr ""
|
||||
msgstr "Razão de desconecto desconhecido."
|
||||
|
||||
#: src/network/clientpackethandler.cpp
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Your client sent something the server didn't expect. Try reconnecting or "
|
||||
"updating your client."
|
||||
msgstr ""
|
||||
"O seu cliente mandou algo que o servidor não esperava. Tente reconectar ou "
|
||||
"atualizar o seu cliente."
|
||||
|
||||
#: src/network/clientpackethandler.cpp
|
||||
msgid ""
|
||||
"Your client's version is not supported.\n"
|
||||
"Please contact the server administrator."
|
||||
msgstr ""
|
||||
"A versão do seu cliente não é supportado.\n"
|
||||
"Por favor contacte o administrador do servidor."
|
||||
|
||||
#: src/server.cpp
|
||||
#, c-format
|
||||
|
@ -2465,7 +2470,7 @@ msgstr "Ruído 2D que localiza os vales e canais dos rios."
|
|||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "3D"
|
||||
msgstr ""
|
||||
msgstr "3D"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "3D clouds"
|
||||
|
@ -2638,13 +2643,13 @@ msgid ""
|
|||
msgstr ""
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Allow clouds to look 3D instead of flat."
|
||||
msgstr "Usar nuvens 3D em vez de planas."
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Allows liquids to be translucent."
|
||||
msgstr ""
|
||||
msgstr "Permitir liquidos semi transparentes."
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid ""
|
||||
|
@ -2697,8 +2702,9 @@ msgid "Anticheat flags"
|
|||
msgstr ""
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Anticheat movement tolerance"
|
||||
msgstr ""
|
||||
msgstr "Tolerância de movimentos anti-batota"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Append item name"
|
||||
|
@ -2724,8 +2730,9 @@ msgid ""
|
|||
msgstr ""
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Apply specular shading to nodes."
|
||||
msgstr ""
|
||||
msgstr "Aplicar shading especular aos nodes."
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Arm inertia"
|
||||
|
@ -2824,9 +2831,8 @@ msgid "Base terrain height."
|
|||
msgstr "Altura base do terreno."
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Base texture size"
|
||||
msgstr "Tamanho mínimo da textura"
|
||||
msgstr "Tamanho base da textura"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Basic privileges"
|
||||
|
@ -2849,9 +2855,8 @@ msgid "Bind address"
|
|||
msgstr "Endereço de bind"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Biome API"
|
||||
msgstr "Biomas"
|
||||
msgstr "API de Biomas"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Biome noise"
|
||||
|
@ -3016,13 +3021,12 @@ msgid "Client"
|
|||
msgstr "Cliente"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Client Debugging"
|
||||
msgstr "Debugging"
|
||||
msgstr "Debugging de cliente"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Client Mesh Chunksize"
|
||||
msgstr ""
|
||||
msgstr "Tamhãnho do Client Mesh Chunksize"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Client and Server"
|
||||
|
@ -3063,7 +3067,7 @@ msgstr "Nuvens no menu"
|
|||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Color depth for post-processing texture"
|
||||
msgstr ""
|
||||
msgstr "Profundidade da cor para a textura post-processing"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Colored fog"
|
||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: Russian (Minetest)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2025-05-03 05:01+0000\n"
|
||||
"Last-Translator: Nana_M <sab.pyrope@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-05-16 16:16+0000\n"
|
||||
"Last-Translator: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>\n"
|
||||
"Language-Team: Russian <https://hosted.weblate.org/projects/minetest/"
|
||||
"minetest/ru/>\n"
|
||||
"Language: ru\n"
|
||||
|
@ -934,29 +934,27 @@ msgstr "Удалить мир «$1»?"
|
|||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "As a result, your keybindings may have been changed."
|
||||
msgstr ""
|
||||
msgstr "В результате ваши привязки клавиш, возможно, были изменены."
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Check out the key settings or refer to the documentation:"
|
||||
msgstr ""
|
||||
msgstr "Ознакомьтесь с настройками клавиш или обратитесь к документации:"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Закрыть"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Keybindings changed"
|
||||
msgstr "Привязки клавиш"
|
||||
msgstr "Изменены привязки клавиш"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Open settings"
|
||||
msgstr "Настройки"
|
||||
msgstr "Открыть настройки"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "The input handling system was reworked in Luanti 5.12.0."
|
||||
msgstr ""
|
||||
msgstr "Система обработки входных данных была переработана в Luanti 5.12.0."
|
||||
|
||||
#: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp
|
||||
msgid "Confirm Password"
|
||||
|
@ -2250,7 +2248,7 @@ msgstr "Бросить"
|
|||
|
||||
#: src/gui/touchscreenlayout.cpp
|
||||
msgid "Exit"
|
||||
msgstr "Закрыть"
|
||||
msgstr "Выйти"
|
||||
|
||||
#: src/gui/touchscreenlayout.cpp
|
||||
msgid "Inventory"
|
||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
|||
"Project-Id-Version: Chinese (Simplified) (Minetest)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-05-14 23:02+0200\n"
|
||||
"PO-Revision-Date: 2025-02-24 16:36+0000\n"
|
||||
"Last-Translator: BX Zhang <zbx1425@outlook.com>\n"
|
||||
"PO-Revision-Date: 2025-05-15 23:02+0000\n"
|
||||
"Last-Translator: y5nw <y5nw@users.noreply.hosted.weblate.org>\n"
|
||||
"Language-Team: Chinese (Simplified Han script) <https://hosted.weblate.org/"
|
||||
"projects/minetest/minetest/zh_Hans/>\n"
|
||||
"Language: zh_CN\n"
|
||||
|
@ -12,7 +12,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.10.1-dev\n"
|
||||
"X-Generator: Weblate 5.12-dev\n"
|
||||
|
||||
#: builtin/client/chatcommands.lua
|
||||
msgid "Clear the out chat queue"
|
||||
|
@ -82,16 +82,15 @@ msgstr "浏览"
|
|||
|
||||
#: builtin/common/settings/components.lua
|
||||
msgid "Conflicts with \"$1\""
|
||||
msgstr ""
|
||||
msgstr "与“$1”冲突"
|
||||
|
||||
#: builtin/common/settings/components.lua
|
||||
msgid "Edit"
|
||||
msgstr "编辑"
|
||||
|
||||
#: builtin/common/settings/components.lua
|
||||
#, fuzzy
|
||||
msgid "Remove keybinding"
|
||||
msgstr "按键绑定。"
|
||||
msgstr "移除按键绑定"
|
||||
|
||||
#: builtin/common/settings/components.lua
|
||||
msgid "Select directory"
|
||||
|
@ -253,7 +252,7 @@ msgstr "通用"
|
|||
|
||||
#: builtin/common/settings/dlg_settings.lua
|
||||
msgid "Long tap"
|
||||
msgstr ""
|
||||
msgstr "长按"
|
||||
|
||||
#: builtin/common/settings/dlg_settings.lua
|
||||
msgid "Movement"
|
||||
|
@ -558,12 +557,11 @@ msgstr "捐赠"
|
|||
|
||||
#: builtin/mainmenu/content/dlg_package.lua
|
||||
msgid "Error loading package information"
|
||||
msgstr ""
|
||||
msgstr "无法获取软件包信息"
|
||||
|
||||
#: builtin/mainmenu/content/dlg_package.lua
|
||||
#, fuzzy
|
||||
msgid "Error loading reviews"
|
||||
msgstr "创建客户端出错:%s"
|
||||
msgstr "无法加载评价"
|
||||
|
||||
#: builtin/mainmenu/content/dlg_package.lua
|
||||
msgid "Forum Topic"
|
||||
|
@ -583,7 +581,7 @@ msgstr "问题跟踪器"
|
|||
|
||||
#: builtin/mainmenu/content/dlg_package.lua
|
||||
msgid "Reviews"
|
||||
msgstr ""
|
||||
msgstr "评价"
|
||||
|
||||
#: builtin/mainmenu/content/dlg_package.lua
|
||||
msgid "Source"
|
||||
|
@ -638,10 +636,11 @@ msgid "Unable to install a $1 as a texture pack"
|
|||
msgstr "无法将$1安装为材质包"
|
||||
|
||||
#: builtin/mainmenu/dlg_clients_list.lua
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Players connected to\n"
|
||||
"$1"
|
||||
msgstr ""
|
||||
msgstr "连接到$1的玩家"
|
||||
|
||||
#: builtin/mainmenu/dlg_config_world.lua
|
||||
msgid "(Enabled, has error)"
|
||||
|
@ -924,25 +923,23 @@ msgstr "删除世界“$1”?"
|
|||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "As a result, your keybindings may have been changed."
|
||||
msgstr ""
|
||||
msgstr "您的一些按键绑定有可能已被更改。"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Check out the key settings or refer to the documentation:"
|
||||
msgstr ""
|
||||
msgstr "请查看按键绑定设置或参考文档:"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "关闭"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Keybindings changed"
|
||||
msgstr "按键绑定。"
|
||||
msgstr "按键绑定已被更改"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
#, fuzzy
|
||||
msgid "Open settings"
|
||||
msgstr "设置"
|
||||
msgstr "打开设置"
|
||||
|
||||
#: builtin/mainmenu/dlg_rebind_keys.lua
|
||||
msgid "The input handling system was reworked in Luanti 5.12.0."
|
||||
|
@ -1292,12 +1289,11 @@ msgid "Ping"
|
|||
msgstr "ping值"
|
||||
|
||||
#: builtin/mainmenu/tab_online.lua
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Players:\n"
|
||||
"$1"
|
||||
msgstr ""
|
||||
"客户端:\n"
|
||||
"玩家::\n"
|
||||
"$1"
|
||||
|
||||
#: builtin/mainmenu/tab_online.lua
|
||||
|
@ -2247,7 +2243,7 @@ msgstr "溢出菜单"
|
|||
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Place/use"
|
||||
msgstr "放置键"
|
||||
msgstr "放置或使用"
|
||||
|
||||
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
|
||||
msgid "Range select"
|
||||
|
@ -2262,9 +2258,8 @@ msgid "Toggle chat log"
|
|||
msgstr "启用/禁用聊天记录"
|
||||
|
||||
#: src/gui/touchscreenlayout.cpp
|
||||
#, fuzzy
|
||||
msgid "Toggle debug"
|
||||
msgstr "启用/禁用雾"
|
||||
msgstr "显示/隐藏调试信息"
|
||||
|
||||
#: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp
|
||||
msgid "Toggle fast"
|
||||
|
@ -2301,9 +2296,8 @@ msgid "Internal server error"
|
|||
msgstr "内部服务器错误"
|
||||
|
||||
#: src/network/clientpackethandler.cpp
|
||||
#, fuzzy
|
||||
msgid "Invalid password"
|
||||
msgstr "旧密码"
|
||||
msgstr "密码错误"
|
||||
|
||||
#. ~ DO NOT TRANSLATE THIS LITERALLY!
|
||||
#. This is a special string which needs to contain the translation's
|
||||
|
@ -2977,9 +2971,8 @@ msgid "Client"
|
|||
msgstr "客户端"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Client Debugging"
|
||||
msgstr "调试"
|
||||
msgstr "客户端调试"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Client Mesh Chunksize"
|
||||
|
@ -3233,7 +3226,6 @@ msgid "Decrease view range"
|
|||
msgstr "减少可视范围"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Decrease volume"
|
||||
msgstr "减小音量"
|
||||
|
||||
|
@ -3446,9 +3438,8 @@ msgid ""
|
|||
msgstr ""
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Drop item"
|
||||
msgstr "丢弃物品键"
|
||||
msgstr "丢弃物品"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Dump the mapgen debug information."
|
||||
|
@ -4133,164 +4124,132 @@ msgstr ""
|
|||
"单位为方块每二次方秒。"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 1"
|
||||
msgstr "快捷栏1键"
|
||||
msgstr "快捷栏第1项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 10"
|
||||
msgstr "快捷栏10键"
|
||||
msgstr "快捷栏第10项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 11"
|
||||
msgstr "快捷栏11键"
|
||||
msgstr "快捷栏第11项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 12"
|
||||
msgstr "快捷栏12键"
|
||||
msgstr "快捷栏第12项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 13"
|
||||
msgstr "快捷栏13键"
|
||||
msgstr "快捷栏第13项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 14"
|
||||
msgstr "快捷栏14键"
|
||||
msgstr "快捷栏第14项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 15"
|
||||
msgstr "快捷栏15键"
|
||||
msgstr "快捷栏第15项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 16"
|
||||
msgstr "快捷栏16键"
|
||||
msgstr "快捷栏第16项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 17"
|
||||
msgstr "快捷栏17键"
|
||||
msgstr "快捷栏第17项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 18"
|
||||
msgstr "快捷栏18键"
|
||||
msgstr "快捷栏第18项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 19"
|
||||
msgstr "快捷栏19键"
|
||||
msgstr "快捷栏第19项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 2"
|
||||
msgstr "快捷栏2键"
|
||||
msgstr "快捷栏第2项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 20"
|
||||
msgstr "快捷栏20键"
|
||||
msgstr "快捷栏第20项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 21"
|
||||
msgstr "快捷栏21键"
|
||||
msgstr "快捷栏第21项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 22"
|
||||
msgstr "快捷栏22键"
|
||||
msgstr "快捷栏第22项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 23"
|
||||
msgstr "快捷栏23键"
|
||||
msgstr "快捷栏第23项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 24"
|
||||
msgstr "快捷栏24键"
|
||||
msgstr "快捷栏第24项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 25"
|
||||
msgstr "快捷栏25键"
|
||||
msgstr "快捷栏第25项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 26"
|
||||
msgstr "快捷栏26键"
|
||||
msgstr "快捷栏第26项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 27"
|
||||
msgstr "快捷栏27键"
|
||||
msgstr "快捷栏第27项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 28"
|
||||
msgstr "快捷栏28键"
|
||||
msgstr "快捷栏第28项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 29"
|
||||
msgstr "快捷栏29键"
|
||||
msgstr "快捷栏第29项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 3"
|
||||
msgstr "快捷栏3键"
|
||||
msgstr "快捷栏第3项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 30"
|
||||
msgstr "快捷栏30键"
|
||||
msgstr "快捷栏第30项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 31"
|
||||
msgstr "快捷栏31键"
|
||||
msgstr "快捷栏第31项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 32"
|
||||
msgstr "快捷栏32键"
|
||||
msgstr "快捷栏第32项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 4"
|
||||
msgstr "快捷栏4键"
|
||||
msgstr "快捷栏第4项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 5"
|
||||
msgstr "快捷栏5键"
|
||||
msgstr "快捷栏第5项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 6"
|
||||
msgstr "快捷栏6键"
|
||||
msgstr "快捷栏第6项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 7"
|
||||
msgstr "快捷栏7键"
|
||||
msgstr "快捷栏第7项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 8"
|
||||
msgstr "快捷栏8键"
|
||||
msgstr "快捷栏第8项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar slot 9"
|
||||
msgstr "快捷栏9键"
|
||||
msgstr "快捷栏第9项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Hotbar: Enable mouse wheel for selection"
|
||||
|
@ -4301,14 +4260,12 @@ msgid "Hotbar: Invert mouse wheel direction"
|
|||
msgstr "快捷栏:反转鼠标滚轮方向"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar: select next item"
|
||||
msgstr "快捷栏下一个键"
|
||||
msgstr "选择快捷栏下一项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Hotbar: select previous item"
|
||||
msgstr "快捷栏上一个键"
|
||||
msgstr "选择快捷栏上一项"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "How deep to make rivers."
|
||||
|
@ -4513,7 +4470,6 @@ msgid "Increase view range"
|
|||
msgstr "增加可视范围"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Increase volume"
|
||||
msgstr "增大音量"
|
||||
|
||||
|
@ -5031,9 +4987,8 @@ msgid "Key to use view zoom when possible."
|
|||
msgstr ""
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Keybindings"
|
||||
msgstr "按键绑定。"
|
||||
msgstr "按键绑定"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Keyboard and Mouse"
|
||||
|
@ -5072,9 +5027,8 @@ msgid "Large cave proportion flooded"
|
|||
msgstr "大型洞穴淹没比"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Large chat console"
|
||||
msgstr "大型聊天控制台键"
|
||||
msgstr "大型聊天控制台"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Leaves style"
|
||||
|
@ -5714,23 +5668,20 @@ msgid "Mouse sensitivity multiplier."
|
|||
msgstr "鼠标灵敏度倍数。"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Move backward"
|
||||
msgstr "向后"
|
||||
msgstr "后退"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Move forward"
|
||||
msgstr "自动向前"
|
||||
msgstr "前进"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Move left"
|
||||
msgstr "移动"
|
||||
msgstr "往左移动"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Move right"
|
||||
msgstr ""
|
||||
msgstr "往右移动"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
|
@ -5879,14 +5830,12 @@ msgid ""
|
|||
msgstr "默认字体后阴影的透明度(alpha),取值范围0~255。"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Open chat"
|
||||
msgstr "打开"
|
||||
msgstr "打开聊天室"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Open inventory"
|
||||
msgstr "物品栏"
|
||||
msgstr "打开物品栏"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid ""
|
||||
|
@ -7174,14 +7123,12 @@ msgid "Toggle Sneak key"
|
|||
msgstr "启用/禁用拍照模式键"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Toggle automatic forward"
|
||||
msgstr "自动前进键"
|
||||
msgstr "启用/禁用自动前进"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Toggle block bounds"
|
||||
msgstr "地图块边界"
|
||||
msgstr "显示/隐藏地图块边界"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
|
@ -7199,18 +7146,16 @@ msgid "Toggle cinematic mode"
|
|||
msgstr "切换电影模式"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Toggle debug info"
|
||||
msgstr "启用/禁用雾"
|
||||
msgstr "显示/隐藏调试信息"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Toggle fog"
|
||||
msgstr "启用/禁用雾"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Toggle fullscreen"
|
||||
msgstr "启用/禁用飞行模式"
|
||||
msgstr "切换全屏模式"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Toggle pitchmove"
|
||||
|
@ -7238,9 +7183,8 @@ msgid "Touchscreen"
|
|||
msgstr "触摸屏"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
#, fuzzy
|
||||
msgid "Touchscreen controls"
|
||||
msgstr "触屏阈值"
|
||||
msgstr "触屏控制"
|
||||
|
||||
#: src/settings_translation_file.cpp
|
||||
msgid "Touchscreen sensitivity"
|
||||
|
|
|
@ -147,8 +147,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
|
|||
/*
|
||||
Menu-game loop
|
||||
*/
|
||||
bool retval = true;
|
||||
bool *kill = porting::signal_handler_killstatus();
|
||||
bool retval = true;
|
||||
volatile auto *kill = porting::signal_handler_killstatus();
|
||||
|
||||
while (m_rendering_engine->run() && !*kill &&
|
||||
!g_gamecallback->shutdown_requested) {
|
||||
|
@ -326,6 +326,18 @@ void ClientLauncher::setting_changed_callback(const std::string &name, void *dat
|
|||
static_cast<ClientLauncher*>(data)->config_guienv();
|
||||
}
|
||||
|
||||
static video::ITexture *loadTexture(video::IVideoDriver *driver, const char *path)
|
||||
{
|
||||
// FIXME?: it would be cleaner to do this through a ITextureSource, but we don't have one
|
||||
video::ITexture *texture = nullptr;
|
||||
verbosestream << "Loading texture " << path << std::endl;
|
||||
if (auto *image = driver->createImageFromFile(path); image) {
|
||||
texture = driver->addTexture(fs::GetFilenameFromPath(path), image);
|
||||
image->drop();
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
void ClientLauncher::config_guienv()
|
||||
{
|
||||
gui::IGUISkin *skin = guienv->getSkin();
|
||||
|
@ -364,10 +376,9 @@ void ClientLauncher::config_guienv()
|
|||
if (cached_id != sprite_ids.end()) {
|
||||
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, cached_id->second);
|
||||
} else {
|
||||
gui::IGUISpriteBank *sprites = skin->getSpriteBank();
|
||||
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
|
||||
video::ITexture *texture = driver->getTexture(path.c_str());
|
||||
s32 id = sprites->addTextureAsSprite(texture);
|
||||
auto *driver = m_rendering_engine->get_video_driver();
|
||||
auto *texture = loadTexture(driver, path.c_str());
|
||||
s32 id = skin->getSpriteBank()->addTextureAsSprite(texture);
|
||||
if (id != -1) {
|
||||
skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, id);
|
||||
sprite_ids.emplace(path, id);
|
||||
|
@ -529,9 +540,9 @@ bool ClientLauncher::launch_game(std::string &error_message,
|
|||
|
||||
void ClientLauncher::main_menu(MainMenuData *menudata)
|
||||
{
|
||||
bool *kill = porting::signal_handler_killstatus();
|
||||
volatile auto *kill = porting::signal_handler_killstatus();
|
||||
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
|
||||
auto *device = m_rendering_engine->get_raw_device();
|
||||
auto *device = m_rendering_engine->get_raw_device();
|
||||
|
||||
// Wait until app is in foreground because of #15883
|
||||
infostream << "Waiting for app to be in foreground" << std::endl;
|
||||
|
|
|
@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
|
|||
matrix.setTextureScale(txs, tys);
|
||||
}
|
||||
|
||||
// Evaluate transform chain recursively; irrlicht does not do this for us
|
||||
static void updatePositionRecursive(scene::ISceneNode *node)
|
||||
{
|
||||
scene::ISceneNode *parent = node->getParent();
|
||||
if (parent)
|
||||
updatePositionRecursive(parent);
|
||||
node->updateAbsolutePosition();
|
||||
}
|
||||
|
||||
static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
|
||||
{
|
||||
thread_local std::vector<u64> logged;
|
||||
|
@ -682,7 +673,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
|
|||
m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
|
||||
m_animated_meshnode->grab();
|
||||
mesh->drop(); // The scene node took hold of it
|
||||
m_animated_meshnode->animateJoints(); // Needed for some animations
|
||||
m_animated_meshnode->setScale(m_prop.visual_size);
|
||||
|
||||
// set vertex colors to ensure alpha is set
|
||||
|
@ -693,6 +683,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
|
|||
m_animated_meshnode->forEachMaterial([this] (auto &mat) {
|
||||
mat.BackfaceCulling = m_prop.backface_culling;
|
||||
});
|
||||
|
||||
m_animated_meshnode->setOnAnimateCallback([&](f32 dtime) {
|
||||
for (auto &it : m_bone_override) {
|
||||
auto* bone = m_animated_meshnode->getJointNode(it.first.c_str());
|
||||
if (!bone)
|
||||
continue;
|
||||
|
||||
BoneOverride &props = it.second;
|
||||
props.dtime_passed += dtime;
|
||||
|
||||
bone->setPosition(props.getPosition(bone->getPosition()));
|
||||
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
|
||||
bone->setScale(props.getScale(bone->getScale()));
|
||||
}
|
||||
});
|
||||
} else
|
||||
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
|
||||
break;
|
||||
|
@ -783,7 +788,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
|
|||
updateMarker();
|
||||
updateNodePos();
|
||||
updateAnimation();
|
||||
updateBones(.0f);
|
||||
updateAttachments();
|
||||
setNodeLight(m_last_light);
|
||||
updateMeshCulling();
|
||||
|
@ -1174,18 +1178,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
|
|||
rot_translator.val_current = m_rotation;
|
||||
updateNodePos();
|
||||
}
|
||||
|
||||
if (m_animated_meshnode) {
|
||||
// Everything must be updated; the whole transform
|
||||
// chain as well as the animated mesh node.
|
||||
// Otherwise, bone attachments would be relative to
|
||||
// a position that's one frame old.
|
||||
if (m_matrixnode)
|
||||
updatePositionRecursive(m_matrixnode);
|
||||
m_animated_meshnode->updateAbsolutePosition();
|
||||
m_animated_meshnode->animateJoints();
|
||||
updateBones(dtime);
|
||||
}
|
||||
}
|
||||
|
||||
static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count)
|
||||
|
@ -1394,44 +1386,6 @@ void GenericCAO::updateAnimationSpeed()
|
|||
m_animated_meshnode->setAnimationSpeed(m_animation_speed);
|
||||
}
|
||||
|
||||
void GenericCAO::updateBones(f32 dtime)
|
||||
{
|
||||
if (!m_animated_meshnode)
|
||||
return;
|
||||
if (m_bone_override.empty()) {
|
||||
m_animated_meshnode->setJointMode(scene::EJUOR_NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
|
||||
for (auto &it : m_bone_override) {
|
||||
std::string bone_name = it.first;
|
||||
scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
|
||||
if (!bone)
|
||||
continue;
|
||||
|
||||
BoneOverride &props = it.second;
|
||||
props.dtime_passed += dtime;
|
||||
|
||||
bone->setPosition(props.getPosition(bone->getPosition()));
|
||||
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
|
||||
bone->setScale(props.getScale(bone->getScale()));
|
||||
}
|
||||
|
||||
// The following is needed for set_bone_pos to propagate to
|
||||
// attached objects correctly.
|
||||
// Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL.
|
||||
for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
|
||||
auto bone = m_animated_meshnode->getJointNode(i);
|
||||
// Look for the root bone.
|
||||
if (bone && bone->getParent() == m_animated_meshnode) {
|
||||
// Update entire skeleton.
|
||||
bone->updateAbsolutePositionOfAllChildren();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenericCAO::updateAttachments()
|
||||
{
|
||||
ClientActiveObject *parent = getParent();
|
||||
|
@ -1747,7 +1701,6 @@ void GenericCAO::processMessage(const std::string &data)
|
|||
} else {
|
||||
m_bone_override[bone] = props;
|
||||
}
|
||||
// updateBones(); now called every step
|
||||
} else if (cmd == AO_CMD_ATTACH_TO) {
|
||||
u16 parent_id = readS16(is);
|
||||
std::string bone = deSerializeString16(is);
|
||||
|
|
|
@ -286,8 +286,6 @@ public:
|
|||
|
||||
void updateAnimationSpeed();
|
||||
|
||||
void updateBones(f32 dtime);
|
||||
|
||||
void processMessage(const std::string &data) override;
|
||||
|
||||
bool directReportPunch(v3f dir, const ItemStack *punchitem,
|
||||
|
|
|
@ -137,11 +137,20 @@ void MapblockMeshGenerator::drawQuad(const TileSpec &tile, v3f *coords, const v3
|
|||
}
|
||||
|
||||
static std::array<video::S3DVertex, 24> setupCuboidVertices(const aabb3f &box,
|
||||
const f32 *txc, const TileSpec *tiles, int tilecount)
|
||||
const f32 *txc, const TileSpec *tiles, int tilecount, v3s16 alignment)
|
||||
{
|
||||
v3f min = box.MinEdge;
|
||||
v3f max = box.MaxEdge;
|
||||
|
||||
// Texture coords are [0,1] if not specified otherwise
|
||||
f32 uniform_txc[24];
|
||||
if (!txc) {
|
||||
for (int i = 0; i != 24; ++i) {
|
||||
uniform_txc[i] = (i % 4 < 2) ? 0.0f : 1.0f;
|
||||
}
|
||||
txc = uniform_txc;
|
||||
}
|
||||
|
||||
std::array<video::S3DVertex, 24> vertices = {{
|
||||
// top
|
||||
video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, {}, txc[0], txc[1]),
|
||||
|
@ -185,15 +194,47 @@ static std::array<video::S3DVertex, 24> setupCuboidVertices(const aabb3f &box,
|
|||
case TileRotation::None:
|
||||
break;
|
||||
case TileRotation::R90:
|
||||
tcoords.set(-tcoords.Y, tcoords.X);
|
||||
tcoords.set(1 - tcoords.Y, tcoords.X);
|
||||
break;
|
||||
case TileRotation::R180:
|
||||
tcoords.set(-tcoords.X, -tcoords.Y);
|
||||
tcoords.set(1 - tcoords.X, 1 - tcoords.Y);
|
||||
break;
|
||||
case TileRotation::R270:
|
||||
tcoords.set(tcoords.Y, -tcoords.X);
|
||||
tcoords.set(tcoords.Y, 1 - tcoords.X);
|
||||
break;
|
||||
}
|
||||
|
||||
if (tile.world_aligned) {
|
||||
// Maps uv dimension of every face to world dimension xyz
|
||||
constexpr int coord_dim[12] = {
|
||||
0, 2, // up
|
||||
0, 2, // down
|
||||
2, 1, // right
|
||||
2, 1, // left
|
||||
0, 1, // back
|
||||
0, 1, // front
|
||||
};
|
||||
|
||||
auto scale = tile.layers[0].scale;
|
||||
f32 scale_factor = 1.0f / scale;
|
||||
|
||||
float x = alignment[coord_dim[face*2]] % scale;
|
||||
float y = alignment[coord_dim[face*2 + 1]] % scale;
|
||||
|
||||
// Faces grow in different directions
|
||||
if (face != 1) {
|
||||
y = tcoords.Y + ((scale-1)-y);
|
||||
} else {
|
||||
y = tcoords.Y + y;
|
||||
}
|
||||
if (face == 3 || face == 4) {
|
||||
x = tcoords.X + ((scale-1)-x);
|
||||
} else {
|
||||
x = tcoords.X + x;
|
||||
}
|
||||
|
||||
tcoords.set(x * scale_factor, y * scale_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,6 +253,7 @@ enum class QuadDiagonal {
|
|||
// for the opposite corners of each face - therefore, there
|
||||
// should be (2+2)*6=24 values in the list. The order of
|
||||
// the faces in the list is up-down-right-left-back-front
|
||||
// if nullptr use standard [0,1] coords
|
||||
// (compatible with ContentFeatures).
|
||||
// mask - a bit mask that suppresses drawing of tiles.
|
||||
// tile i will not be drawn if mask & (1 << i) is 1
|
||||
|
@ -224,7 +266,7 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
|
|||
{
|
||||
assert(tilecount >= 1 && tilecount <= 6); // pre-condition
|
||||
|
||||
auto vertices = setupCuboidVertices(box, txc, tiles, tilecount);
|
||||
auto vertices = setupCuboidVertices(box, txc, tiles, tilecount, cur_node.p);
|
||||
|
||||
for (int k = 0; k < 6; ++k) {
|
||||
if (mask & (1 << k))
|
||||
|
@ -301,12 +343,13 @@ video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
|
|||
|
||||
void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
|
||||
{
|
||||
f32 tx1 = (box.MinEdge.X / BS) + 0.5;
|
||||
f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
|
||||
f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
|
||||
f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
|
||||
f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
|
||||
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
|
||||
// Generate texture coords which are aligned to coords of a solid nodes
|
||||
f32 tx1 = (box.MinEdge.X / BS) + 0.5f;
|
||||
f32 ty1 = (box.MinEdge.Y / BS) + 0.5f;
|
||||
f32 tz1 = (box.MinEdge.Z / BS) + 0.5f;
|
||||
f32 tx2 = (box.MaxEdge.X / BS) + 0.5f;
|
||||
f32 ty2 = (box.MaxEdge.Y / BS) + 0.5f;
|
||||
f32 tz2 = (box.MaxEdge.Z / BS) + 0.5f;
|
||||
f32 txc[24] = {
|
||||
tx1, 1 - tz2, tx2, 1 - tz1, // up
|
||||
tx1, tz1, tx2, tz2, // down
|
||||
|
@ -334,7 +377,6 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box,
|
|||
const TileSpec *tiles, int tile_count, const f32 *txc, u8 mask)
|
||||
{
|
||||
bool scale = std::fabs(cur_node.f->visual_scale - 1.0f) > 1e-3f;
|
||||
f32 texture_coord_buf[24];
|
||||
f32 dx1 = box.MinEdge.X;
|
||||
f32 dy1 = box.MinEdge.Y;
|
||||
f32 dz1 = box.MinEdge.Z;
|
||||
|
@ -342,19 +384,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box,
|
|||
f32 dy2 = box.MaxEdge.Y;
|
||||
f32 dz2 = box.MaxEdge.Z;
|
||||
if (scale) {
|
||||
if (!txc) { // generate texture coords before scaling
|
||||
generateCuboidTextureCoords(box, texture_coord_buf);
|
||||
txc = texture_coord_buf;
|
||||
}
|
||||
box.MinEdge *= cur_node.f->visual_scale;
|
||||
box.MaxEdge *= cur_node.f->visual_scale;
|
||||
}
|
||||
box.MinEdge += cur_node.origin;
|
||||
box.MaxEdge += cur_node.origin;
|
||||
if (!txc) {
|
||||
generateCuboidTextureCoords(box, texture_coord_buf);
|
||||
txc = texture_coord_buf;
|
||||
}
|
||||
if (data->m_smooth_lighting) {
|
||||
LightInfo lights[8];
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
|
@ -442,10 +476,8 @@ void MapblockMeshGenerator::drawSolidNode()
|
|||
u8 mask = faces ^ 0b0011'1111; // k-th bit is set if k-th face is to be *omitted*, as expected by cuboid drawing functions.
|
||||
cur_node.origin = intToFloat(cur_node.p, BS);
|
||||
auto box = aabb3f(v3f(-0.5 * BS), v3f(0.5 * BS));
|
||||
f32 texture_coord_buf[24];
|
||||
box.MinEdge += cur_node.origin;
|
||||
box.MaxEdge += cur_node.origin;
|
||||
generateCuboidTextureCoords(box, texture_coord_buf);
|
||||
if (data->m_smooth_lighting) {
|
||||
LightPair lights[6][4];
|
||||
for (int face = 0; face < 6; ++face) {
|
||||
|
@ -458,7 +490,7 @@ void MapblockMeshGenerator::drawSolidNode()
|
|||
}
|
||||
}
|
||||
|
||||
drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) {
|
||||
drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) {
|
||||
auto final_lights = lights[face];
|
||||
for (int j = 0; j < 4; j++) {
|
||||
video::S3DVertex &vertex = vertices[j];
|
||||
|
@ -471,7 +503,7 @@ void MapblockMeshGenerator::drawSolidNode()
|
|||
return QuadDiagonal::Diag02;
|
||||
});
|
||||
} else {
|
||||
drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) {
|
||||
drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) {
|
||||
video::SColor color = encode_light(lights[face], cur_node.f->light_source);
|
||||
if (!cur_node.f->light_source)
|
||||
applyFacesShading(color, vertices[0].Normal);
|
||||
|
@ -952,7 +984,10 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
|
|||
edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
|
||||
if (edge_invisible)
|
||||
continue;
|
||||
drawAutoLightedCuboid(frame_edges[edge], tiles[1]);
|
||||
|
||||
f32 txc[24];
|
||||
generateCuboidTextureCoords(frame_edges[edge], txc);
|
||||
drawAutoLightedCuboid(frame_edges[edge], tiles[1], txc);
|
||||
}
|
||||
|
||||
for (int face = 0; face < 6; face++) {
|
||||
|
@ -996,16 +1031,17 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
|
|||
float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
|
||||
TileSpec tile;
|
||||
getSpecialTile(0, &tile);
|
||||
drawAutoLightedCuboid(
|
||||
aabb3f(
|
||||
-(nb[5] ? g : b),
|
||||
-(nb[4] ? g : b),
|
||||
-(nb[3] ? g : b),
|
||||
(nb[2] ? g : b),
|
||||
(nb[1] ? g : b) * vlev,
|
||||
(nb[0] ? g : b)
|
||||
),
|
||||
tile);
|
||||
aabb3f box(
|
||||
-(nb[5] ? g : b),
|
||||
-(nb[4] ? g : b),
|
||||
-(nb[3] ? g : b),
|
||||
(nb[2] ? g : b),
|
||||
(nb[1] ? g : b) * vlev,
|
||||
(nb[0] ? g : b)
|
||||
);
|
||||
f32 txc[24];
|
||||
generateCuboidTextureCoords(box, txc);
|
||||
drawAutoLightedCuboid(box, tile, txc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1649,7 +1685,10 @@ void MapblockMeshGenerator::drawNodeboxNode()
|
|||
|
||||
for (auto &box : boxes) {
|
||||
u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors);
|
||||
drawAutoLightedCuboid(box, tiles, 6, nullptr, mask);
|
||||
|
||||
f32 txc[24];
|
||||
generateCuboidTextureCoords(box, txc);
|
||||
drawAutoLightedCuboid(box, tiles, 6, txc, mask);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1676,7 +1715,7 @@ void MapblockMeshGenerator::drawMeshNode()
|
|||
|
||||
if (cur_node.f->mesh_ptr) {
|
||||
// clone and rotate mesh
|
||||
mesh = cloneMesh(cur_node.f->mesh_ptr);
|
||||
mesh = cloneStaticMesh(cur_node.f->mesh_ptr);
|
||||
bool modified = true;
|
||||
if (facedir)
|
||||
rotateMeshBy6dFacedir(mesh, facedir);
|
||||
|
|
|
@ -86,7 +86,7 @@ private:
|
|||
template <typename Fn>
|
||||
void drawCuboid(const aabb3f &box, const TileSpec *tiles, int tilecount,
|
||||
const f32 *txc, u8 mask, Fn &&face_lighter);
|
||||
void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
|
||||
static void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
|
||||
void drawAutoLightedCuboid(aabb3f box, const TileSpec &tile, f32 const *txc = nullptr, u8 mask = 0);
|
||||
void drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, f32 const *txc = nullptr, u8 mask = 0);
|
||||
u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const;
|
||||
|
|
|
@ -65,6 +65,8 @@
|
|||
#include "client/sound/sound_openal.h"
|
||||
#endif
|
||||
|
||||
#include <csignal>
|
||||
|
||||
class NodeDugEvent : public MtEvent
|
||||
{
|
||||
public:
|
||||
|
@ -561,7 +563,7 @@ public:
|
|||
Game();
|
||||
~Game();
|
||||
|
||||
bool startup(bool *kill,
|
||||
bool startup(volatile std::sig_atomic_t *kill,
|
||||
InputHandler *input,
|
||||
RenderingEngine *rendering_engine,
|
||||
const GameStartData &game_params,
|
||||
|
@ -793,14 +795,14 @@ private:
|
|||
This class does take ownership/responsibily for cleaning up etc of any of
|
||||
these items (e.g. device)
|
||||
*/
|
||||
IrrlichtDevice *device;
|
||||
RenderingEngine *m_rendering_engine;
|
||||
video::IVideoDriver *driver;
|
||||
scene::ISceneManager *smgr;
|
||||
bool *kill;
|
||||
std::string *error_message;
|
||||
bool *reconnect_requested;
|
||||
PausedNodesList paused_animated_nodes;
|
||||
IrrlichtDevice *device;
|
||||
RenderingEngine *m_rendering_engine;
|
||||
video::IVideoDriver *driver;
|
||||
scene::ISceneManager *smgr;
|
||||
volatile std::sig_atomic_t *kill;
|
||||
std::string *error_message;
|
||||
bool *reconnect_requested;
|
||||
PausedNodesList paused_animated_nodes;
|
||||
|
||||
bool simple_singleplayer_mode;
|
||||
/* End 'cache' */
|
||||
|
@ -932,7 +934,7 @@ Game::~Game()
|
|||
m_rendering_engine->finalize();
|
||||
}
|
||||
|
||||
bool Game::startup(bool *kill,
|
||||
bool Game::startup(volatile std::sig_atomic_t *kill,
|
||||
InputHandler *input,
|
||||
RenderingEngine *rendering_engine,
|
||||
const GameStartData &start_data,
|
||||
|
@ -1114,8 +1116,12 @@ void Game::run()
|
|||
|
||||
void Game::shutdown()
|
||||
{
|
||||
// Clear text when exiting.
|
||||
// Delete text and menus first
|
||||
m_game_ui->clearText();
|
||||
m_game_formspec.reset();
|
||||
while (g_menumgr.menuCount() > 0) {
|
||||
g_menumgr.deleteFront();
|
||||
}
|
||||
|
||||
if (g_touchcontrols)
|
||||
g_touchcontrols->hide();
|
||||
|
@ -1126,11 +1132,6 @@ void Game::shutdown()
|
|||
|
||||
sky.reset();
|
||||
|
||||
/* cleanup menus */
|
||||
while (g_menumgr.menuCount() > 0) {
|
||||
g_menumgr.deleteFront();
|
||||
}
|
||||
|
||||
// only if the shutdown progress bar isn't shown yet
|
||||
if (m_shutdown_progress == 0.0f)
|
||||
showOverlayMessage(N_("Shutting down..."), 0, 0);
|
||||
|
@ -4236,7 +4237,7 @@ void Game::readSettings()
|
|||
****************************************************************************/
|
||||
/****************************************************************************/
|
||||
|
||||
void the_game(bool *kill,
|
||||
void the_game(volatile std::sig_atomic_t *kill,
|
||||
InputHandler *input,
|
||||
RenderingEngine *rendering_engine,
|
||||
const GameStartData &start_data,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "irrlichttypes.h"
|
||||
#include "config.h"
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
|
||||
#if !IS_CLIENT_BUILD
|
||||
|
@ -36,7 +37,7 @@ struct CameraOrientation {
|
|||
#define GAME_FALLBACK_TIMEOUT 1.8f
|
||||
#define GAME_CONNECTION_TIMEOUT 10.0f
|
||||
|
||||
void the_game(bool *kill,
|
||||
void the_game(volatile std::sig_atomic_t *kill,
|
||||
InputHandler *input,
|
||||
RenderingEngine *rendering_engine,
|
||||
const GameStartData &start_data,
|
||||
|
|
|
@ -31,16 +31,6 @@ struct TextDestNodeMetadata : public TextDest
|
|||
m_p = p;
|
||||
m_client = client;
|
||||
}
|
||||
// This is deprecated I guess? -celeron55
|
||||
void gotText(const std::wstring &text)
|
||||
{
|
||||
std::string ntext = wide_to_utf8(text);
|
||||
infostream << "Submitting 'text' field of node at (" << m_p.X << ","
|
||||
<< m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
|
||||
StringMap fields;
|
||||
fields["text"] = ntext;
|
||||
m_client->sendNodemetaFields(m_p, "", fields);
|
||||
}
|
||||
void gotText(const StringMap &fields)
|
||||
{
|
||||
m_client->sendNodemetaFields(m_p, "", fields);
|
||||
|
@ -217,10 +207,11 @@ void GameFormSpec::deleteFormspec()
|
|||
}
|
||||
}
|
||||
|
||||
GameFormSpec::~GameFormSpec() {
|
||||
void GameFormSpec::reset()
|
||||
{
|
||||
if (m_formspec)
|
||||
m_formspec->quitMenu();
|
||||
this->deleteFormspec();
|
||||
deleteFormspec();
|
||||
}
|
||||
|
||||
bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname)
|
||||
|
|
|
@ -26,7 +26,7 @@ struct GameFormSpec
|
|||
{
|
||||
void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input);
|
||||
|
||||
~GameFormSpec();
|
||||
~GameFormSpec() { reset(); }
|
||||
|
||||
void showFormSpec(const std::string &formspec, const std::string &formname);
|
||||
void showCSMFormSpec(const std::string &formspec, const std::string &formname);
|
||||
|
@ -43,6 +43,7 @@ struct GameFormSpec
|
|||
void disableDebugView();
|
||||
|
||||
bool handleCallbacks();
|
||||
void reset();
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Returns false if no formspec open
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
#include "mesh.h"
|
||||
#include "IMeshBuffer.h"
|
||||
#include "SSkinMeshBuffer.h"
|
||||
#include "debug.h"
|
||||
#include "log.h"
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <IAnimatedMesh.h>
|
||||
#include <SAnimatedMesh.h>
|
||||
#include <IAnimatedMeshSceneNode.h>
|
||||
#include "S3DVertex.h"
|
||||
#include "SMesh.h"
|
||||
#include <SMesh.h>
|
||||
#include "SMeshBuffer.h"
|
||||
|
||||
inline static void applyShadeFactor(video::SColor& color, float factor)
|
||||
|
@ -95,11 +96,23 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
|
|||
mesh->addMeshBuffer(buf);
|
||||
buf->drop();
|
||||
}
|
||||
scaleMesh(mesh, scale); // also recalculates bounding box
|
||||
return mesh;
|
||||
}
|
||||
|
||||
scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
|
||||
mesh->drop();
|
||||
scaleMesh(anim_mesh, scale); // also recalculates bounding box
|
||||
return anim_mesh;
|
||||
template<typename F>
|
||||
inline static void transformMeshBuffer(scene::IMeshBuffer *buf,
|
||||
const F &transform_vertex)
|
||||
{
|
||||
const u32 stride = getVertexPitchFromType(buf->getVertexType());
|
||||
u32 vertex_count = buf->getVertexCount();
|
||||
u8 *vertices = (u8 *)buf->getVertices();
|
||||
for (u32 i = 0; i < vertex_count; i++) {
|
||||
auto *vertex = (video::S3DVertex *)(vertices + i * stride);
|
||||
transform_vertex(vertex);
|
||||
}
|
||||
buf->setDirty(scene::EBT_VERTEX);
|
||||
buf->recalculateBoundingBox();
|
||||
}
|
||||
|
||||
void scaleMesh(scene::IMesh *mesh, v3f scale)
|
||||
|
@ -112,14 +125,9 @@ void scaleMesh(scene::IMesh *mesh, v3f scale)
|
|||
u32 mc = mesh->getMeshBufferCount();
|
||||
for (u32 j = 0; j < mc; j++) {
|
||||
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
|
||||
const u32 stride = getVertexPitchFromType(buf->getVertexType());
|
||||
u32 vertex_count = buf->getVertexCount();
|
||||
u8 *vertices = (u8 *)buf->getVertices();
|
||||
for (u32 i = 0; i < vertex_count; i++)
|
||||
((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
|
||||
|
||||
buf->setDirty(scene::EBT_VERTEX);
|
||||
buf->recalculateBoundingBox();
|
||||
transformMeshBuffer(buf, [scale](video::S3DVertex *vertex) {
|
||||
vertex->Pos *= scale;
|
||||
});
|
||||
|
||||
// calculate total bounding box
|
||||
if (j == 0)
|
||||
|
@ -140,14 +148,9 @@ void translateMesh(scene::IMesh *mesh, v3f vec)
|
|||
u32 mc = mesh->getMeshBufferCount();
|
||||
for (u32 j = 0; j < mc; j++) {
|
||||
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
|
||||
const u32 stride = getVertexPitchFromType(buf->getVertexType());
|
||||
u32 vertex_count = buf->getVertexCount();
|
||||
u8 *vertices = (u8 *)buf->getVertices();
|
||||
for (u32 i = 0; i < vertex_count; i++)
|
||||
((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
|
||||
|
||||
buf->setDirty(scene::EBT_VERTEX);
|
||||
buf->recalculateBoundingBox();
|
||||
transformMeshBuffer(buf, [vec](video::S3DVertex *vertex) {
|
||||
vertex->Pos += vec;
|
||||
});
|
||||
|
||||
// calculate total bounding box
|
||||
if (j == 0)
|
||||
|
@ -330,44 +333,40 @@ bool checkMeshNormals(scene::IMesh *mesh)
|
|||
return true;
|
||||
}
|
||||
|
||||
template<class VertexType, class SMeshBufferType>
|
||||
static scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
|
||||
{
|
||||
auto *v = static_cast<VertexType *>(mesh_buffer->getVertices());
|
||||
u16 *indices = mesh_buffer->getIndices();
|
||||
auto *cloned_buffer = new SMeshBufferType();
|
||||
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
|
||||
mesh_buffer->getIndexCount());
|
||||
// Rigidly animated meshes may have transformation matrices that need to be applied
|
||||
if (auto *sbuf = dynamic_cast<scene::SSkinMeshBuffer *>(mesh_buffer)) {
|
||||
transformMeshBuffer(cloned_buffer, [sbuf](video::S3DVertex *vertex) {
|
||||
sbuf->Transformation.transformVect(vertex->Pos);
|
||||
vertex->Normal = sbuf->Transformation.rotateAndScaleVect(vertex->Normal);
|
||||
vertex->Normal.normalize();
|
||||
});
|
||||
}
|
||||
return cloned_buffer;
|
||||
}
|
||||
|
||||
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
|
||||
{
|
||||
switch (mesh_buffer->getVertexType()) {
|
||||
case video::EVT_STANDARD: {
|
||||
video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
|
||||
u16 *indices = mesh_buffer->getIndices();
|
||||
scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
|
||||
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
|
||||
mesh_buffer->getIndexCount());
|
||||
return cloned_buffer;
|
||||
case video::EVT_STANDARD:
|
||||
return cloneMeshBuffer<video::S3DVertex, scene::SMeshBuffer>(mesh_buffer);
|
||||
case video::EVT_2TCOORDS:
|
||||
return cloneMeshBuffer<video::S3DVertex2TCoords, scene::SMeshBufferLightMap>(mesh_buffer);
|
||||
case video::EVT_TANGENTS:
|
||||
return cloneMeshBuffer<video::S3DVertexTangents, scene::SMeshBufferTangents>(mesh_buffer);
|
||||
}
|
||||
case video::EVT_2TCOORDS: {
|
||||
video::S3DVertex2TCoords *v =
|
||||
(video::S3DVertex2TCoords *) mesh_buffer->getVertices();
|
||||
u16 *indices = mesh_buffer->getIndices();
|
||||
scene::SMeshBufferLightMap *cloned_buffer =
|
||||
new scene::SMeshBufferLightMap();
|
||||
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
|
||||
mesh_buffer->getIndexCount());
|
||||
return cloned_buffer;
|
||||
}
|
||||
case video::EVT_TANGENTS: {
|
||||
video::S3DVertexTangents *v =
|
||||
(video::S3DVertexTangents *) mesh_buffer->getVertices();
|
||||
u16 *indices = mesh_buffer->getIndices();
|
||||
scene::SMeshBufferTangents *cloned_buffer =
|
||||
new scene::SMeshBufferTangents();
|
||||
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
|
||||
mesh_buffer->getIndexCount());
|
||||
return cloned_buffer;
|
||||
}
|
||||
}
|
||||
// This should not happen.
|
||||
sanity_check(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
|
||||
scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh)
|
||||
{
|
||||
scene::SMesh* dst_mesh = new scene::SMesh();
|
||||
for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
|
||||
|
|
|
@ -93,10 +93,8 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees);
|
|||
*/
|
||||
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer);
|
||||
|
||||
/*
|
||||
Clone the mesh.
|
||||
*/
|
||||
scene::SMesh* cloneMesh(scene::IMesh *src_mesh);
|
||||
/// Clone a mesh. For an animated mesh, this will clone the static pose.
|
||||
scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh);
|
||||
|
||||
/*
|
||||
Convert nodeboxes to mesh. Each tile goes into a different buffer.
|
||||
|
|
|
@ -14,25 +14,19 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertice
|
|||
const TileLayer *layer = &tile.layers[layernum];
|
||||
if (layer->empty())
|
||||
continue;
|
||||
append(*layer, vertices, numVertices, indices, numIndices, layernum,
|
||||
tile.world_aligned);
|
||||
append(*layer, vertices, numVertices, indices, numIndices, layernum);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices,
|
||||
u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum,
|
||||
bool use_scale)
|
||||
u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum)
|
||||
{
|
||||
PreMeshBuffer &p = findBuffer(layer, layernum, numVertices);
|
||||
|
||||
f32 scale = 1.0f;
|
||||
if (use_scale)
|
||||
scale = 1.0f / layer.scale;
|
||||
|
||||
u32 vertex_count = p.vertices.size();
|
||||
for (u32 i = 0; i < numVertices; i++) {
|
||||
p.vertices.emplace_back(vertices[i].Pos + offset, vertices[i].Normal,
|
||||
vertices[i].Color, scale * vertices[i].TCoords);
|
||||
vertices[i].Color, vertices[i].TCoords);
|
||||
m_bounding_radius_sq = std::max(m_bounding_radius_sq,
|
||||
(vertices[i].Pos - m_center_pos).getLengthSQ());
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ private:
|
|||
void append(const TileLayer &material,
|
||||
const video::S3DVertex *vertices, u32 numVertices,
|
||||
const u16 *indices, u32 numIndices,
|
||||
u8 layernum, bool use_scale = false);
|
||||
u8 layernum);
|
||||
|
||||
PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices);
|
||||
};
|
||||
|
|
|
@ -410,7 +410,7 @@ video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name
|
|||
shadow_map_name.c_str(), texture_format);
|
||||
}
|
||||
|
||||
return m_driver->getTexture(shadow_map_name.c_str());
|
||||
return m_driver->findTexture(shadow_map_name.c_str());
|
||||
}
|
||||
|
||||
void ShadowRenderer::renderShadowMap(video::ITexture *target,
|
||||
|
|
|
@ -191,8 +191,6 @@ struct TileSpec
|
|||
bool world_aligned = false;
|
||||
//! Tile rotation.
|
||||
TileRotation rotation = TileRotation::None;
|
||||
//! This much light does the tile emit.
|
||||
u8 emissive_light = 0;
|
||||
//! The first is base texture, the second is overlay.
|
||||
TileLayer layers[MAX_TILE_LAYERS];
|
||||
};
|
||||
|
|
|
@ -255,7 +255,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
|
|||
dim = core::dimension2d<u32>(dim.Width, frame_height);
|
||||
}
|
||||
scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
|
||||
scene::SMesh *mesh = cloneMesh(original);
|
||||
scene::SMesh *mesh = cloneStaticMesh(original);
|
||||
original->drop();
|
||||
//set texture
|
||||
mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
|
||||
|
@ -639,7 +639,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
|
|||
// get mesh
|
||||
core::dimension2d<u32> dim = texture->getSize();
|
||||
scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
|
||||
scene::SMesh *mesh = cloneMesh(original);
|
||||
scene::SMesh *mesh = cloneStaticMesh(original);
|
||||
original->drop();
|
||||
|
||||
//set texture
|
||||
|
|
|
@ -27,12 +27,13 @@ public:
|
|||
void fill(v3s16 bpmin, v3s16 bpmax, MapNode n)
|
||||
{
|
||||
for (s16 z = bpmin.Z; z <= bpmax.Z; z++)
|
||||
for (s16 y = bpmin.Y; y <= bpmax.Y; y++)
|
||||
for (s16 x = bpmin.X; x <= bpmax.X; x++) {
|
||||
for (s16 x = bpmin.X; x <= bpmax.X; x++)
|
||||
for (s16 y = bpmin.Y; y <= bpmax.Y; y++) {
|
||||
MapBlock *block = getBlockNoCreateNoEx({x, y, z});
|
||||
if (block) {
|
||||
auto *data = block->getData();
|
||||
for (size_t i = 0; i < MapBlock::nodecount; i++)
|
||||
block->getData()[i] = n;
|
||||
data[i] = n;
|
||||
block->expireIsAirCache();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -718,19 +718,24 @@ void GUIButton::setFromStyle(const StyleSpec& style)
|
|||
setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
|
||||
setOverrideFont(style.getFont());
|
||||
|
||||
BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
|
||||
|
||||
if (style.isNotDefault(StyleSpec::BGIMG)) {
|
||||
video::ITexture *texture = style.getTexture(StyleSpec::BGIMG,
|
||||
getTextureSource());
|
||||
setImage(guiScalingImageButton(
|
||||
Environment->getVideoDriver(), texture,
|
||||
AbsoluteRect.getWidth(), AbsoluteRect.getHeight()));
|
||||
if (BgMiddle.getArea() == 0) {
|
||||
setImage(guiScalingImageButton(
|
||||
Environment->getVideoDriver(), texture,
|
||||
AbsoluteRect.getWidth(), AbsoluteRect.getHeight()));
|
||||
} else {
|
||||
// Scaling happens in `draw2DImage9Slice`
|
||||
setImage(texture);
|
||||
}
|
||||
setScaleImage(true);
|
||||
} else {
|
||||
setImage(nullptr);
|
||||
}
|
||||
|
||||
BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
|
||||
|
||||
// Child padding and offset
|
||||
Padding = style.getRect(StyleSpec::PADDING, core::rect<s32>());
|
||||
Padding = core::rect<s32>(
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#include "client/sound/sound_openal.h"
|
||||
#endif
|
||||
|
||||
#include <csignal>
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
void TextDestGuiEngine::gotText(const StringMap &fields)
|
||||
|
@ -40,12 +42,6 @@ void TextDestGuiEngine::gotText(const StringMap &fields)
|
|||
m_engine->getScriptIface()->handleMainMenuButtons(fields);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void TextDestGuiEngine::gotText(const std::wstring &text)
|
||||
{
|
||||
m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
MenuTextureSource::~MenuTextureSource()
|
||||
{
|
||||
|
@ -74,6 +70,7 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
|
|||
if (retval)
|
||||
return retval;
|
||||
|
||||
verbosestream << "MenuTextureSource: loading " << name << std::endl;
|
||||
video::IImage *image = m_driver->createImageFromFile(name.c_str());
|
||||
if (!image)
|
||||
return NULL;
|
||||
|
@ -109,7 +106,7 @@ GUIEngine::GUIEngine(JoystickController *joystick,
|
|||
RenderingEngine *rendering_engine,
|
||||
IMenuManager *menumgr,
|
||||
MainMenuData *data,
|
||||
bool &kill) :
|
||||
volatile std::sig_atomic_t &kill) :
|
||||
m_rendering_engine(rendering_engine),
|
||||
m_parent(parent),
|
||||
m_menumanager(menumgr),
|
||||
|
@ -404,12 +401,6 @@ GUIEngine::~GUIEngine()
|
|||
m_sound_manager.reset();
|
||||
|
||||
m_irr_toplefttext->remove();
|
||||
|
||||
// delete textures
|
||||
for (image_definition &texture : m_textures) {
|
||||
if (texture.texture)
|
||||
m_rendering_engine->get_video_driver()->removeTexture(texture.texture);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -597,26 +588,16 @@ void GUIEngine::drawFooter(video::IVideoDriver *driver)
|
|||
bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath,
|
||||
bool tile_image, unsigned int minsize)
|
||||
{
|
||||
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
|
||||
m_textures[layer].texture = nullptr;
|
||||
|
||||
if (m_textures[layer].texture) {
|
||||
driver->removeTexture(m_textures[layer].texture);
|
||||
m_textures[layer].texture = NULL;
|
||||
}
|
||||
|
||||
if (texturepath.empty() || !fs::PathExists(texturepath)) {
|
||||
if (texturepath.empty() || !fs::PathExists(texturepath))
|
||||
return false;
|
||||
}
|
||||
|
||||
m_textures[layer].texture = driver->getTexture(texturepath.c_str());
|
||||
m_textures[layer].texture = m_texture_source->getTexture(texturepath);
|
||||
m_textures[layer].tile = tile_image;
|
||||
m_textures[layer].minsize = minsize;
|
||||
|
||||
if (!m_textures[layer].texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return m_textures[layer].texture != nullptr;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "util/enriched_string.h"
|
||||
#include "translation.h"
|
||||
|
||||
#include <csignal>
|
||||
|
||||
/******************************************************************************/
|
||||
/* Structs and macros */
|
||||
/******************************************************************************/
|
||||
|
@ -61,12 +63,6 @@ public:
|
|||
*/
|
||||
void gotText(const StringMap &fields);
|
||||
|
||||
/**
|
||||
* receive text/events transmitted by guiFormSpecMenu
|
||||
* @param text textual representation of event
|
||||
*/
|
||||
void gotText(const std::wstring &text);
|
||||
|
||||
private:
|
||||
/** target to transmit data to */
|
||||
GUIEngine *m_engine = nullptr;
|
||||
|
@ -130,7 +126,7 @@ public:
|
|||
RenderingEngine *rendering_engine,
|
||||
IMenuManager *menumgr,
|
||||
MainMenuData *data,
|
||||
bool &kill);
|
||||
volatile std::sig_atomic_t &kill);
|
||||
|
||||
/** default destructor */
|
||||
virtual ~GUIEngine();
|
||||
|
@ -199,7 +195,7 @@ private:
|
|||
irr_ptr<GUIFormSpecMenu> m_menu;
|
||||
|
||||
/** reference to kill variable managed by SIGINT handler */
|
||||
bool &m_kill;
|
||||
volatile std::sig_atomic_t &m_kill;
|
||||
|
||||
/** variable used to abort menu and return back to main game handling */
|
||||
bool m_startgame = false;
|
||||
|
|
|
@ -77,6 +77,9 @@
|
|||
return; \
|
||||
}
|
||||
|
||||
// Element ID of the "Proceed" button shown for sizeless formspecs
|
||||
constexpr s32 ID_PROCEED_BTN = 257;
|
||||
|
||||
/*
|
||||
GUIFormSpecMenu
|
||||
*/
|
||||
|
@ -1235,10 +1238,14 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
|
|||
item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
|
||||
}
|
||||
|
||||
//now really show table
|
||||
GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
|
||||
rect, m_tsrc);
|
||||
|
||||
// Apply styling before calculating the cell sizes
|
||||
auto style = getDefaultStyleForElement("table", name);
|
||||
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
|
||||
e->setOverrideFont(style.getFont());
|
||||
|
||||
if (spec.fname == m_focused_element) {
|
||||
Environment->setFocus(e);
|
||||
}
|
||||
|
@ -1252,10 +1259,6 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
|
|||
if (!str_initial_selection.empty() && str_initial_selection != "0")
|
||||
e->setSelected(stoi(str_initial_selection));
|
||||
|
||||
auto style = getDefaultStyleForElement("table", name);
|
||||
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
|
||||
e->setOverrideFont(style.getFont());
|
||||
|
||||
m_tables.emplace_back(spec, e);
|
||||
m_fields.push_back(spec);
|
||||
}
|
||||
|
@ -2998,7 +3001,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
|
|||
gui::IGUIElement *focused_element = Environment->getFocus();
|
||||
if (focused_element && focused_element->getParent() == this) {
|
||||
s32 focused_id = focused_element->getID();
|
||||
if (focused_id > 257) {
|
||||
if (focused_id > ID_PROCEED_BTN) {
|
||||
for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
|
||||
if (field.fid == focused_id) {
|
||||
m_focused_element = field.fname;
|
||||
|
@ -3308,7 +3311,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
|
|||
size.X / 2 - 70, pos.Y,
|
||||
size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
|
||||
);
|
||||
GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257,
|
||||
GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, ID_PROCEED_BTN,
|
||||
wstrgettext("Proceed").c_str());
|
||||
}
|
||||
}
|
||||
|
@ -4031,12 +4034,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
|
|||
if (m_joystick->wasKeyDown(KeyType::ESC)) {
|
||||
tryClose();
|
||||
} else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
|
||||
if (m_allowclose) {
|
||||
acceptInput(quit_mode_accept);
|
||||
quitMenu();
|
||||
} else {
|
||||
acceptInput(quit_mode_try);
|
||||
}
|
||||
trySubmitClose();
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
|
@ -4053,7 +4051,16 @@ void GUIFormSpecMenu::tryClose()
|
|||
quitMenu();
|
||||
} else {
|
||||
acceptInput(quit_mode_try);
|
||||
m_text_dst->gotText(L"MenuQuit");
|
||||
}
|
||||
}
|
||||
|
||||
void GUIFormSpecMenu::trySubmitClose()
|
||||
{
|
||||
if (m_allowclose) {
|
||||
acceptInput(quit_mode_accept);
|
||||
quitMenu();
|
||||
} else {
|
||||
acceptInput(quit_mode_try);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4100,12 +4107,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||
break;
|
||||
}
|
||||
if (current_keys_pending.key_enter) {
|
||||
if (m_allowclose) {
|
||||
acceptInput(quit_mode_accept);
|
||||
quitMenu();
|
||||
} else {
|
||||
acceptInput(quit_mode_try);
|
||||
}
|
||||
trySubmitClose();
|
||||
} else {
|
||||
acceptInput();
|
||||
}
|
||||
|
@ -4819,12 +4821,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||
}
|
||||
|
||||
if (event.EventType == EET_GUI_EVENT) {
|
||||
if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
|
||||
&& isVisible()) {
|
||||
// find the element that was clicked
|
||||
const s32 caller_id = event.GUIEvent.Caller->getID();
|
||||
bool close_on_enter;
|
||||
|
||||
switch (event.GUIEvent.EventType) {
|
||||
case gui::EGET_TAB_CHANGED:
|
||||
if (!isVisible())
|
||||
break;
|
||||
|
||||
// find the element that was clicked
|
||||
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
|
||||
if ((s.ftype == f_TabHeader) &&
|
||||
(s.fid == event.GUIEvent.Caller->getID())) {
|
||||
if (s.ftype == f_TabHeader &&
|
||||
s.fid == caller_id) {
|
||||
if (!s.sound.empty() && m_sound_manager)
|
||||
m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f));
|
||||
s.send = true;
|
||||
|
@ -4833,26 +4841,26 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
|
||||
&& isVisible()) {
|
||||
break;
|
||||
|
||||
case gui::EGET_ELEMENT_FOCUS_LOST:
|
||||
if (!isVisible())
|
||||
break;
|
||||
|
||||
if (!canTakeFocus(event.GUIEvent.Element)) {
|
||||
infostream<<"GUIFormSpecMenu: Not allowing focus change."
|
||||
<<std::endl;
|
||||
// Returning true disables focus change
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
|
||||
(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
|
||||
(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
|
||||
(event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
|
||||
s32 caller_id = event.GUIEvent.Caller->getID();
|
||||
break;
|
||||
|
||||
if (caller_id == 257) {
|
||||
acceptInput(quit_mode_accept);
|
||||
m_text_dst->gotText(L"ExitButton");
|
||||
quitMenu();
|
||||
case gui::EGET_BUTTON_CLICKED:
|
||||
case gui::EGET_CHECKBOX_CHANGED:
|
||||
case gui::EGET_COMBO_BOX_CHANGED:
|
||||
case gui::EGET_SCROLL_BAR_CHANGED:
|
||||
if (caller_id == ID_PROCEED_BTN) {
|
||||
trySubmitClose();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4882,7 +4890,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||
|
||||
if (s.is_exit) {
|
||||
acceptInput(quit_mode_accept);
|
||||
m_text_dst->gotText(L"ExitButton");
|
||||
quitMenu();
|
||||
return true;
|
||||
}
|
||||
|
@ -4923,60 +4930,57 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||
s.send = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
|
||||
// move scroll_containers
|
||||
for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
|
||||
c.second->onScrollEvent(event.GUIEvent.Caller);
|
||||
}
|
||||
if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
|
||||
// move scroll_containers
|
||||
for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
|
||||
c.second->onScrollEvent(event.GUIEvent.Caller);
|
||||
}
|
||||
break;
|
||||
|
||||
if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
|
||||
if (event.GUIEvent.Caller->getID() > 257) {
|
||||
bool close_on_enter = true;
|
||||
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;
|
||||
case gui::EGET_EDITBOX_ENTER:
|
||||
if (caller_id <= ID_PROCEED_BTN)
|
||||
break;
|
||||
|
||||
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 (m_allowclose) {
|
||||
acceptInput(quit_mode_accept);
|
||||
quitMenu();
|
||||
} else {
|
||||
acceptInput(quit_mode_try);
|
||||
}
|
||||
} else {
|
||||
if (close_on_enter)
|
||||
trySubmitClose();
|
||||
else
|
||||
acceptInput();
|
||||
return true;
|
||||
|
||||
case gui::EGET_TABLE_CHANGED:
|
||||
if (caller_id <= ID_PROCEED_BTN)
|
||||
break;
|
||||
|
||||
// find the element that was clicked
|
||||
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
|
||||
// if it's a table, set the send field
|
||||
// so lua knows which table was changed
|
||||
if (s.ftype == f_Table && s.fid == caller_id) {
|
||||
s.send = true;
|
||||
acceptInput();
|
||||
s.send = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
|
||||
int current_id = event.GUIEvent.Caller->getID();
|
||||
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;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,8 +68,6 @@ struct TextDest
|
|||
{
|
||||
virtual ~TextDest() = default;
|
||||
|
||||
// This is deprecated I guess? -celeron55
|
||||
virtual void gotText(const std::wstring &text) {}
|
||||
virtual void gotText(const StringMap &fields) = 0;
|
||||
|
||||
std::string m_formname;
|
||||
|
@ -493,6 +491,7 @@ private:
|
|||
bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect);
|
||||
|
||||
void tryClose();
|
||||
void trySubmitClose();
|
||||
|
||||
void showTooltip(const std::wstring &text, const irr::video::SColor &color,
|
||||
const irr::video::SColor &bgcolor);
|
||||
|
|
|
@ -85,7 +85,7 @@ public:
|
|||
void setTextList(const std::vector<std::string> &content,
|
||||
bool transparent);
|
||||
|
||||
/* Set generic table options, columns and content */
|
||||
/** Set generic table options, columns and content, calculate cell sizes */
|
||||
// Adds empty strings to end of content if there is an incomplete row
|
||||
void setTable(const TableOptions &options,
|
||||
const TableColumns &columns,
|
||||
|
|
|
@ -59,6 +59,8 @@ public:
|
|||
if(!m_stack.empty()) {
|
||||
m_stack.back()->setVisible(true);
|
||||
guienv->setFocus(m_stack.back());
|
||||
} else {
|
||||
guienv->removeFocus(menu);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1138,7 +1138,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
|||
return false;
|
||||
}
|
||||
ChatInterface iface;
|
||||
bool &kill = *porting::signal_handler_killstatus();
|
||||
volatile auto &kill = *porting::signal_handler_killstatus();
|
||||
|
||||
try {
|
||||
// Create server
|
||||
|
@ -1181,7 +1181,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
|||
server.start();
|
||||
|
||||
// Run server
|
||||
bool &kill = *porting::signal_handler_killstatus();
|
||||
volatile auto &kill = *porting::signal_handler_killstatus();
|
||||
dedicated_server_loop(server, kill);
|
||||
|
||||
} catch (const ModError &e) {
|
||||
|
@ -1226,7 +1226,7 @@ static bool migrate_map_database(const GameParams &game_params, const Settings &
|
|||
|
||||
u32 count = 0;
|
||||
u64 last_update_time = 0;
|
||||
bool &kill = *porting::signal_handler_killstatus();
|
||||
volatile auto &kill = *porting::signal_handler_killstatus();
|
||||
|
||||
std::vector<v3s16> blocks;
|
||||
old_db->listAllLoadableBlocks(blocks);
|
||||
|
@ -1280,7 +1280,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting
|
|||
|
||||
u32 count = 0;
|
||||
u64 last_update_time = 0;
|
||||
bool &kill = *porting::signal_handler_killstatus();
|
||||
volatile auto &kill = *porting::signal_handler_killstatus();
|
||||
const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE;
|
||||
const s16 map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9);
|
||||
|
||||
|
|
108
src/map.cpp
108
src/map.cpp
|
@ -772,52 +772,79 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent)
|
|||
infostream<<std::endl;
|
||||
}
|
||||
|
||||
const bool all_new = m_area.hasEmptyExtent() || block_area_nodes.contains(m_area);
|
||||
std::map<v3s16, bool> had_blocks;
|
||||
// we can skip this calculation if the areas are disjoint
|
||||
if (!m_area.intersect(block_area_nodes).hasEmptyExtent())
|
||||
had_blocks = getCoveredBlocks();
|
||||
|
||||
const bool all_new = m_area.hasEmptyExtent();
|
||||
addArea(block_area_nodes);
|
||||
|
||||
for(s32 z=p_min.Z; z<=p_max.Z; z++)
|
||||
for(s32 y=p_min.Y; y<=p_max.Y; y++)
|
||||
for(s32 x=p_min.X; x<=p_max.X; x++)
|
||||
{
|
||||
u8 flags = 0;
|
||||
MapBlock *block;
|
||||
v3s16 p(x,y,z);
|
||||
if (m_loaded_blocks.count(p) > 0)
|
||||
// if this block was already in the vmanip and it has data, skip
|
||||
if (auto it = had_blocks.find(p); it != had_blocks.end() && it->second)
|
||||
continue;
|
||||
|
||||
bool block_data_inexistent = false;
|
||||
{
|
||||
TimeTaker timer2("emerge load", &emerge_load_time);
|
||||
|
||||
block = m_map->getBlockNoCreateNoEx(p);
|
||||
if (!block)
|
||||
block_data_inexistent = true;
|
||||
else
|
||||
block->copyTo(*this);
|
||||
}
|
||||
|
||||
if(block_data_inexistent)
|
||||
{
|
||||
|
||||
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
|
||||
if (block) {
|
||||
block->copyTo(*this);
|
||||
} else {
|
||||
if (load_if_inexistent && !blockpos_over_max_limit(p)) {
|
||||
block = m_map->emergeBlock(p, true);
|
||||
assert(block);
|
||||
block->copyTo(*this);
|
||||
} else {
|
||||
flags |= VMANIP_BLOCK_DATA_INEXIST;
|
||||
|
||||
// Mark area inexistent
|
||||
VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
|
||||
setFlags(a, VOXELFLAG_NO_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
m_loaded_blocks[p] = flags;
|
||||
}
|
||||
|
||||
if (all_new)
|
||||
m_is_dirty = false;
|
||||
}
|
||||
|
||||
std::map<v3s16, bool> MMVManip::getCoveredBlocks() const
|
||||
{
|
||||
std::map<v3s16, bool> ret;
|
||||
if (m_area.hasEmptyExtent())
|
||||
return ret;
|
||||
|
||||
// Figure out if *any* node in this block has data according to m_flags
|
||||
const auto &check_block = [this] (v3s16 bp) -> bool {
|
||||
v3s16 pmin = bp * MAP_BLOCKSIZE;
|
||||
v3s16 pmax = pmin + v3s16(MAP_BLOCKSIZE-1);
|
||||
for(s16 z=pmin.Z; z<=pmax.Z; z++)
|
||||
for(s16 y=pmin.Y; y<=pmax.Y; y++)
|
||||
for(s16 x=pmin.X; x<=pmax.X; x++) {
|
||||
if (!(m_flags[m_area.index(x,y,z)] & VOXELFLAG_NO_DATA))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
v3s16 bpmin = getNodeBlockPos(m_area.MinEdge);
|
||||
v3s16 bpmax = getNodeBlockPos(m_area.MaxEdge);
|
||||
|
||||
if (bpmin * MAP_BLOCKSIZE != m_area.MinEdge)
|
||||
throw BaseException("MMVManip not block-aligned");
|
||||
if ((bpmax+1) * MAP_BLOCKSIZE - v3s16(1) != m_area.MaxEdge)
|
||||
throw BaseException("MMVManip not block-aligned");
|
||||
|
||||
for(s16 z=bpmin.Z; z<=bpmax.Z; z++)
|
||||
for(s16 y=bpmin.Y; y<=bpmax.Y; y++)
|
||||
for(s16 x=bpmin.X; x<=bpmax.X; x++) {
|
||||
v3s16 bp(x,y,z);
|
||||
ret[bp] = check_block(bp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
|
||||
bool overwrite_generated) const
|
||||
{
|
||||
|
@ -825,16 +852,27 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
|
|||
return;
|
||||
assert(m_map);
|
||||
|
||||
/*
|
||||
Copy data of all blocks
|
||||
*/
|
||||
assert(!m_loaded_blocks.empty());
|
||||
for (auto &loaded_block : m_loaded_blocks) {
|
||||
v3s16 p = loaded_block.first;
|
||||
size_t nload = 0;
|
||||
|
||||
// Copy all the blocks with data back to the map
|
||||
const auto loaded_blocks = getCoveredBlocks();
|
||||
for (auto &it : loaded_blocks) {
|
||||
if (!it.second)
|
||||
continue;
|
||||
v3s16 p = it.first;
|
||||
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
|
||||
bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST);
|
||||
if (!existed || (block == NULL) ||
|
||||
(!overwrite_generated && block->isGenerated()))
|
||||
if (!block) {
|
||||
if (!blockpos_over_max_limit(p)) {
|
||||
block = m_map->emergeBlock(p, true);
|
||||
nload++;
|
||||
}
|
||||
}
|
||||
if (!block) {
|
||||
warningstream << "blitBackAll: Couldn't load block " << p
|
||||
<< " to write data to map" << std::endl;
|
||||
continue;
|
||||
}
|
||||
if (!overwrite_generated && block->isGenerated())
|
||||
continue;
|
||||
|
||||
block->copyFrom(*this);
|
||||
|
@ -844,6 +882,10 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
|
|||
if(modified_blocks)
|
||||
(*modified_blocks)[p] = block;
|
||||
}
|
||||
|
||||
if (nload > 0) {
|
||||
verbosestream << "blitBackAll: " << nload << " blocks had to be loaded for writing" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
MMVManip *MMVManip::clone() const
|
||||
|
@ -860,11 +902,7 @@ MMVManip *MMVManip::clone() const
|
|||
ret->m_flags = new u8[size];
|
||||
memcpy(ret->m_flags, m_flags, size * sizeof(u8));
|
||||
}
|
||||
|
||||
ret->m_is_dirty = m_is_dirty;
|
||||
// Even if the copy is disconnected from a map object keep the information
|
||||
// needed to write it back to one
|
||||
ret->m_loaded_blocks = m_loaded_blocks;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
36
src/map.h
36
src/map.h
|
@ -307,16 +307,29 @@ public:
|
|||
MMVManip(Map *map);
|
||||
virtual ~MMVManip() = default;
|
||||
|
||||
virtual void clear()
|
||||
{
|
||||
VoxelManipulator::clear();
|
||||
m_loaded_blocks.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
Loads specified area from map and *adds* it to the area already
|
||||
contained in the VManip.
|
||||
*/
|
||||
void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
|
||||
bool load_if_inexistent = true);
|
||||
|
||||
// This is much faster with big chunks of generated data
|
||||
/**
|
||||
Uses the flags array to determine which blocks the VManip covers,
|
||||
and for which of them we have any data.
|
||||
@warning requires VManip area to be block-aligned
|
||||
@return map of blockpos -> any data?
|
||||
*/
|
||||
std::map<v3s16, bool> getCoveredBlocks() const;
|
||||
|
||||
/**
|
||||
Writes data in VManip back to the map. Blocks without any data in the VManip
|
||||
are skipped.
|
||||
@note VOXELFLAG_NO_DATA is checked per-block, not per-node. So you need
|
||||
to ensure that the relevant parts of m_data are initialized.
|
||||
@param modified_blocks output array of touched blocks (optional)
|
||||
@param overwrite_generated if false, blocks marked as generate in the map are not changed
|
||||
*/
|
||||
void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
|
||||
bool overwrite_generated = true) const;
|
||||
|
||||
|
@ -339,13 +352,4 @@ protected:
|
|||
|
||||
// may be null
|
||||
Map *m_map = nullptr;
|
||||
/*
|
||||
key = blockpos
|
||||
value = flags describing the block
|
||||
*/
|
||||
std::map<v3s16, u8> m_loaded_blocks;
|
||||
|
||||
enum : u8 {
|
||||
VMANIP_BLOCK_DATA_INEXIST = 1 << 0,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1314,11 +1314,6 @@ Connection::~Connection()
|
|||
m_sendThread->stop();
|
||||
m_receiveThread->stop();
|
||||
|
||||
//TODO for some unkonwn reason send/receive threads do not exit as they're
|
||||
// supposed to be but wait on peer timeout. To speed up shutdown we reduce
|
||||
// timeout to half a second.
|
||||
m_sendThread->setPeerTimeout(0.5);
|
||||
|
||||
// wait for threads to finish
|
||||
m_sendThread->wait();
|
||||
m_receiveThread->wait();
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "util/numeric.h"
|
||||
#include "porting.h"
|
||||
#include "network/networkprotocol.h"
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
@ -301,7 +302,7 @@ private:
|
|||
// Backwards compatibility
|
||||
PeerHandler *m_bc_peerhandler;
|
||||
|
||||
bool m_shutting_down = false;
|
||||
std::atomic<bool> m_shutting_down = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -40,10 +40,6 @@ void Server::handleCommand_Deprecated(NetworkPacket* pkt)
|
|||
|
||||
void Server::handleCommand_Init(NetworkPacket* pkt)
|
||||
{
|
||||
|
||||
if(pkt->getSize() < 1)
|
||||
return;
|
||||
|
||||
session_t peer_id = pkt->getPeerId();
|
||||
RemoteClient *client = getClient(peer_id, CS_Created);
|
||||
|
||||
|
@ -75,15 +71,6 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
|
|||
verbosestream << "Server: Got TOSERVER_INIT from " << addr_s <<
|
||||
" (peer_id=" << peer_id << ")" << std::endl;
|
||||
|
||||
// Do not allow multiple players in simple singleplayer mode.
|
||||
// This isn't a perfect way to do it, but will suffice for now
|
||||
if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) {
|
||||
infostream << "Server: Not allowing another client (" << addr_s <<
|
||||
") to connect in simple singleplayer mode" << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (denyIfBanned(peer_id))
|
||||
return;
|
||||
|
||||
|
@ -161,18 +148,14 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
|
|||
return;
|
||||
}
|
||||
|
||||
RemotePlayer *player = m_env->getPlayer(playername, true);
|
||||
|
||||
// If player is already connected, cancel
|
||||
if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
|
||||
actionstream << "Server: Player with name \"" << playername <<
|
||||
"\" tried to connect, but player with same name is already connected" << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
|
||||
// Do not allow multiple players in simple singleplayer mode
|
||||
if (isSingleplayer() && !m_clients.getClientIDs(CS_HelloSent).empty()) {
|
||||
infostream << "Server: Not allowing another client (" << addr_s <<
|
||||
") to connect in simple singleplayer mode" << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
|
||||
return;
|
||||
}
|
||||
|
||||
m_clients.setPlayerName(peer_id, playername);
|
||||
|
||||
// Or the "singleplayer" name to be used on regular servers
|
||||
if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) {
|
||||
actionstream << "Server: Player with the name \"singleplayer\" tried "
|
||||
"to connect from " << addr_s << std::endl;
|
||||
|
@ -180,12 +163,25 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
|
|||
return;
|
||||
}
|
||||
|
||||
{
|
||||
RemotePlayer *player = m_env->getPlayer(playername, true);
|
||||
// If player is already connected, cancel
|
||||
if (player && player->getPeerId() != PEER_ID_INEXISTENT) {
|
||||
actionstream << "Server: Player with name \"" << playername <<
|
||||
"\" tried to connect, but player with same name is already connected" << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
client->setName(playerName);
|
||||
|
||||
{
|
||||
std::string reason;
|
||||
if (m_script->on_prejoinplayer(playername, addr_s, &reason)) {
|
||||
actionstream << "Server: Player with the name \"" << playerName <<
|
||||
"\" tried to connect from " << addr_s <<
|
||||
" but it was disallowed for the following reason: " << reason <<
|
||||
" but was disallowed for the following reason: " << reason <<
|
||||
std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason);
|
||||
return;
|
||||
|
@ -195,14 +191,11 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
|
|||
infostream << "Server: New connection: \"" << playerName << "\" from " <<
|
||||
addr_s << " (peer_id=" << peer_id << ")" << std::endl;
|
||||
|
||||
// Enforce user limit.
|
||||
// Don't enforce for users that have some admin right or mod permits it.
|
||||
if (m_clients.isUserLimitReached() &&
|
||||
playername != g_settings->get("name") &&
|
||||
!m_script->can_bypass_userlimit(playername, addr_s)) {
|
||||
// Early check for user limit, so the client doesn't need to run
|
||||
// through the join process only to be denied.
|
||||
if (checkUserLimit(playerName, addr_s)) {
|
||||
actionstream << "Server: " << playername << " tried to join from " <<
|
||||
addr_s << ", but there are already max_users=" <<
|
||||
g_settings->getU16("max_users") << " players." << std::endl;
|
||||
addr_s << ", but the user limit was reached." << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
|
||||
return;
|
||||
}
|
||||
|
@ -302,6 +295,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
|
|||
sendMediaAnnouncement(peer_id, lang);
|
||||
|
||||
RemoteClient *client = getClient(peer_id, CS_InitDone);
|
||||
assert(client);
|
||||
|
||||
// Keep client language for server translations
|
||||
client->setLangCode(lang);
|
||||
|
@ -354,6 +348,8 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
|
|||
void Server::handleCommand_ClientReady(NetworkPacket* pkt)
|
||||
{
|
||||
session_t peer_id = pkt->getPeerId();
|
||||
RemoteClient *client = getClient(peer_id, CS_Created);
|
||||
assert(client);
|
||||
|
||||
// decode all information first
|
||||
u8 major_ver, minor_ver, patch_ver, reserved;
|
||||
|
@ -364,8 +360,17 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
|
|||
if (pkt->getRemainingBytes() >= 2)
|
||||
*pkt >> formspec_ver;
|
||||
|
||||
m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver,
|
||||
full_ver);
|
||||
client->setVersionInfo(major_ver, minor_ver, patch_ver, full_ver);
|
||||
|
||||
// Since only active clients count for the user limit, two could race the
|
||||
// join process so we have to do a final check for the user limit here.
|
||||
std::string addr_s = client->getAddress().serializeString();
|
||||
if (checkUserLimit(client->getName(), addr_s)) {
|
||||
actionstream << "Server: " << client->getName() << " tried to join from " <<
|
||||
addr_s << ", but the user limit was reached (late)." << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Emerge player
|
||||
PlayerSAO* playersao = StageTwoClientInit(peer_id);
|
||||
|
@ -1426,7 +1431,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
|
|||
|
||||
std::string salt, verification_key;
|
||||
|
||||
std::string addr_s = getPeerAddress(peer_id).serializeString();
|
||||
std::string addr_s = client->getAddress().serializeString();
|
||||
u8 is_empty;
|
||||
|
||||
*pkt >> salt >> verification_key >> is_empty;
|
||||
|
@ -1512,9 +1517,11 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
|
|||
RemoteClient *client = getClient(peer_id, CS_Invalid);
|
||||
ClientState cstate = client->getState();
|
||||
|
||||
std::string addr_s = client->getAddress().serializeString();
|
||||
|
||||
if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
|
||||
actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
|
||||
" from " << getPeerAddress(peer_id).serializeString() <<
|
||||
" from " << addr_s <<
|
||||
". Ignoring." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
@ -1524,7 +1531,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
|
|||
if (client->chosen_mech != AUTH_MECHANISM_NONE) {
|
||||
actionstream << "Server: got SRP _A packet, while auth is already "
|
||||
"going on with mech " << client->chosen_mech << " from " <<
|
||||
getPeerAddress(peer_id).serializeString() <<
|
||||
addr_s <<
|
||||
" (wantSudo=" << wantSudo << "). Ignoring." << std::endl;
|
||||
if (wantSudo) {
|
||||
DenySudoAccess(peer_id);
|
||||
|
@ -1541,7 +1548,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
|
|||
|
||||
infostream << "Server: TOSERVER_SRP_BYTES_A received with "
|
||||
<< "based_on=" << int(based_on) << " and len_A="
|
||||
<< bytes_A.length() << "." << std::endl;
|
||||
<< bytes_A.length() << std::endl;
|
||||
|
||||
AuthMechanism chosen = (based_on == 0) ?
|
||||
AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP;
|
||||
|
@ -1550,17 +1557,17 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
|
|||
// Right now, the auth mechs don't change between login and sudo mode.
|
||||
if (!client->isMechAllowed(chosen)) {
|
||||
actionstream << "Server: Player \"" << client->getName() <<
|
||||
"\" at " << getPeerAddress(peer_id).serializeString() <<
|
||||
"\" from " << addr_s <<
|
||||
" tried to change password using unallowed mech " << chosen <<
|
||||
"." << std::endl;
|
||||
std::endl;
|
||||
DenySudoAccess(peer_id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!client->isMechAllowed(chosen)) {
|
||||
actionstream << "Server: Client tried to authenticate from " <<
|
||||
getPeerAddress(peer_id).serializeString() <<
|
||||
" using unallowed mech " << chosen << "." << std::endl;
|
||||
addr_s <<
|
||||
" using unallowed mech " << chosen << std::endl;
|
||||
DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "client/texturesource.h"
|
||||
#include "client/tile.h"
|
||||
#include <IMeshManipulator.h>
|
||||
#include <SMesh.h>
|
||||
#include <SkinnedMesh.h>
|
||||
#endif
|
||||
#include "log.h"
|
||||
|
@ -959,23 +960,37 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
|
|||
palette = tsrc->getPalette(palette_name);
|
||||
|
||||
if (drawtype == NDT_MESH && !mesh.empty()) {
|
||||
// Read the mesh and apply scale
|
||||
mesh_ptr = client->getMesh(mesh);
|
||||
if (mesh_ptr) {
|
||||
v3f scale = v3f(BS) * visual_scale;
|
||||
scaleMesh(mesh_ptr, scale);
|
||||
// Note: By freshly reading, we get an unencumbered mesh.
|
||||
if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
|
||||
bool apply_bs = false;
|
||||
if (auto *skinned_mesh = dynamic_cast<scene::SkinnedMesh *>(src_mesh)) {
|
||||
// Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS.
|
||||
// See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329
|
||||
bool is_gltf = skinned_mesh->getSourceFormat() ==
|
||||
scene::SkinnedMesh::SourceFormat::GLTF;
|
||||
apply_bs = skinned_mesh->isStatic() && !is_gltf;
|
||||
// Nodes do not support mesh animation, so we clone the static pose.
|
||||
// This simplifies working with the mesh: We can just scale the vertices
|
||||
// as transformations have already been applied.
|
||||
mesh_ptr = cloneStaticMesh(src_mesh);
|
||||
src_mesh->drop();
|
||||
} else {
|
||||
auto *static_mesh = dynamic_cast<scene::SMesh *>(src_mesh);
|
||||
assert(static_mesh);
|
||||
mesh_ptr = static_mesh;
|
||||
// Compatibility: Apply BS scaling to static meshes (.obj). See #15811.
|
||||
apply_bs = true;
|
||||
}
|
||||
scaleMesh(mesh_ptr, v3f((apply_bs ? BS : 1.0f) * visual_scale));
|
||||
recalculateBoundingBox(mesh_ptr);
|
||||
if (!checkMeshNormals(mesh_ptr)) {
|
||||
// TODO this should be done consistently when the mesh is loaded
|
||||
infostream << "ContentFeatures: recalculating normals for mesh "
|
||||
<< mesh << std::endl;
|
||||
meshmanip->recalculateNormals(mesh_ptr, true, false);
|
||||
} else {
|
||||
// Animation is not supported, but we need to reset it to
|
||||
// default state if it is animated.
|
||||
// Note: recalculateNormals() also does this hence the else-block
|
||||
if (mesh_ptr->getMeshType() == scene::EAMT_SKINNED)
|
||||
((scene::SkinnedMesh*) mesh_ptr)->resetAnimation();
|
||||
}
|
||||
} else {
|
||||
mesh_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ struct ContentFeatures
|
|||
enum NodeDrawType drawtype;
|
||||
std::string mesh;
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
scene::IMesh *mesh_ptr; // mesh in case of mesh node
|
||||
scene::SMesh *mesh_ptr; // mesh in case of mesh node
|
||||
video::SColor minimap_color;
|
||||
#endif
|
||||
float visual_scale; // Misc. scale parameter
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
#include "util/string.h"
|
||||
#include "util/tracy_wrapper.h"
|
||||
#include <vector>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <signal.h>
|
||||
|
@ -81,31 +82,28 @@ namespace porting
|
|||
Signal handler (grabs Ctrl-C on POSIX systems)
|
||||
*/
|
||||
|
||||
static bool g_killed = false;
|
||||
volatile static std::sig_atomic_t g_killed = false;
|
||||
|
||||
bool *signal_handler_killstatus()
|
||||
volatile std::sig_atomic_t *signal_handler_killstatus()
|
||||
{
|
||||
return &g_killed;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) // POSIX
|
||||
#define STDERR_FILENO 2
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
if (!g_killed) {
|
||||
if (sig == SIGINT) {
|
||||
dstream << "INFO: signal_handler(): "
|
||||
<< "Ctrl-C pressed, shutting down." << std::endl;
|
||||
const char *dbg_text{"INFO: signal_handler(): "
|
||||
"Ctrl-C pressed, shutting down.\n"};
|
||||
write(STDERR_FILENO, dbg_text, strlen(dbg_text));
|
||||
} else if (sig == SIGTERM) {
|
||||
dstream << "INFO: signal_handler(): "
|
||||
<< "got SIGTERM, shutting down." << std::endl;
|
||||
const char *dbg_text{"INFO: signal_handler(): "
|
||||
"got SIGTERM, shutting down.\n"};
|
||||
write(STDERR_FILENO, dbg_text, strlen(dbg_text));
|
||||
}
|
||||
|
||||
// Comment out for less clutter when testing scripts
|
||||
/*dstream << "INFO: sigint_handler(): "
|
||||
<< "Printing debug stacks" << std::endl;
|
||||
debug_stacks_print();*/
|
||||
|
||||
g_killed = true;
|
||||
} else {
|
||||
(void)signal(sig, SIG_DFL);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#endif
|
||||
|
||||
// Be mindful of what you include here!
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include "config.h"
|
||||
#include "irrlichttypes.h" // u64
|
||||
|
@ -77,7 +78,7 @@ namespace porting
|
|||
void signal_handler_init();
|
||||
// Returns a pointer to a bool.
|
||||
// When the bool is true, program should quit.
|
||||
[[nodiscard]] bool *signal_handler_killstatus();
|
||||
[[nodiscard]] volatile std::sig_atomic_t *signal_handler_killstatus();
|
||||
|
||||
/*
|
||||
Path of static data directory.
|
||||
|
|
|
@ -56,9 +56,16 @@ void read_item_definition(lua_State* L, int index,
|
|||
if (index < 0)
|
||||
index = lua_gettop(L) + 1 + index;
|
||||
|
||||
def.type = (ItemType)getenumfield(L, index, "type",
|
||||
es_ItemType, ITEM_NONE);
|
||||
def.name.clear();
|
||||
getstringfield(L, index, "name", def.name);
|
||||
|
||||
{
|
||||
auto str = getstringfield_default(L, index, "type", "");
|
||||
if (!string_to_enum(es_ItemType, def.type, str))
|
||||
warningstream << "Item " << def.name
|
||||
<< " has unknown type \"" << str << '"' << std::endl;
|
||||
}
|
||||
|
||||
getstringfield(L, index, "description", def.description);
|
||||
getstringfield(L, index, "short_description", def.short_description);
|
||||
getstringfield(L, index, "inventory_image", def.inventory_image);
|
||||
|
@ -605,9 +612,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
|
|||
case NDT_PLANTLIKE:
|
||||
case NDT_FIRELIKE:
|
||||
default_tiling = false;
|
||||
// "break" is omitted here intentionaly, as PLANTLIKE
|
||||
// FIRELIKE drawtype both should default to having
|
||||
// backface_culling to false.
|
||||
[[fallthrough]];
|
||||
case NDT_MESH:
|
||||
case NDT_LIQUID:
|
||||
|
@ -621,7 +625,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
|
|||
break;
|
||||
}
|
||||
|
||||
// key at index -2 and value at index
|
||||
if(lua_isstring(L, index)){
|
||||
// "default_lava.png"
|
||||
tiledef.name = lua_tostring(L, index);
|
||||
|
@ -634,7 +637,10 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
|
|||
// name="default_lava.png"
|
||||
tiledef.name.clear();
|
||||
getstringfield(L, index, "name", tiledef.name);
|
||||
getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat.
|
||||
warn_if_field_exists(L, index, "image", "TileDef",
|
||||
"Deprecated: new name is \"name\".");
|
||||
getstringfield(L, index, "image", tiledef.name);
|
||||
|
||||
tiledef.backface_culling = getboolfield_default(
|
||||
L, index, "backface_culling", default_culling);
|
||||
tiledef.tileable_horizontal = getboolfield_default(
|
||||
|
@ -659,6 +665,9 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
|
|||
lua_getfield(L, index, "animation");
|
||||
tiledef.animation = read_animation_definition(L, -1);
|
||||
lua_pop(L, 1);
|
||||
} else if (!lua_isnil(L, index)) {
|
||||
// TODO: should be an error
|
||||
errorstream << "TileDef: Invalid type! (expected string or table)" << std::endl;
|
||||
}
|
||||
|
||||
return tiledef;
|
||||
|
@ -672,13 +681,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
|
||||
/* Cache existence of some callbacks */
|
||||
lua_getfield(L, index, "on_construct");
|
||||
if(!lua_isnil(L, -1)) f.has_on_construct = true;
|
||||
f.has_on_construct = !lua_isnil(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "on_destruct");
|
||||
if(!lua_isnil(L, -1)) f.has_on_destruct = true;
|
||||
f.has_on_destruct = !lua_isnil(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "after_destruct");
|
||||
if(!lua_isnil(L, -1)) f.has_after_destruct = true;
|
||||
f.has_after_destruct = !lua_isnil(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, index, "on_rightclick");
|
||||
|
@ -695,8 +704,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
|
||||
/* Visual definition */
|
||||
|
||||
f.drawtype = (NodeDrawType)getenumfield(L, index, "drawtype",
|
||||
ScriptApiNode::es_DrawType,NDT_NORMAL);
|
||||
{
|
||||
auto str = getstringfield_default(L, index, "drawtype", "");
|
||||
if (!string_to_enum(ScriptApiNode::es_DrawType, f.drawtype, str))
|
||||
warningstream << "Node " << f.name
|
||||
<< " has unknown drawtype \"" << str << '"' << std::endl;
|
||||
}
|
||||
|
||||
getfloatfield(L, index, "visual_scale", f.visual_scale);
|
||||
|
||||
/* Meshnode model filename */
|
||||
|
@ -796,10 +810,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
if (lua_toboolean(L, -1))
|
||||
f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND;
|
||||
} else if (check_field_or_nil(L, -1, LUA_TSTRING, "use_texture_alpha")) {
|
||||
int result = f.alpha;
|
||||
string_to_enum(ScriptApiNode::es_TextureAlphaMode, result,
|
||||
std::string(lua_tostring(L, -1)));
|
||||
f.alpha = static_cast<enum AlphaMode>(result);
|
||||
string_to_enum(ScriptApiNode::es_TextureAlphaMode, f.alpha, lua_tostring(L, -1));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
|
@ -817,10 +828,18 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
|
||||
getboolfield(L, index, "post_effect_color_shaded", f.post_effect_color_shaded);
|
||||
|
||||
f.param_type = (ContentParamType)getenumfield(L, index, "paramtype",
|
||||
ScriptApiNode::es_ContentParamType, CPT_NONE);
|
||||
f.param_type_2 = (ContentParamType2)getenumfield(L, index, "paramtype2",
|
||||
ScriptApiNode::es_ContentParamType2, CPT2_NONE);
|
||||
{
|
||||
auto str = getstringfield_default(L, index, "paramtype", "");
|
||||
if (!string_to_enum(ScriptApiNode::es_ContentParamType, f.param_type, str))
|
||||
warningstream << "Node " << f.name
|
||||
<< " has unknown paramtype \"" << str << '"' << std::endl;
|
||||
}
|
||||
{
|
||||
auto str = getstringfield_default(L, index, "paramtype2", "");
|
||||
if (!string_to_enum(ScriptApiNode::es_ContentParamType2, f.param_type_2, str))
|
||||
warningstream << "Node " << f.name
|
||||
<< " has unknown paramtype2 \"" << str << '"' << std::endl;
|
||||
}
|
||||
|
||||
if (!f.palette_name.empty() &&
|
||||
!(f.param_type_2 == CPT2_COLOR ||
|
||||
|
@ -855,8 +874,12 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
// Liquids flow into and replace node
|
||||
getboolfield(L, index, "floodable", f.floodable);
|
||||
// Whether the node is non-liquid, source liquid or flowing liquid
|
||||
f.liquid_type = (LiquidType)getenumfield(L, index, "liquidtype",
|
||||
ScriptApiNode::es_LiquidType, LIQUID_NONE);
|
||||
{
|
||||
auto str = getstringfield_default(L, index, "liquidtype", "");
|
||||
if (!string_to_enum(ScriptApiNode::es_LiquidType, f.liquid_type, str))
|
||||
warningstream << "Node " << f.name
|
||||
<< " has unknown liquidtype \"" << str << '"' << std::endl;
|
||||
}
|
||||
// If the content is liquid, this is the flowing version of the liquid.
|
||||
getstringfield(L, index, "liquid_alternative_flowing",
|
||||
f.liquid_alternative_flowing);
|
||||
|
@ -915,7 +938,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
lua_pushnil(L);
|
||||
while (lua_next(L, table) != 0) {
|
||||
// Value at -1
|
||||
std::string side(lua_tostring(L, -1));
|
||||
std::string_view side(lua_tostring(L, -1));
|
||||
// Note faces are flipped to make checking easier
|
||||
if (side == "top")
|
||||
f.connect_sides |= 2;
|
||||
|
@ -986,6 +1009,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
|
|||
} else if(lua_isnil(L, -1)) {
|
||||
f.liquid_move_physics = f.liquid_type != LIQUID_NONE;
|
||||
} else {
|
||||
// TODO: should be an error
|
||||
errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
@ -1805,10 +1829,8 @@ WearBarParams read_wear_bar_params(
|
|||
auto blend = WearBarParams::BLEND_MODE_CONSTANT;
|
||||
lua_getfield(L, stack_idx, "blend");
|
||||
if (check_field_or_nil(L, -1, LUA_TSTRING, "blend")) {
|
||||
int blendInt;
|
||||
if (!string_to_enum(WearBarParams::es_BlendMode, blendInt, std::string(lua_tostring(L, -1))))
|
||||
if (!string_to_enum(WearBarParams::es_BlendMode, blend, lua_tostring(L, -1)))
|
||||
throw LuaError("Invalid wear bar color blend mode");
|
||||
blend = static_cast<WearBarParams::BlendMode>(blendInt);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
|
@ -2395,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)
|
||||
{
|
||||
std::string statstr = lua_tostring(L, 3);
|
||||
{
|
||||
int statint;
|
||||
if (!string_to_enum(es_HudElementStat, statint, statstr)) {
|
||||
script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream);
|
||||
return false;
|
||||
}
|
||||
|
||||
stat = static_cast<HudElementStat>(statint);
|
||||
if (!string_to_enum(es_HudElementStat, stat, statstr)) {
|
||||
script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (stat) {
|
||||
|
|
|
@ -18,6 +18,8 @@ extern "C" {
|
|||
#include <cmath>
|
||||
#include "common/c_types.h"
|
||||
|
||||
static v3d read_v3d(lua_State *L, int index);
|
||||
static v3d check_v3d(lua_State *L, int index);
|
||||
|
||||
#define CHECK_TYPE(index, name, type) do { \
|
||||
int t = lua_type(L, (index)); \
|
||||
|
@ -28,6 +30,16 @@ extern "C" {
|
|||
} \
|
||||
} while(0)
|
||||
|
||||
// TODO: this should be turned into an error in 2026.
|
||||
// Just revert the commit that added this line.
|
||||
#define CHECK_NOT_NIL(index, name) do { \
|
||||
if (lua_isnoneornil(L, (index))) { \
|
||||
auto msg = std::string("Invalid ") + (name) + \
|
||||
" (value is nil)."; \
|
||||
log_deprecated(L, msg, 1, true); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define CHECK_FLOAT(value, name) do {\
|
||||
if (std::isnan(value) || std::isinf(value)) { \
|
||||
throw LuaError("Invalid float value for '" name \
|
||||
|
@ -35,7 +47,13 @@ extern "C" {
|
|||
} \
|
||||
} while (0)
|
||||
|
||||
// strictly check type of coordinate
|
||||
// (this won't permit string-to-int conversion, so maybe not the best idea?)
|
||||
#define CHECK_POS_COORD(index, name) CHECK_TYPE(index, "vector coordinate " name, LUA_TNUMBER)
|
||||
// loosely check type of coordinate
|
||||
#define CHECK_POS_COORD2(index, name) CHECK_NOT_NIL(index, "vector coordinate " name)
|
||||
|
||||
// Note: not needed when using read_v3_aux
|
||||
#define CHECK_POS_TAB(index) CHECK_TYPE(index, "vector", LUA_TTABLE)
|
||||
|
||||
|
||||
|
@ -44,6 +62,7 @@ extern "C" {
|
|||
*/
|
||||
static void read_v3_aux(lua_State *L, int index)
|
||||
{
|
||||
// TODO: someone find out if it's faster to have the type check in Lua too
|
||||
CHECK_POS_TAB(index);
|
||||
lua_pushvalue(L, index);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR);
|
||||
|
@ -87,24 +106,12 @@ void push_v2f(lua_State *L, v2f p)
|
|||
|
||||
v2s16 read_v2s16(lua_State *L, int index)
|
||||
{
|
||||
v2s16 p;
|
||||
CHECK_POS_TAB(index);
|
||||
lua_getfield(L, index, "x");
|
||||
p.X = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "y");
|
||||
p.Y = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return p;
|
||||
return v2s16::from(read_v2f(L, index));
|
||||
}
|
||||
|
||||
void push_v2s16(lua_State *L, v2s16 p)
|
||||
{
|
||||
lua_createtable(L, 0, 2);
|
||||
lua_pushinteger(L, p.X);
|
||||
lua_setfield(L, -2, "x");
|
||||
lua_pushinteger(L, p.Y);
|
||||
lua_setfield(L, -2, "y");
|
||||
push_v2s32(L, v2s32::from(p));
|
||||
}
|
||||
|
||||
void push_v2s32(lua_State *L, v2s32 p)
|
||||
|
@ -127,15 +134,7 @@ void push_v2u32(lua_State *L, v2u32 p)
|
|||
|
||||
v2s32 read_v2s32(lua_State *L, int index)
|
||||
{
|
||||
v2s32 p;
|
||||
CHECK_POS_TAB(index);
|
||||
lua_getfield(L, index, "x");
|
||||
p.X = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "y");
|
||||
p.Y = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return p;
|
||||
return v2s32::from(read_v2f(L, index));
|
||||
}
|
||||
|
||||
v2f read_v2f(lua_State *L, int index)
|
||||
|
@ -143,9 +142,11 @@ v2f read_v2f(lua_State *L, int index)
|
|||
v2f p;
|
||||
CHECK_POS_TAB(index);
|
||||
lua_getfield(L, index, "x");
|
||||
CHECK_POS_COORD2(-1, "x");
|
||||
p.X = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "y");
|
||||
CHECK_POS_COORD2(-1, "y");
|
||||
p.Y = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return p;
|
||||
|
@ -170,30 +171,20 @@ v2f check_v2f(lua_State *L, int index)
|
|||
|
||||
v3f read_v3f(lua_State *L, int index)
|
||||
{
|
||||
read_v3_aux(L, index);
|
||||
float x = lua_tonumber(L, -3);
|
||||
float y = lua_tonumber(L, -2);
|
||||
float z = lua_tonumber(L, -1);
|
||||
lua_pop(L, 3);
|
||||
return v3f(x, y, z);
|
||||
return v3f::from(read_v3d(L, index));
|
||||
}
|
||||
|
||||
v3f check_v3f(lua_State *L, int index)
|
||||
{
|
||||
read_v3_aux(L, index);
|
||||
CHECK_POS_COORD(-3, "x");
|
||||
CHECK_POS_COORD(-2, "y");
|
||||
CHECK_POS_COORD(-1, "z");
|
||||
float x = lua_tonumber(L, -3);
|
||||
float y = lua_tonumber(L, -2);
|
||||
float z = lua_tonumber(L, -1);
|
||||
lua_pop(L, 3);
|
||||
return v3f(x, y, z);
|
||||
return v3f::from(check_v3d(L, index));
|
||||
}
|
||||
|
||||
v3d read_v3d(lua_State *L, int index)
|
||||
{
|
||||
read_v3_aux(L, index);
|
||||
CHECK_POS_COORD2(-3, "x");
|
||||
CHECK_POS_COORD2(-2, "y");
|
||||
CHECK_POS_COORD2(-1, "z");
|
||||
double x = lua_tonumber(L, -3);
|
||||
double y = lua_tonumber(L, -2);
|
||||
double z = lua_tonumber(L, -1);
|
||||
|
@ -211,6 +202,9 @@ v3d check_v3d(lua_State *L, int index)
|
|||
double y = lua_tonumber(L, -2);
|
||||
double z = lua_tonumber(L, -1);
|
||||
lua_pop(L, 3);
|
||||
CHECK_FLOAT(x, "x");
|
||||
CHECK_FLOAT(y, "y");
|
||||
CHECK_FLOAT(z, "z");
|
||||
return v3d(x, y, z);
|
||||
}
|
||||
|
||||
|
@ -286,18 +280,23 @@ video::SColor read_ARGB8(lua_State *L, int index)
|
|||
return std::fmax(0.0, std::fmin(255.0, c));
|
||||
};
|
||||
|
||||
// FIXME: maybe we should have strict type checks here. compare to is_color_table()
|
||||
|
||||
video::SColor color(0);
|
||||
CHECK_TYPE(index, "ARGB color", LUA_TTABLE);
|
||||
lua_getfield(L, index, "a");
|
||||
color.setAlpha(lua_isnumber(L, -1) ? clamp_col(lua_tonumber(L, -1)) : 0xFF);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "r");
|
||||
CHECK_NOT_NIL(-1, "color component R");
|
||||
color.setRed(clamp_col(lua_tonumber(L, -1)));
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "g");
|
||||
CHECK_NOT_NIL(-1, "color component G");
|
||||
color.setGreen(clamp_col(lua_tonumber(L, -1)));
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "b");
|
||||
CHECK_NOT_NIL(-1, "color component B");
|
||||
color.setBlue(clamp_col(lua_tonumber(L, -1)));
|
||||
lua_pop(L, 1);
|
||||
return color;
|
||||
|
|
|
@ -74,13 +74,23 @@ v2f check_v2f(lua_State *L, int index);
|
|||
v3f check_v3f(lua_State *L, int index);
|
||||
v3s16 check_v3s16(lua_State *L, int index);
|
||||
|
||||
// TODO: some day we should figure out the type-checking situation so it's done
|
||||
// everywhere. (right now {x=true, y=false} as v2f is {0,0} with no warning)
|
||||
|
||||
/// @warning relaxed type-checking, prefer `check_v3f`.
|
||||
v3f read_v3f(lua_State *L, int index);
|
||||
/// @warning relaxed type-checking, prefer `check_v2f`.
|
||||
v2f read_v2f(lua_State *L, int index);
|
||||
/// @warning relaxed type-checking
|
||||
v2s16 read_v2s16(lua_State *L, int index);
|
||||
/// @warning relaxed type-checking
|
||||
v2s32 read_v2s32(lua_State *L, int index);
|
||||
/// @warning relaxed type-checking, prefer `check_v3s16`.
|
||||
v3s16 read_v3s16(lua_State *L, int index);
|
||||
|
||||
video::SColor read_ARGB8(lua_State *L, int index);
|
||||
bool read_color(lua_State *L, int index, video::SColor *color);
|
||||
bool is_color_table (lua_State *L, int index);
|
||||
bool is_color_table(lua_State *L, int index);
|
||||
|
||||
/**
|
||||
* Read a floating-point axis-aligned box from Lua.
|
||||
|
@ -95,7 +105,6 @@ bool is_color_table (lua_State *L, int index);
|
|||
*/
|
||||
aabb3f read_aabb3f(lua_State *L, int index, f32 scale);
|
||||
|
||||
v3s16 read_v3s16(lua_State *L, int index);
|
||||
std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale);
|
||||
size_t read_stringlist(lua_State *L, int index,
|
||||
std::vector<std::string> *result);
|
||||
|
|
|
@ -38,7 +38,6 @@ public:
|
|||
static struct EnumString es_ContentParamType[];
|
||||
static struct EnumString es_ContentParamType2[];
|
||||
static struct EnumString es_LiquidType[];
|
||||
static struct EnumString es_LiquidMoveType[];
|
||||
static struct EnumString es_NodeBoxType[];
|
||||
static struct EnumString es_TextureAlphaMode[];
|
||||
};
|
||||
|
|
|
@ -266,15 +266,22 @@ int ModApiEnv::l_bulk_swap_node(lua_State *L)
|
|||
// get_node_raw(x, y, z) -> content, param1, param2, pos_ok
|
||||
int ModApiEnv::l_get_node_raw(lua_State *L)
|
||||
{
|
||||
GET_ENV_PTR;
|
||||
GET_PLAIN_ENV_PTR;
|
||||
|
||||
// pos
|
||||
// mirrors implementation of read_v3s16 (with the exact same rounding)
|
||||
double x = lua_tonumber(L, 1);
|
||||
double y = lua_tonumber(L, 2);
|
||||
double z = lua_tonumber(L, 3);
|
||||
v3s16 pos = doubleToInt(v3d(x, y, z), 1.0);
|
||||
// Do it
|
||||
v3s16 pos;
|
||||
// mirrors the implementation of read_v3s16 (with the exact same rounding)
|
||||
{
|
||||
if (lua_isnoneornil(L, 1))
|
||||
log_deprecated(L, "X position is nil", 1, true);
|
||||
if (lua_isnoneornil(L, 2))
|
||||
log_deprecated(L, "Y position is nil", 1, true);
|
||||
if (lua_isnoneornil(L, 3))
|
||||
log_deprecated(L, "Z position is nil", 1, true);
|
||||
double x = lua_tonumber(L, 1);
|
||||
double y = lua_tonumber(L, 2);
|
||||
double z = lua_tonumber(L, 3);
|
||||
pos = doubleToInt(v3d(x, y, z), 1.0);
|
||||
}
|
||||
bool pos_ok;
|
||||
MapNode n = env->getMap().getNode(pos, &pos_ok);
|
||||
// Return node and pos_ok
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue