mirror of
https://github.com/luanti-org/luanti.git
synced 2025-09-30 19:22:14 +00:00
Merge branch 'luanti-org:master' into skybox
This commit is contained in:
commit
770ead7624
111 changed files with 1815 additions and 1848 deletions
20
.github/CONTRIBUTING.md
vendored
20
.github/CONTRIBUTING.md
vendored
|
@ -25,16 +25,16 @@ Contributions are welcome! Here's how you can help:
|
|||
the work, to avoid disappointment.
|
||||
|
||||
You may also benefit from discussing on our IRC development channel
|
||||
[#luanti-dev](http://www.luanti.org/irc/). Note that a proper IRC client
|
||||
[#luanti-dev](https://docs.luanti.org/about/irc/). Note that a proper IRC client
|
||||
is required to speak on this channel.
|
||||
|
||||
3. Start coding!
|
||||
- Refer to the
|
||||
[Lua API](https://github.com/luanti-org/luanti/blob/master/doc/lua_api.md),
|
||||
[Developer Wiki](https://dev.luanti.org/) and other
|
||||
[Luanti Documentation](https://docs.luanti.org/) and other
|
||||
[documentation](https://github.com/luanti-org/luanti/tree/master/doc).
|
||||
- Follow the [C/C++](https://dev.luanti.org/Code_style_guidelines) and
|
||||
[Lua](https://dev.luanti.org/Lua_code_style_guidelines) code style guidelines.
|
||||
- Follow the [C/C++](https://docs.luanti.org/for-engine-devs/code-style-guidelines/) and
|
||||
[Lua](https://docs.luanti.org/for-engine-devs/lua-code-style-guidelines/) code style guidelines.
|
||||
- Check your code works as expected and document any changes to the Lua API.
|
||||
- To avoid conflicting changes between contributions, do not do the following manually. They will be done before each release.
|
||||
- Run `updatepo.sh` or update `luanti.po{,t}` even if your code adds new translatable strings.
|
||||
|
@ -64,8 +64,8 @@ Contributions are welcome! Here's how you can help:
|
|||
picture of the project.
|
||||
2. It works.
|
||||
3. It follows the code style for
|
||||
[C/C++](https://dev.luanti.org/Code_style_guidelines) or
|
||||
[Lua](https://dev.luanti.org/Lua_code_style_guidelines).
|
||||
[C/C++](https://docs.luanti.org/for-engine-devs/code-style-guidelines/) or
|
||||
[Lua](https://docs.luanti.org/for-engine-devs/lua-code-style-guidelines/).
|
||||
4. The code's interfaces are well designed, regardless of other aspects that
|
||||
might need more work in the future.
|
||||
5. It uses protocols and formats which include the required compatibility.
|
||||
|
@ -106,7 +106,7 @@ the project page with a list of current languages
|
|||
Builtin (the component which contains things like server messages, chat command
|
||||
descriptions, privilege descriptions) is translated separately; it needs to be
|
||||
translated by editing a `.tr` text file. See
|
||||
[Translation](https://dev.luanti.org/Translation) for more information.
|
||||
[Translation](https://docs.luanti.org/for-creators/translation/) for more information.
|
||||
|
||||
## Donations
|
||||
|
||||
|
@ -116,11 +116,11 @@ methods on [our website](http://www.luanti.org/development/#donate).
|
|||
# Maintaining
|
||||
|
||||
* This is a concise version of the
|
||||
[Rules & Guidelines](https://dev.luanti.org/engine-dev-process/) on the developer wiki.*
|
||||
[Rules & Guidelines](https://docs.luanti.org/for-engine-devs/) on the Luanti Documentation.*
|
||||
|
||||
These notes are for those who have push access Luanti (core developers / maintainers).
|
||||
|
||||
- See the [project organisation](https://dev.luanti.org/Organisation) for the people involved.
|
||||
- See the [project organisation](https://docs.luanti.org/for-engine-devs/organization/) for the people involved.
|
||||
|
||||
## Concept approvals and roadmaps
|
||||
|
||||
|
@ -169,4 +169,4 @@ Submit a :+1: (+1) or "Looks good" comment to show you believe the pull-request
|
|||
|
||||
## Releasing a new version
|
||||
|
||||
*Refer to [dev.luanti.org/Releasing_Luanti](https://dev.luanti.org/Releasing_Luanti)*
|
||||
*Refer to [docs.luanti.org/for-engine-devs/releasing-luanti](https://docs.luanti.org/for-engine-devs/releasing-luanti/)*
|
||||
|
|
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
|
@ -76,6 +76,8 @@ jobs:
|
|||
env:
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
# just to check that they compile correctly
|
||||
CMAKE_FLAGS: '-DBUILD_BENCHMARKS=1'
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
|
|
@ -28,7 +28,7 @@ Table of Contents
|
|||
Further documentation
|
||||
----------------------
|
||||
- Website: https://www.luanti.org/
|
||||
- Wiki: https://wiki.luanti.org/
|
||||
- Luanti Documentation: https://docs.luanti.org/
|
||||
- Forum: https://forum.luanti.org/
|
||||
- GitHub: https://github.com/luanti-org/luanti/
|
||||
- [Developer documentation](doc/developing/)
|
||||
|
|
|
@ -438,11 +438,28 @@ function make.key(setting)
|
|||
if value == "" then
|
||||
return height
|
||||
end
|
||||
|
||||
local critical_keys = {
|
||||
keymap_drop = true,
|
||||
keymap_dig = true,
|
||||
keymap_place = true,
|
||||
}
|
||||
|
||||
for _, o in ipairs(core.full_settingtypes) do
|
||||
if o.type == "key" and o.name ~= setting.name and core.are_keycodes_equal(core.settings:get(o.name), value) then
|
||||
table.insert(fs, ("label[0,%f;%s]"):format(height + 0.3,
|
||||
core.colorize(mt_color_orange, fgettext([[Conflicts with "$1"]], fgettext(o.readable_name)))))
|
||||
height = height + 0.6
|
||||
if o.type == "key" and o.name ~= setting.name and
|
||||
core.are_keycodes_equal(core.settings:get(o.name), value) then
|
||||
|
||||
local is_current_close_world = setting.name == "keymap_close_world"
|
||||
local is_other_close_world = o.name == "keymap_close_world"
|
||||
local is_current_critical = critical_keys[setting.name]
|
||||
local is_other_critical = critical_keys[o.name]
|
||||
|
||||
if (is_other_critical or is_current_critical) or
|
||||
(not is_current_close_world and not is_other_close_world) then
|
||||
table.insert(fs, ("label[0,%f;%s]"):format(height + 0.3,
|
||||
core.colorize(mt_color_orange, fgettext([[Conflicts with "$1"]], fgettext(o.readable_name)))))
|
||||
height = height + 0.6
|
||||
end
|
||||
end
|
||||
end
|
||||
return height
|
||||
|
@ -454,12 +471,12 @@ function make.key(setting)
|
|||
|
||||
get_formspec = function(self, avail_w)
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
local btn_bind_width = math.max(2.5, avail_w/2)
|
||||
local btn_bind_width = math.max(2.5, avail_w / 2)
|
||||
local value = core.settings:get(setting.name)
|
||||
local fs = {
|
||||
("label[0,0.4;%s]"):format(get_label(setting)),
|
||||
("button_key[%f,0;%f,0.8;%s;%s]"):format(
|
||||
btn_bind_width, btn_bind_width-0.8,
|
||||
btn_bind_width, btn_bind_width - 0.8,
|
||||
btn_bind, core.formspec_escape(value)),
|
||||
("image_button[%f,0;0.8,0.8;%s;%s;]"):format(avail_w - 0.8,
|
||||
core.formspec_escape(defaulttexturedir .. "clear.png"),
|
||||
|
|
|
@ -16,7 +16,7 @@ local minetest_example_header = [[
|
|||
# to the program, eg. "luanti.exe --config ../minetest.conf.example".
|
||||
|
||||
# Further documentation:
|
||||
# https://wiki.luanti.org/
|
||||
# https://docs.luanti.org/
|
||||
|
||||
]]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -740,16 +740,16 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
|
|||
--
|
||||
|
||||
local get_node_raw = core.get_node_raw
|
||||
core.get_node_raw = nil
|
||||
local get_name_from_content_id = core.get_name_from_content_id
|
||||
|
||||
function core.get_node(pos)
|
||||
local content, param1, param2 = get_node_raw(pos.x, pos.y, pos.z)
|
||||
return {name = core.get_name_from_content_id(content), param1 = param1, param2 = param2}
|
||||
return {name = get_name_from_content_id(content), param1 = param1, param2 = param2}
|
||||
end
|
||||
|
||||
function core.get_node_or_nil(pos)
|
||||
local content, param1, param2, pos_ok = get_node_raw(pos.x, pos.y, pos.z)
|
||||
return pos_ok and
|
||||
{name = core.get_name_from_content_id(content), param1 = param1, param2 = param2}
|
||||
{name = get_name_from_content_id(content), param1 = param1, param2 = param2}
|
||||
or nil
|
||||
end
|
||||
|
|
|
@ -3,18 +3,61 @@
|
|||
-- SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
|
||||
local function get_info_formspec(size, padding, text)
|
||||
return table.concat({
|
||||
"formspec_version[6]",
|
||||
"size[", size.x, ",", size.y, "]",
|
||||
"padding[0,0]",
|
||||
"bgcolor[;true]",
|
||||
local function get_description_hypertext(package, info, loading_error)
|
||||
-- Screenshots and description
|
||||
local hypertext = "<big><b>" .. core.hypertext_escape(package.short_description) .. "</b></big>\n"
|
||||
|
||||
"label[4,4.35;", text, "]",
|
||||
"container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
|
||||
"button[0,0;2,0.8;back;", fgettext("Back"), "]",
|
||||
"container_end[]",
|
||||
})
|
||||
local screenshots = info and info.screenshots or {{url = package.thumbnail}}
|
||||
|
||||
local winfo = core.get_window_info()
|
||||
local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
|
||||
for i, ss in ipairs(screenshots) do
|
||||
local path = get_screenshot(package, ss.url, 2)
|
||||
hypertext = hypertext .. "<action name=\"ss_".. i .. "\"><img name=\"" ..
|
||||
core.hypertext_escape(path) .. "\" width=" .. (3 * fs_to_px) ..
|
||||
" height=" .. (2 * fs_to_px) .. "></action>"
|
||||
if i ~= #screenshots then
|
||||
hypertext = hypertext .. "<img name=\"blank.png\" width=" .. (0.25 * fs_to_px) ..
|
||||
" height=" .. (2.25 * fs_to_px).. ">"
|
||||
end
|
||||
end
|
||||
|
||||
if info then
|
||||
hypertext = hypertext .. "\n" .. info.long_description.head
|
||||
|
||||
local first = true
|
||||
local function add_link_button(label, name)
|
||||
if info[name] then
|
||||
if not first then
|
||||
hypertext = hypertext .. " | "
|
||||
end
|
||||
hypertext = hypertext .. "<action name=link_" .. name .. ">" .. label .. "</action>"
|
||||
info.long_description.links["link_" .. name] = info[name]
|
||||
first = false
|
||||
end
|
||||
end
|
||||
|
||||
add_link_button(hgettext("Donate"), "donate_url")
|
||||
add_link_button(hgettext("Website"), "website")
|
||||
add_link_button(hgettext("Source"), "repo")
|
||||
add_link_button(hgettext("Issue Tracker"), "issue_tracker")
|
||||
add_link_button(hgettext("Translate"), "translation_url")
|
||||
add_link_button(hgettext("Forum Topic"), "forum_url")
|
||||
|
||||
hypertext = hypertext .. "\n\n" .. info.long_description.body
|
||||
|
||||
elseif loading_error then
|
||||
hypertext = hypertext .. "\n\n" .. hgettext("Error loading package information")
|
||||
else
|
||||
hypertext = hypertext .. "\n\n" .. hgettext("Loading...")
|
||||
end
|
||||
|
||||
-- Fix the path to blank.png. This is needed for bullet indentation,
|
||||
-- and also used for screenshot spacing.
|
||||
hypertext = hypertext:gsub("<img name=\"?blank.png\"? ",
|
||||
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
|
||||
|
||||
return hypertext
|
||||
end
|
||||
|
||||
|
||||
|
@ -41,18 +84,10 @@ local function get_formspec(data)
|
|||
|
||||
assert(data.package.name == info.name)
|
||||
data.info = info
|
||||
-- note: get_full_package_info can also return cached info immediately
|
||||
ui.update()
|
||||
end)
|
||||
end
|
||||
|
||||
-- get_full_package_info can return cached info immediately, so
|
||||
-- check to see if that happened
|
||||
if not data.info then
|
||||
if data.loading_error then
|
||||
return get_info_formspec(size, window_padding, fgettext("Error loading package information"))
|
||||
end
|
||||
return get_info_formspec(size, window_padding, fgettext("Loading..."))
|
||||
end
|
||||
end
|
||||
|
||||
-- Check installation status
|
||||
|
@ -60,10 +95,14 @@ local function get_formspec(data)
|
|||
|
||||
local info = data.info
|
||||
|
||||
local info_line =
|
||||
fgettext("by $1 — $2 downloads — +$3 / $4 / -$5",
|
||||
local info_line
|
||||
if info then
|
||||
info_line = fgettext_ne("by $1 — $2 downloads — +$3 / $4 / -$5",
|
||||
info.author, info.downloads,
|
||||
info.reviews.positive, info.reviews.neutral, info.reviews.negative)
|
||||
else
|
||||
info_line = fgettext_ne("by $1", package.author)
|
||||
end
|
||||
|
||||
local bottom_buttons_y = H - 0.8
|
||||
|
||||
|
@ -79,7 +118,7 @@ local function get_formspec(data)
|
|||
"button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
|
||||
|
||||
"style_type[label;font_size=+24;font=bold]",
|
||||
"label[0,0.4;", core.formspec_escape(info.title), "]",
|
||||
"label[0,0.4;", core.formspec_escape(package.title), "]",
|
||||
"style_type[label;font_size=;font=]",
|
||||
|
||||
"label[0,1.2;", core.formspec_escape(info_line), "]",
|
||||
|
@ -100,11 +139,13 @@ local function get_formspec(data)
|
|||
formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir)
|
||||
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
|
||||
elseif not package.path then
|
||||
local label = info and fgettext("Install [$1]", info.download_size) or
|
||||
fgettext("Install")
|
||||
formspec[#formspec + 1] = "style[install;bgcolor=green]"
|
||||
formspec[#formspec + 1] = "button["
|
||||
formspec[#formspec + 1] = right_button_rect
|
||||
formspec[#formspec + 1] =";install;"
|
||||
formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
|
||||
formspec[#formspec + 1] = label
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
if package.installed_release < package.release then
|
||||
|
@ -125,13 +166,15 @@ local function get_formspec(data)
|
|||
formspec[#formspec + 1] = "]"
|
||||
end
|
||||
|
||||
local review_count = info.reviews.positive + info.reviews.neutral + info.reviews.negative
|
||||
local current_tab = data.current_tab or 1
|
||||
local tab_titles = {
|
||||
fgettext("Description"),
|
||||
fgettext("Information"),
|
||||
fgettext("Reviews") .. core.formspec_escape(" [" .. review_count .. "]"),
|
||||
}
|
||||
if info then
|
||||
local review_count = info.reviews.positive + info.reviews.neutral + info.reviews.negative
|
||||
table.insert(tab_titles, fgettext("Information"))
|
||||
table.insert(tab_titles, fgettext("Reviews") .. core.formspec_escape(" [" .. review_count .. "]"))
|
||||
end
|
||||
|
||||
local tab_body_height = bottom_buttons_y - 2.8
|
||||
|
||||
|
@ -147,59 +190,21 @@ local function get_formspec(data)
|
|||
})
|
||||
|
||||
if current_tab == 1 then
|
||||
-- Screenshots and description
|
||||
local hypertext = "<big><b>" .. core.hypertext_escape(info.short_description) .. "</b></big>\n"
|
||||
local winfo = core.get_window_info()
|
||||
local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
|
||||
for i, ss in ipairs(info.screenshots) do
|
||||
local path = get_screenshot(package, ss.url, 2)
|
||||
hypertext = hypertext .. "<action name=\"ss_".. i .. "\"><img name=\"" ..
|
||||
core.hypertext_escape(path) .. "\" width=" .. (3 * fs_to_px) ..
|
||||
" height=" .. (2 * fs_to_px) .. "></action>"
|
||||
if i ~= #info.screenshots then
|
||||
hypertext = hypertext .. "<img name=\"blank.png\" width=" .. (0.25 * fs_to_px) ..
|
||||
" height=" .. (2.25 * fs_to_px).. ">"
|
||||
end
|
||||
end
|
||||
hypertext = hypertext .. "\n" .. info.long_description.head
|
||||
|
||||
local first = true
|
||||
local function add_link_button(label, name)
|
||||
if info[name] then
|
||||
if not first then
|
||||
hypertext = hypertext .. " | "
|
||||
end
|
||||
hypertext = hypertext .. "<action name=link_" .. name .. ">" .. core.hypertext_escape(label) .. "</action>"
|
||||
info.long_description.links["link_" .. name] = info[name]
|
||||
first = false
|
||||
end
|
||||
end
|
||||
|
||||
add_link_button(fgettext("Donate"), "donate_url")
|
||||
add_link_button(fgettext("Website"), "website")
|
||||
add_link_button(fgettext("Source"), "repo")
|
||||
add_link_button(fgettext("Issue Tracker"), "issue_tracker")
|
||||
add_link_button(fgettext("Translate"), "translation_url")
|
||||
add_link_button(fgettext("Forum Topic"), "forum_url")
|
||||
|
||||
hypertext = hypertext .. "\n\n" .. info.long_description.body
|
||||
|
||||
-- Fix the path to blank.png. This is needed for bullet indentation.
|
||||
hypertext = hypertext:gsub("<img name=\"?blank.png\"? ",
|
||||
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
|
||||
|
||||
local hypertext = get_description_hypertext(package, info, data.loading_error)
|
||||
table.insert_all(formspec, {
|
||||
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
|
||||
";desc;", core.formspec_escape(hypertext), "]",
|
||||
})
|
||||
|
||||
elseif current_tab == 2 then
|
||||
assert(info)
|
||||
local hypertext = info.info_hypertext.head .. info.info_hypertext.body
|
||||
table.insert_all(formspec, {
|
||||
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
|
||||
";info;", core.formspec_escape(hypertext), "]",
|
||||
})
|
||||
elseif current_tab == 3 then
|
||||
assert(info)
|
||||
if not package.reviews and not data.reviews_error and not data.reviews_loading then
|
||||
data.reviews_loading = true
|
||||
|
||||
|
@ -286,10 +291,6 @@ local function handle_submit(this, fields)
|
|||
return true
|
||||
end
|
||||
|
||||
if not info then
|
||||
return false
|
||||
end
|
||||
|
||||
if fields.open_contentdb then
|
||||
local version = core.get_version()
|
||||
local url = core.settings:get("contentdb_url") .. "/packages/" .. package.url_part ..
|
||||
|
@ -312,6 +313,12 @@ local function handle_submit(this, fields)
|
|||
return true
|
||||
end
|
||||
|
||||
-- The events handled below are only valid if the package info has finished
|
||||
-- loading.
|
||||
if not info then
|
||||
return false
|
||||
end
|
||||
|
||||
if fields.tabs then
|
||||
this.data.current_tab = tonumber(fields.tabs)
|
||||
return true
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -182,23 +182,17 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
|
|||
|
||||
[**Keybindings]
|
||||
|
||||
# Key for moving the player forward.
|
||||
keymap_forward (Move forward) key SYSTEM_SCANCODE_26
|
||||
|
||||
# Key for moving the player backward.
|
||||
# Will also disable autoforward, when active.
|
||||
keymap_backward (Move backward) key SYSTEM_SCANCODE_22
|
||||
|
||||
# Key for moving the player left.
|
||||
keymap_left (Move left) key SYSTEM_SCANCODE_4
|
||||
|
||||
# Key for moving the player right.
|
||||
keymap_right (Move right) key SYSTEM_SCANCODE_7
|
||||
|
||||
# Key for jumping.
|
||||
keymap_jump (Jump) key SYSTEM_SCANCODE_44
|
||||
|
||||
# Key for sneaking.
|
||||
# Also used for climbing down and descending in water if aux1_descends is disabled.
|
||||
keymap_sneak (Sneak) key SYSTEM_SCANCODE_225
|
||||
|
||||
|
@ -210,13 +204,11 @@ keymap_dig (Dig/punch/use) key KEY_LBUTTON
|
|||
# (Note: The actual meaning might vary on a per-game basis.)
|
||||
keymap_place (Place/use) key KEY_RBUTTON
|
||||
|
||||
# Key for opening the inventory.
|
||||
keymap_inventory (Open inventory) key SYSTEM_SCANCODE_12
|
||||
|
||||
# Key for moving fast in fast mode.
|
||||
keymap_aux1 (Aux1) key SYSTEM_SCANCODE_8
|
||||
|
||||
# Key for opening the chat window.
|
||||
keymap_chat (Open chat) key SYSTEM_SCANCODE_23
|
||||
|
||||
# Key for opening the chat window to type commands.
|
||||
|
@ -225,70 +217,48 @@ keymap_cmd (Command) key SYSTEM_SCANCODE_56
|
|||
# Key for opening the chat window to type local commands.
|
||||
keymap_cmd_local (Local command) key SYSTEM_SCANCODE_55
|
||||
|
||||
# Key for toggling unlimited view range.
|
||||
keymap_rangeselect (Range select) key
|
||||
keymap_rangeselect (Toggle unlimited view range) key
|
||||
|
||||
# Key for toggling flying.
|
||||
keymap_freemove (Toggle fly) key SYSTEM_SCANCODE_14
|
||||
|
||||
# Key for toggling pitch move mode.
|
||||
keymap_pitchmove (Toggle pitchmove) key
|
||||
|
||||
# Key for toggling fast mode.
|
||||
keymap_fastmove (Toggle fast) key SYSTEM_SCANCODE_13
|
||||
|
||||
# Key for toggling noclip mode.
|
||||
keymap_noclip (Toggle noclip) key SYSTEM_SCANCODE_11
|
||||
|
||||
# Key for selecting the next item in the hotbar.
|
||||
keymap_hotbar_next (Hotbar: select next item) key SYSTEM_SCANCODE_17
|
||||
|
||||
# Key for selecting the previous item in the hotbar.
|
||||
keymap_hotbar_previous (Hotbar: select previous item) key SYSTEM_SCANCODE_5
|
||||
|
||||
# Key for muting the game.
|
||||
keymap_mute (Mute) key SYSTEM_SCANCODE_16
|
||||
|
||||
# Key for increasing the volume.
|
||||
keymap_increase_volume (Increase volume) key
|
||||
|
||||
# Key for decreasing the volume.
|
||||
keymap_decrease_volume (Decrease volume) key
|
||||
|
||||
# Key for toggling autoforward.
|
||||
keymap_autoforward (Toggle automatic forward) key
|
||||
|
||||
# Key for toggling cinematic mode.
|
||||
keymap_cinematic (Toggle cinematic mode) key
|
||||
|
||||
# Key for toggling display of minimap.
|
||||
keymap_minimap (Toggle minimap) key SYSTEM_SCANCODE_25
|
||||
|
||||
# Key for taking screenshots.
|
||||
keymap_screenshot (Screenshot) key SYSTEM_SCANCODE_69
|
||||
|
||||
# Key for toggling fullscreen mode.
|
||||
keymap_fullscreen (Toggle fullscreen) key SYSTEM_SCANCODE_68
|
||||
|
||||
# Key for dropping the currently selected item.
|
||||
keymap_drop (Drop item) key SYSTEM_SCANCODE_20
|
||||
|
||||
# Key to use view zoom when possible.
|
||||
keymap_zoom (Zoom) key SYSTEM_SCANCODE_29
|
||||
|
||||
# Key for toggling the display of the HUD.
|
||||
keymap_toggle_hud (Toggle HUD) key SYSTEM_SCANCODE_58
|
||||
|
||||
# Key for toggling the display of chat.
|
||||
keymap_toggle_chat (Toggle chat log) key SYSTEM_SCANCODE_59
|
||||
|
||||
# Key for toggling the display of the large chat console.
|
||||
keymap_console (Large chat console) key SYSTEM_SCANCODE_67
|
||||
keymap_console (Toggle large chat console) key SYSTEM_SCANCODE_67
|
||||
|
||||
# Key for toggling the display of fog.
|
||||
keymap_toggle_fog (Toggle fog) key SYSTEM_SCANCODE_60
|
||||
|
||||
# Key for toggling the display of debug info.
|
||||
keymap_toggle_debug (Toggle debug info) key SYSTEM_SCANCODE_62
|
||||
|
||||
# Key for toggling the display of the profiler. Used for development.
|
||||
|
@ -297,109 +267,78 @@ keymap_toggle_profiler (Toggle profiler) key SYSTEM_SCANCODE_63
|
|||
# Key for toggling the display of mapblock boundaries.
|
||||
keymap_toggle_block_bounds (Toggle block bounds) key
|
||||
|
||||
# Key for switching between first- and third-person camera.
|
||||
keymap_camera_mode (Toggle camera mode) key SYSTEM_SCANCODE_6
|
||||
|
||||
# Key for increasing the viewing range.
|
||||
keymap_increase_viewing_range_min (Increase view range) key SYSTEM_SCANCODE_46
|
||||
|
||||
# Key for decreasing the viewing range.
|
||||
keymap_decrease_viewing_range_min (Decrease view range) key SYSTEM_SCANCODE_45
|
||||
|
||||
# Key for selecting the first hotbar slot.
|
||||
# Modifier key bind for closing your world.
|
||||
# Requires ESC + the selected key to work.
|
||||
keymap_close_world (Return to Main Menu) key
|
||||
|
||||
keymap_slot1 (Hotbar slot 1) key SYSTEM_SCANCODE_30
|
||||
|
||||
# Key for selecting the second hotbar slot.
|
||||
keymap_slot2 (Hotbar slot 2) key SYSTEM_SCANCODE_31
|
||||
|
||||
# Key for selecting the third hotbar slot.
|
||||
keymap_slot3 (Hotbar slot 3) key SYSTEM_SCANCODE_32
|
||||
|
||||
# Key for selecting the fourth hotbar slot.
|
||||
keymap_slot4 (Hotbar slot 4) key SYSTEM_SCANCODE_33
|
||||
|
||||
# Key for selecting the fifth hotbar slot.
|
||||
keymap_slot5 (Hotbar slot 5) key SYSTEM_SCANCODE_34
|
||||
|
||||
# Key for selecting the sixth hotbar slot.
|
||||
keymap_slot6 (Hotbar slot 6) key SYSTEM_SCANCODE_35
|
||||
|
||||
# Key for selecting the seventh hotbar slot.
|
||||
keymap_slot7 (Hotbar slot 7) key SYSTEM_SCANCODE_36
|
||||
|
||||
# Key for selecting the eighth hotbar slot.
|
||||
keymap_slot8 (Hotbar slot 8) key SYSTEM_SCANCODE_37
|
||||
|
||||
# Key for selecting the ninth hotbar slot.
|
||||
keymap_slot9 (Hotbar slot 9) key SYSTEM_SCANCODE_38
|
||||
|
||||
# Key for selecting the tenth hotbar slot.
|
||||
keymap_slot10 (Hotbar slot 10) key SYSTEM_SCANCODE_39
|
||||
|
||||
# Key for selecting the 11th hotbar slot.
|
||||
keymap_slot11 (Hotbar slot 11) key
|
||||
|
||||
# Key for selecting the 12th hotbar slot.
|
||||
keymap_slot12 (Hotbar slot 12) key
|
||||
|
||||
# Key for selecting the 13th hotbar slot.
|
||||
keymap_slot13 (Hotbar slot 13) key
|
||||
|
||||
# Key for selecting the 14th hotbar slot.
|
||||
keymap_slot14 (Hotbar slot 14) key
|
||||
|
||||
# Key for selecting the 15th hotbar slot.
|
||||
keymap_slot15 (Hotbar slot 15) key
|
||||
|
||||
# Key for selecting the 16th hotbar slot.
|
||||
keymap_slot16 (Hotbar slot 16) key
|
||||
|
||||
# Key for selecting the 17th hotbar slot.
|
||||
keymap_slot17 (Hotbar slot 17) key
|
||||
|
||||
# Key for selecting the 18th hotbar slot.
|
||||
keymap_slot18 (Hotbar slot 18) key
|
||||
|
||||
# Key for selecting the 19th hotbar slot.
|
||||
keymap_slot19 (Hotbar slot 19) key
|
||||
|
||||
# Key for selecting the 20th hotbar slot.
|
||||
keymap_slot20 (Hotbar slot 20) key
|
||||
|
||||
# Key for selecting the 21st hotbar slot.
|
||||
keymap_slot21 (Hotbar slot 21) key
|
||||
|
||||
# Key for selecting the 22nd hotbar slot.
|
||||
keymap_slot22 (Hotbar slot 22) key
|
||||
|
||||
# Key for selecting the 23rd hotbar slot.
|
||||
keymap_slot23 (Hotbar slot 23) key
|
||||
|
||||
# Key for selecting the 24th hotbar slot.
|
||||
keymap_slot24 (Hotbar slot 24) key
|
||||
|
||||
# Key for selecting the 25th hotbar slot.
|
||||
keymap_slot25 (Hotbar slot 25) key
|
||||
|
||||
# Key for selecting the 26th hotbar slot.
|
||||
keymap_slot26 (Hotbar slot 26) key
|
||||
|
||||
# Key for selecting the 27th hotbar slot.
|
||||
keymap_slot27 (Hotbar slot 27) key
|
||||
|
||||
# Key for selecting the 28th hotbar slot.
|
||||
keymap_slot28 (Hotbar slot 28) key
|
||||
|
||||
# Key for selecting the 29th hotbar slot.
|
||||
keymap_slot29 (Hotbar slot 29) key
|
||||
|
||||
# Key for selecting the 30th hotbar slot.
|
||||
keymap_slot30 (Hotbar slot 30) key
|
||||
|
||||
# Key for selecting the 31st hotbar slot.
|
||||
keymap_slot31 (Hotbar slot 31) key
|
||||
|
||||
# Key for selecting the 32nd hotbar slot.
|
||||
keymap_slot32 (Hotbar slot 32) key
|
||||
|
||||
[*Touchscreen]
|
||||
|
@ -2074,11 +2013,11 @@ transparency_sorting_group_by_buffers (Transparency Sorting Group by Buffers) bo
|
|||
cloud_radius (Cloud radius) int 12 8 62
|
||||
|
||||
# Delay between mesh updates on the client in ms. Increasing this will slow
|
||||
# down the rate of mesh updates, thus reducing jitter on slower clients.
|
||||
mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50
|
||||
# down the rate of mesh updates, which can help reduce jitter.
|
||||
mesh_generation_interval (Mapblock mesh generation delay) int 0 0 25
|
||||
|
||||
# Number of threads to use for mesh generation.
|
||||
# Value of 0 (default) will let Luanti autodetect the number of available threads.
|
||||
# Value of 0 (default) will let Luanti automatically choose the number of threads.
|
||||
mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8
|
||||
|
||||
# All mesh buffers with less than this number of vertices will be merged
|
||||
|
|
60
doc/README.md
Normal file
60
doc/README.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Documentation
|
||||
|
||||
This directory contains mostly reference documentation for the Luanti engine.
|
||||
For a less prescriptive and more guiding documentation, also look at:
|
||||
https://docs.luanti.org
|
||||
|
||||
Note that the inner workings of the engine are not well documented. It's most
|
||||
often better to read the code.
|
||||
|
||||
Markdown files are written in a way that they can also be read in plain text.
|
||||
When modifying, please keep it that way!
|
||||
|
||||
Here is a list with descriptions of relevant files:
|
||||
|
||||
## Server Modding
|
||||
|
||||
- [lua_api.md](lua_api.md): Server Modding API reference. (Not only the Lua part,
|
||||
but also file structure and everything else.)
|
||||
If you want to make a mod or game, look here!
|
||||
A rendered version is also available at <https://api.luanti.org/>.
|
||||
- [builtin_entities.md](builtin_entities.md): Doc for entities predefined by the
|
||||
engine (in builtin), i.e. dropped items and falling nodes.
|
||||
|
||||
## Client-Side Content
|
||||
|
||||
- [texture_packs.md](texture_packs.md): Layout and description of Luanti's
|
||||
texture packs structure and configuration.
|
||||
- [client_lua_api.md](client_lua_api.md): Client-Provided Client-Side Modding
|
||||
(CPCSM) API reference.
|
||||
|
||||
## Mainmenu scripting
|
||||
|
||||
- [menu_lua_api.md](menu_lua_api.md): API reference for the mainmenu scripting
|
||||
environment.
|
||||
- [fst_api.txt](fst_api.txt): Formspec Toolkit API, included in builtin for the
|
||||
main menu.
|
||||
|
||||
## Formats and Protocols
|
||||
|
||||
- [world_format.md](world_format.md): Structure of Luanti world directories and
|
||||
format of the files therein.
|
||||
Note: If you want to write your own deserializer, it will be easier to read
|
||||
the `serialize()` and `deSerialize()` functions of the various structures in
|
||||
C++, e.g. `MapBlock::deSerialize()`.
|
||||
- [protocol.txt](protocol.txt): *Rough* outline of Luanti's network protocol.
|
||||
|
||||
## Misc.
|
||||
|
||||
- [compiling/](compiling/): Compilation instructions, and options.
|
||||
- [ides/](ides/): Instructions for configuring certain IDEs for engine development.
|
||||
- [developing/](developing/): Information about Luanti development.
|
||||
Note: [developing/profiling.md](developing/profiling.md) can be useful for
|
||||
modders and server owners!
|
||||
- [android.md](android.md): Android quirks.
|
||||
- [direction.md](direction.md): Information related to the future direction of
|
||||
Luanti. Commonly referred to as the roadmap document.
|
||||
- [breakages.md](breakages.md): List of planned breakages for the next major
|
||||
release, i.e. 6.0.0.
|
||||
- [docker_server.md](docker_server.md): Information about our Docker server
|
||||
images in the ghcr.
|
|
@ -42,7 +42,7 @@ configuration file can usually be found at:
|
|||
* After 5.4.2:
|
||||
* `/sdcard/Android/data/net.minetest.minetest/` or `/storage/emulated/0/Android/data/net.minetest.minetest/` if stored on the device
|
||||
* `/storage/emulated/(varying folder name)/Android/data/net.minetest.minetest/` if stored on the SD card
|
||||
* [Learn more about Android directory](https://wiki.luanti.org/Accessing_Android_Data_Directory)
|
||||
* [Learn more about Android directory](https://docs.luanti.org/for-players/mobile/)
|
||||
|
||||
## Useful settings
|
||||
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
# Developer documentation
|
||||
|
||||
## Wiki
|
||||
## Luanti Documentation
|
||||
|
||||
Some important development docs are found in the wiki: https://dev.luanti.org/
|
||||
Some important development docs are found on the docs site: https://docs.luanti.org/
|
||||
|
||||
Notable pages:
|
||||
|
||||
- [Releasing Luanti](https://dev.luanti.org/Releasing_Luanti)
|
||||
- [Engine translations](https://dev.luanti.org/Translation#Maintaining_engine_translations)
|
||||
- [Changelog](https://dev.luanti.org/Changelog)
|
||||
- [Organisation](https://dev.luanti.org/Organisation)
|
||||
- [Code style guidelines](https://dev.luanti.org/Code_style_guidelines)
|
||||
- [Releasing Luanti](https://docs.luanti.org/for-engine-devs/releasing-luanti/)
|
||||
- [Engine translations](https://docs.luanti.org/for-creators/translation/)
|
||||
- [Changelog](https://docs.luanti.org/about/changelog/)
|
||||
- [Organisation](https://docs.luanti.org/for-engine-devs/organization/)
|
||||
- [Code style guidelines](https://docs.luanti.org/for-engine-devs/code-style-guidelines/)
|
||||
and [Lua code style guidelines](https://docs.luanti.org/for-engine-devs/lua-code-style-guidelines/)
|
||||
|
||||
## In this folder
|
||||
|
||||
- [Developing minetestserver with Docker](docker.md)
|
||||
- [Android tips & tricks](android.md)
|
||||
- [OS/library compatibility policy](os-compatibility.md)
|
||||
- [Miscellaneous](misc.md)
|
||||
- [docker.md](docker.md): Developing minetestserver with Docker
|
||||
- [android.md](android.md): Android tips & tricks
|
||||
- [os-compatibility.md](os-compatibility.md): OS/library compatibility policy
|
||||
- [profiling.md](profiling.md): Profiling instructions
|
||||
|
||||
## IRC
|
||||
|
||||
Oftentimes knowledge hasn't been written down (yet) and your best bet is to ask someone experienced and/or the core developers.
|
||||
|
||||
Feel free to join the [#minetest-dev IRC](https://wiki.luanti.org/IRC) and ask questions related to **engine development**.
|
||||
Feel free to join the [#luanti-dev IRC](https://docs.luanti.org/about/irc/) and ask questions related to **engine development**.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Miscellaneous
|
||||
# Profiling
|
||||
|
||||
## Profiling Luanti on Linux with perf
|
||||
|
|
@ -37,7 +37,7 @@ Examples include
|
|||
[general view distance](https://github.com/luanti-org/luanti/issues/7222).
|
||||
|
||||
This includes work on maintaining
|
||||
[our Irrlicht fork](https://github.com/minetest/irrlicht), and switching to
|
||||
[our Irrlicht fork](https://github.com/luanti-org/luanti/tree/master/irr), and switching to
|
||||
alternative libraries to replace Irrlicht functionality as needed
|
||||
|
||||
### 2.2 Internal code refactoring
|
||||
|
|
|
@ -3,8 +3,10 @@ Formspec toolkit api 0.0.3
|
|||
|
||||
Formspec toolkit is a set of functions to create basic ui elements.
|
||||
|
||||
You can find the files in builtin/fstk/.
|
||||
|
||||
File: fst/ui.lua
|
||||
|
||||
File: fstk/ui.lua
|
||||
----------------
|
||||
|
||||
ui.lua adds base ui interface to add additional components to.
|
||||
|
@ -25,7 +27,7 @@ ui.find_by_name(name) --> returns component or nil
|
|||
^ find a component within ui
|
||||
^ name: name of component to look for
|
||||
|
||||
File: fst/tabview.lua
|
||||
File: fstk/tabview.lua
|
||||
---------------------
|
||||
|
||||
tabview_create(name, size, tabheaderpos) --> returns tabview component
|
||||
|
@ -92,7 +94,7 @@ methods:
|
|||
* icon: path to icon
|
||||
* on_click(tabview): callback function
|
||||
|
||||
File: fst/dialog.lua
|
||||
File: fstk/dialog.lua
|
||||
---------------------
|
||||
Only one dialog can be shown at a time. If a dialog is closed it's parent is
|
||||
gonna be activated and shown again.
|
||||
|
@ -129,7 +131,7 @@ members:
|
|||
- parent
|
||||
^ parent component to return to on exit
|
||||
|
||||
File: fst/buttonbar.lua
|
||||
File: fstk/buttonbar.lua
|
||||
-----------------------
|
||||
|
||||
buttonbar_create(name, pos, size, bgcolor, cbf_buttonhandler)
|
||||
|
|
|
@ -10,7 +10,7 @@ safely without breaking backwards compatibility.
|
|||
|
||||
* More information at <http://www.luanti.org/>
|
||||
* Additional documentation: <https://docs.luanti.org/>
|
||||
* (Unofficial) Minetest Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
|
||||
* (Unofficial) Luanti Modding Book by rubenwardy: <https://rubenwardy.com/minetest_modding_book/>
|
||||
* Modding tools: <https://github.com/luanti-org/modtools>
|
||||
|
||||
Introduction
|
||||
|
@ -315,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.
|
||||
|
||||
|
@ -6478,6 +6481,11 @@ Environment access
|
|||
* `core.get_node_or_nil(pos)`
|
||||
* Same as `get_node` but returns `nil` for unloaded areas.
|
||||
* Note that even loaded areas can contain "ignore" nodes.
|
||||
* `core.get_node_raw(x, y, z)`
|
||||
* Same as `get_node` but a faster low-level API
|
||||
* Returns `content_id`, `param1`, `param2`, and `pos_ok`
|
||||
* The `content_id` can be mapped to a name using `core.get_name_from_content_id()`
|
||||
* If `pos_ok` is false, the area is unloaded and `content_id == core.CONTENT_IGNORE`
|
||||
* `core.get_node_light(pos[, timeofday])`
|
||||
* Gets the light value at the given position. Note that the light value
|
||||
"inside" the node at the given position is returned, so you usually want
|
||||
|
@ -6583,6 +6591,7 @@ Environment access
|
|||
* `core.get_value_noise(noiseparams)`
|
||||
* Return world-specific value noise.
|
||||
* The actual seed used is the noiseparams seed plus the world seed.
|
||||
* **Important**: Requires the mapgen environment to be initalized, do not use at load time.
|
||||
* `core.get_value_noise(seeddiff, octaves, persistence, spread)`
|
||||
* Deprecated: use `core.get_value_noise(noiseparams)` instead.
|
||||
* `core.get_perlin(noiseparams)`
|
||||
|
@ -6657,6 +6666,9 @@ Environment access
|
|||
of the *active* mapgen setting `"mapgen_limit"`.
|
||||
* `chunksize` is an optional number. If it is absent, its value is that
|
||||
of the *active* mapgen setting `"chunksize"`.
|
||||
* `core.get_mapgen_chunksize()`
|
||||
* Returns the currently active chunksize of the mapgen, as a vector.
|
||||
The size is specified in blocks.
|
||||
* `core.get_mapgen_setting(name)`
|
||||
* Gets the *active* mapgen setting (or nil if none exists) in string
|
||||
format with the following order of precedence:
|
||||
|
@ -6836,6 +6848,7 @@ You can find mod channels communication scheme in `doc/mod_channels.png`.
|
|||
* Server joins channel `channel_name`, and creates it if necessary. You
|
||||
should listen for incoming messages with
|
||||
`core.register_on_modchannel_message`
|
||||
* This returns a [ModChannel] object.
|
||||
|
||||
Inventory
|
||||
---------
|
||||
|
@ -6867,10 +6880,15 @@ Formspec
|
|||
* `core.show_formspec(playername, formname, formspec)`
|
||||
* `playername`: name of player to show formspec
|
||||
* `formname`: name passed to `on_player_receive_fields` callbacks.
|
||||
It should follow the `"modname:<whatever>"` naming convention.
|
||||
* `formname` must not be empty, unless you want to reshow
|
||||
the inventory formspec without updating it for future opens.
|
||||
* It should follow the `"modname:<whatever>"` naming convention.
|
||||
* If empty: Shows a custom, temporary inventory formspec.
|
||||
* An inventory formspec shown this way will also be updated if
|
||||
`ObjectRef:set_inventory_formspec` is called.
|
||||
* Use `ObjectRef:set_inventory_formspec` to change the player's
|
||||
inventory formspec for future opens.
|
||||
* Supported if server AND client are both of version >= 5.13.0.
|
||||
* `formspec`: formspec to display
|
||||
* See also: `core.register_on_player_receive_fields`
|
||||
* `core.close_formspec(playername, formname)`
|
||||
* `playername`: name of player to close formspec
|
||||
* `formname`: has to exactly match the one given in `show_formspec`, or the
|
||||
|
@ -8648,9 +8666,12 @@ child will follow movement and rotation of that bone.
|
|||
* Returns `nil` if no attribute found.
|
||||
* `get_meta()`: Returns metadata associated with the player (a PlayerMetaRef).
|
||||
* `set_inventory_formspec(formspec)`
|
||||
* Redefine player's inventory form
|
||||
* Should usually be called in `on_joinplayer`
|
||||
* Redefines the player's inventory formspec.
|
||||
* Should usually be called at least once in the `on_joinplayer` callback.
|
||||
* If `formspec` is `""`, the player's inventory is disabled.
|
||||
* If the inventory formspec is currently open on the client, it is
|
||||
updated immediately.
|
||||
* See also: `core.register_on_player_receive_fields`
|
||||
* `get_inventory_formspec()`: returns a formspec string
|
||||
* `set_formspec_prepend(formspec)`:
|
||||
* the formspec string will be added to every formspec shown to the user,
|
||||
|
@ -9270,6 +9291,8 @@ 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.
|
||||
|
||||
**Important**: These require the mapgen environment to be initalized, do not use at load time.
|
||||
|
||||
* `ValueNoise(noiseparams)`
|
||||
* `ValueNoise(seed, octaves, persistence, spread)` (deprecated)
|
||||
* `core.get_value_noise(noiseparams)`
|
||||
|
@ -9306,6 +9329,8 @@ 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.
|
||||
|
||||
**Important**: These require the mapgen environment to be initalized, do not use at load time.
|
||||
|
||||
### Methods
|
||||
|
||||
* `get_2d_map(pos)`: returns a `<size.x>` times `<size.y>` 2D array of 2D noise
|
||||
|
|
|
@ -3,7 +3,8 @@ Updated 2011-06-18
|
|||
|
||||
A custom protocol over UDP.
|
||||
Integers are big endian.
|
||||
Refer to connection.{h,cpp} for further reference.
|
||||
Refer to network/mtp/internal.h, network/networkprotocol.{h,cpp}, and
|
||||
server/clientiface.h for further reference.
|
||||
|
||||
Initialization:
|
||||
- A dummy reliable packet with peer_id=PEER_ID_INEXISTENT=0 is sent to the server:
|
||||
|
|
|
@ -401,7 +401,7 @@ See below for description.
|
|||
Luanti will correct lighting in the day light bank when the block at
|
||||
`(1, 0, 0)` is also loaded.
|
||||
|
||||
Timestamp and node ID mappings were introduced in map format version 29.
|
||||
Timestamp and node ID mappings come here if map format version >= 29.
|
||||
* `u32` timestamp
|
||||
* Timestamp when last saved, as seconds from starting the game.
|
||||
* `0xffffffff` = invalid/unknown timestamp, nothing should be done with the time
|
||||
|
@ -482,13 +482,7 @@ Timestamp and node ID mappings were introduced in map format version 29.
|
|||
* `s32` timeout * 1000
|
||||
* `s32` elapsed * 1000
|
||||
|
||||
* Since map format version 25:
|
||||
* `u8` length of the data of a single timer (always 2+4+4=10)
|
||||
* `u16` `num_of_timers`
|
||||
* foreach `num_of_timers`:
|
||||
* `u16` timer position (`(z*16*16 + y*16 + x)`)
|
||||
* `s32` timeout * 1000
|
||||
* `s32` elapsed * 1000
|
||||
* Map format version >= 25: see below
|
||||
|
||||
`u8` static object version:
|
||||
* Always 0
|
||||
|
@ -516,6 +510,14 @@ Before map format version 29:
|
|||
* `u16` `name_len`
|
||||
* `u8[name_len]` `name`
|
||||
|
||||
Since map format version 25, node timers come here:
|
||||
* `u8` length of the data of a single timer (always 2+4+4=10)
|
||||
* `u16` `num_of_timers`
|
||||
* foreach `num_of_timers`:
|
||||
* `u16` timer position (`(z*16*16 + y*16 + x)`)
|
||||
* `s32` timeout * 1000
|
||||
* `s32` elapsed * 1000
|
||||
|
||||
End of File (EOF).
|
||||
|
||||
# Format of Nodes
|
||||
|
|
|
@ -106,7 +106,7 @@ core.register_chatcommand("bench_bulk_set_node", {
|
|||
core.chat_send_player(name, "Warming up finished, now benchmarking ...")
|
||||
|
||||
local start_time = core.get_us_time()
|
||||
for i=1,#pos_list do
|
||||
for i = 1, #pos_list do
|
||||
core.set_node(pos_list[i], {name = "mapgen_stone"})
|
||||
end
|
||||
local middle_time = core.get_us_time()
|
||||
|
@ -116,6 +116,7 @@ core.register_chatcommand("bench_bulk_set_node", {
|
|||
((middle_time - start_time)) / 1000,
|
||||
((end_time - middle_time)) / 1000
|
||||
)
|
||||
print(msg)
|
||||
return true, msg
|
||||
end,
|
||||
})
|
||||
|
@ -129,16 +130,13 @@ core.register_chatcommand("bench_bulk_get_node", {
|
|||
return false, "No player."
|
||||
end
|
||||
local pos_list = get_positions_cube(player:get_pos())
|
||||
local dummy = 0
|
||||
local function bench()
|
||||
local start_time = core.get_us_time()
|
||||
for i=1,#pos_list do
|
||||
for i = 1, #pos_list do
|
||||
local n = core.get_node(pos_list[i])
|
||||
-- Make sure the name lookup is never optimized away.
|
||||
-- Table allocation might still be omitted. But only accessing
|
||||
-- the name of a node is a common pattern anyways.
|
||||
if n.name == "benchmarks:nonexistent_node" then
|
||||
error("should never happen")
|
||||
end
|
||||
-- Make sure the name lookup is not optimized away (can this even happen?)
|
||||
dummy = dummy + #n.name
|
||||
end
|
||||
return core.get_us_time() - start_time
|
||||
end
|
||||
|
@ -151,6 +149,115 @@ core.register_chatcommand("bench_bulk_get_node", {
|
|||
|
||||
local msg = string.format("Benchmark results: core.get_node loop 1: %.2f ms",
|
||||
result_us / 1000)
|
||||
print(msg)
|
||||
return true, msg
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("bench_bulk_get_node_raw", {
|
||||
params = "",
|
||||
description = "Benchmark: Bulk-get 99×99×99 nodes with raw API",
|
||||
func = function(name, param)
|
||||
local player = core.get_player_by_name(name)
|
||||
if not player then
|
||||
return false, "No player."
|
||||
end
|
||||
local pos_list = get_positions_cube(player:get_pos())
|
||||
local dummy = 0
|
||||
local function bench()
|
||||
local start_time = core.get_us_time()
|
||||
for i = 1, #pos_list do
|
||||
local pos_i = pos_list[i]
|
||||
local nid = core.get_node_raw(pos_i.x, pos_i.y, pos_i.z)
|
||||
-- Make sure the result is not optimized away
|
||||
dummy = dummy + nid
|
||||
end
|
||||
return core.get_us_time() - start_time
|
||||
end
|
||||
|
||||
core.chat_send_player(name, "Benchmarking core.get_node_raw. Warming up ...")
|
||||
bench()
|
||||
|
||||
core.chat_send_player(name, "Warming up finished, now benchmarking ...")
|
||||
local result_us = bench()
|
||||
|
||||
local msg = string.format("Benchmark results: core.get_node_raw loop 1: %.2f ms",
|
||||
result_us / 1000)
|
||||
print(msg)
|
||||
return true, msg
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("bench_bulk_get_node_raw2", {
|
||||
params = "",
|
||||
description = "Benchmark: Bulk-get 99×99×99 nodes with raw API and lookup names",
|
||||
func = function(name, param)
|
||||
local player = core.get_player_by_name(name)
|
||||
if not player then
|
||||
return false, "No player."
|
||||
end
|
||||
local pos_list = get_positions_cube(player:get_pos())
|
||||
local dummy = 0
|
||||
local function bench()
|
||||
local start_time = core.get_us_time()
|
||||
for i = 1, #pos_list do
|
||||
local pos_i = pos_list[i]
|
||||
local nid = core.get_node_raw(pos_i.x, pos_i.y, pos_i.z)
|
||||
local name = core.get_name_from_content_id(nid)
|
||||
-- Make sure the name lookup is not optimized away
|
||||
dummy = dummy + #name
|
||||
end
|
||||
return core.get_us_time() - start_time
|
||||
end
|
||||
|
||||
core.chat_send_player(name, "Benchmarking core.get_node_raw+get_name_from_content_id. Warming up ...")
|
||||
bench()
|
||||
|
||||
core.chat_send_player(name, "Warming up finished, now benchmarking ...")
|
||||
local result_us = bench()
|
||||
|
||||
local msg = string.format("Benchmark results: core.get_node_raw+get_name_from_content_id loop 1: %.2f ms",
|
||||
result_us / 1000)
|
||||
print(msg)
|
||||
return true, msg
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("bench_bulk_get_node_vm", {
|
||||
params = "",
|
||||
description = "Benchmark: Bulk-get 99×99×99 nodes with voxel manipulator",
|
||||
func = function(name, param)
|
||||
local player = core.get_player_by_name(name)
|
||||
if not player then
|
||||
return false, "No player."
|
||||
end
|
||||
local pos = player:get_pos()
|
||||
local dummy = 0
|
||||
local function bench()
|
||||
local start_time = core.get_us_time()
|
||||
local vm = core.get_voxel_manip(pos:offset(1,1,1), pos:offset(100,100,100))
|
||||
local data = vm:get_data()
|
||||
local mid_time = core.get_us_time()
|
||||
-- Note that the VManip will actually retrieve more than just the 100³ nodes
|
||||
-- and also we don't need to iterate pos_list here, so it's not an entirely
|
||||
-- fair comparison.
|
||||
for i = 1, 99*99*99 do
|
||||
local nid = data[i]
|
||||
-- Make sure the table lookup is not optimized away
|
||||
dummy = dummy + nid
|
||||
end
|
||||
return core.get_us_time() - start_time, mid_time - start_time
|
||||
end
|
||||
|
||||
core.chat_send_player(name, "Benchmarking core.get_voxel_vmanip+get_data+loop . Warming up ...")
|
||||
bench()
|
||||
|
||||
core.chat_send_player(name, "Warming up finished, now benchmarking ...")
|
||||
local result_us, get_data_us = bench()
|
||||
|
||||
local msg = string.format("Benchmark results: core.get_voxel_vmanip+get_data+loop loop 1: %.2f ms of which get_data() %.2f ms",
|
||||
result_us / 1000, get_data_us / 1000)
|
||||
print(msg)
|
||||
return true, msg
|
||||
end,
|
||||
})
|
||||
|
@ -174,7 +281,7 @@ core.register_chatcommand("bench_bulk_swap_node", {
|
|||
core.chat_send_player(name, "Warming up finished, now benchmarking ...")
|
||||
|
||||
local start_time = core.get_us_time()
|
||||
for i=1,#pos_list do
|
||||
for i = 1, #pos_list do
|
||||
core.swap_node(pos_list[i], {name = "mapgen_stone"})
|
||||
end
|
||||
local middle_time = core.get_us_time()
|
||||
|
@ -184,6 +291,7 @@ core.register_chatcommand("bench_bulk_swap_node", {
|
|||
((middle_time - start_time)) / 1000,
|
||||
((end_time - middle_time)) / 1000
|
||||
)
|
||||
print(msg)
|
||||
return true, msg
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -13,3 +13,9 @@ Jordach (CC BY-SA 3.0):
|
|||
Zeg9 (CC BY-SA 3.0):
|
||||
testentities_lava_flan.x
|
||||
testentities_lava_flan.png
|
||||
|
||||
"Cool Guy":
|
||||
|
||||
hecks (refer to irr/LICENSE):
|
||||
testentities_cool_guy.x
|
||||
testentities_cool_guy.png
|
||||
|
|
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
|
||||
|
|
|
@ -29,6 +29,6 @@ if core.ipc_cas("unittests:mg_once", nil, true) then
|
|||
end
|
||||
|
||||
core.register_on_generated(function(vm, pos1, pos2, blockseed)
|
||||
local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1
|
||||
assert(pos2:subtract(pos1) == vector.new(n, n, n))
|
||||
local cs = core.get_mapgen_chunksize()
|
||||
assert(pos2:subtract(pos1) == cs:multiply(core.MAP_BLOCKSIZE):subtract(1))
|
||||
end)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
IrrlichtMt version 1.9
|
||||
======================
|
||||
|
||||
IrrlichtMt is the 3D engine of [Minetest](https://github.com/minetest).
|
||||
IrrlichtMt is the 3D engine of [Luanti](https://github.com/luanti-org).
|
||||
It is based on the [Irrlicht Engine](https://irrlicht.sourceforge.io/) but is now developed independently.
|
||||
It is intentionally not compatible to upstream and is planned to be eventually absorbed into Minetest.
|
||||
It is intentionally not compatible to upstream and is planned to be eventually absorbed into Luanti.
|
||||
|
||||
Build
|
||||
-----
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -37,9 +44,8 @@ public:
|
|||
//! constructor
|
||||
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;
|
||||
|
@ -64,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;
|
||||
|
@ -89,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
|
||||
|
@ -140,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
|
||||
|
@ -236,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;
|
||||
}
|
||||
|
||||
|
@ -288,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() {
|
||||
|
@ -309,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;
|
||||
|
@ -329,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,
|
||||
|
@ -376,25 +392,25 @@ 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;
|
||||
};
|
||||
|
|
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -539,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,
|
||||
|
@ -584,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();
|
||||
}
|
||||
|
@ -642,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 = ([&]() {
|
||||
|
@ -663,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"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "CXMeshFileLoader.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "Transform.h"
|
||||
#include "os.h"
|
||||
|
||||
#include "fast_atof.h"
|
||||
|
@ -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")) {
|
||||
|
|
|
@ -158,10 +158,10 @@
|
|||
|
||||
<url type="homepage">https://www.luanti.org</url>
|
||||
<url type="bugtracker">https://www.luanti.org/get-involved/#reporting-issues</url>
|
||||
<url type="translate">https://dev.luanti.org/Translation/</url>
|
||||
<url type="translate">https://docs.luanti.org/for-creators/translation/</url>
|
||||
<url type="donation">https://www.luanti.org/get-involved/#donate</url>
|
||||
<url type="faq">https://wiki.luanti.org/FAQ</url>
|
||||
<url type="help">https://wiki.luanti.org</url>
|
||||
<url type="faq">https://docs.luanti.org/about/faq</url>
|
||||
<url type="help">https://docs.luanti.org</url>
|
||||
<url type="vcs-browser">https://github.com/luanti-org/luanti</url>
|
||||
<url type="contribute">https://www.luanti.org/get-involved</url>
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<meta charset="utf-8">
|
||||
<title>Luanti API documentation</title>
|
||||
|
||||
<link rel="canonical" href="https://api.minetest.netURL">
|
||||
<meta http-equiv="refresh" content="0; url=https://api.minetest.netURL">
|
||||
<link rel="canonical" href="https://api.luanti.orgURL">
|
||||
<meta http-equiv="refresh" content="0; url=https://api.luanti.orgURL">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Redirecting…</h1>
|
||||
<a href="https://api.minetest.netURL">Click here if you are not redirected.</a>
|
||||
<a href="https://api.luanti.orgURL">Click here if you are not redirected.</a>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -953,7 +953,7 @@ else()
|
|||
set(MATH_FLAGS "-fno-math-errno -fno-trapping-math -fno-signed-zeros")
|
||||
|
||||
# Enable SSE for floating point math on 32-bit x86 by default
|
||||
# reasoning see minetest issue #11810 and https://gcc.gnu.org/wiki/FloatingPointMath
|
||||
# reasoning see luanti issue #11810 and https://gcc.gnu.org/wiki/FloatingPointMath
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
check_c_source_compiles("#ifndef __i686__\n#error\n#endif\nint main(){}" IS_I686)
|
||||
if(IS_I686)
|
||||
|
|
|
@ -4,6 +4,7 @@ set (BENCHMARK_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_lighting.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_map.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapmodify.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_sha.cpp
|
||||
PARENT_SCOPE)
|
||||
|
|
179
src/benchmark/benchmark_map.cpp
Normal file
179
src/benchmark/benchmark_map.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
// Luanti
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
// Copyright (C) 2023 Minetest Authors
|
||||
|
||||
#include "catch.h"
|
||||
#include "dummygamedef.h"
|
||||
#include "map.h"
|
||||
#include "mapsector.h"
|
||||
|
||||
namespace {
|
||||
class TestMap : public Map {
|
||||
public:
|
||||
TestMap(IGameDef *gamedef) : Map(gamedef) {}
|
||||
|
||||
MapBlock * createBlockTest(v3s16 p)
|
||||
{
|
||||
v2s16 p2d(p.X, p.Z);
|
||||
s16 block_y = p.Y;
|
||||
|
||||
MapSector *sector = getSectorNoGenerate(p2d);
|
||||
if (!sector) {
|
||||
sector = new MapSector(this, p2d, m_gamedef);
|
||||
m_sectors[p2d] = sector;
|
||||
}
|
||||
|
||||
MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
|
||||
if (block)
|
||||
return block;
|
||||
|
||||
return sector->createBlankBlock(block_y);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
static void fillMap(TestMap &map, s16 n)
|
||||
{
|
||||
for(s16 z=0; z<n; z++)
|
||||
for(s16 y=0; y<n; y++)
|
||||
for(s16 x=0; x<n; x++) {
|
||||
v3s16 p(x,y,z);
|
||||
// create an empty block
|
||||
map.createBlockTest(p);
|
||||
}
|
||||
}
|
||||
|
||||
static int readBlocks(Map &map, s16 n)
|
||||
{
|
||||
int result = 0;
|
||||
for(s16 z=0; z<n; z++)
|
||||
for(s16 y=0; y<n; y++)
|
||||
for(s16 x=0; x<n; x++) {
|
||||
v3s16 p(x,y,z);
|
||||
MapBlock *block = map.getBlockNoCreateNoEx(p);
|
||||
if (block) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int readRandomBlocks(Map &map, s16 n)
|
||||
{
|
||||
int result = 0;
|
||||
for(int i=0; i < n * n * n; i++) {
|
||||
v3s16 p(myrand_range(0, n), myrand_range(0, n), myrand_range(0, n));
|
||||
MapBlock *block = map.getBlockNoCreateNoEx(p);
|
||||
if (block) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static int readYColumn(Map &map, s16 n)
|
||||
{
|
||||
int result = 0;
|
||||
for(s16 z=0; z<n; z++)
|
||||
for(s16 x=0; x<n; x++)
|
||||
for(s16 y=n-1; y>0; y--) {
|
||||
v3s16 p(x,y,z);
|
||||
MapBlock *block = map.getBlockNoCreateNoEx(p);
|
||||
if (block) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int readNodes(Map &map, s16 n)
|
||||
{
|
||||
int result = 0;
|
||||
for(s16 z=0; z<n * MAP_BLOCKSIZE; z+=8)
|
||||
for(s16 y=0; y<n * MAP_BLOCKSIZE; y+=4)
|
||||
for(s16 x=0; x<n * MAP_BLOCKSIZE; x++) {
|
||||
v3s16 p(x,y,z);
|
||||
MapNode n = map.getNode(p);
|
||||
if (n.getContent() != CONTENT_IGNORE)
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#define BENCH1(_count) \
|
||||
BENCHMARK_ADVANCED("create_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
meter.measure([&] { \
|
||||
fillMap(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readEmpty_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
meter.measure([&] { \
|
||||
return readBlocks(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readFilled_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
fillMap(map, _count); \
|
||||
meter.measure([&] { \
|
||||
return readBlocks(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readEmptyYCol_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
meter.measure([&] { \
|
||||
return readYColumn(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readFilledYCol_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
fillMap(map, _count); \
|
||||
meter.measure([&] { \
|
||||
return readYColumn(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readEmptyRandom_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
meter.measure([&] { \
|
||||
return readRandomBlocks(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readFilledRandom_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
fillMap(map, _count); \
|
||||
meter.measure([&] { \
|
||||
return readRandomBlocks(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readEmptyNodes_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
meter.measure([&] { \
|
||||
return readNodes(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
BENCHMARK_ADVANCED("readFilledNodes_" #_count)(Catch::Benchmark::Chronometer meter) { \
|
||||
DummyGameDef gamedef; \
|
||||
TestMap map(&gamedef); \
|
||||
fillMap(map, _count); \
|
||||
meter.measure([&] { \
|
||||
return readNodes(map, _count); \
|
||||
}); \
|
||||
}; \
|
||||
|
||||
|
||||
TEST_CASE("benchmark_map") {
|
||||
BENCH1(10)
|
||||
BENCH1(40) // 64.000 blocks
|
||||
}
|
|
@ -612,12 +612,7 @@ void Client::step(float dtime)
|
|||
if (minimap_mapblocks.empty())
|
||||
do_mapper_update = false;
|
||||
|
||||
bool is_empty = true;
|
||||
for (int l = 0; l < MAX_TILE_LAYERS; l++)
|
||||
if (r.mesh->getMesh(l)->getMeshBufferCount() != 0)
|
||||
is_empty = false;
|
||||
|
||||
if (is_empty) {
|
||||
if (r.mesh->isEmpty()) {
|
||||
delete r.mesh;
|
||||
} else {
|
||||
// Replace with the new mesh
|
||||
|
@ -1897,39 +1892,38 @@ float Client::getCurRate()
|
|||
|
||||
void Client::makeScreenshot()
|
||||
{
|
||||
irr::video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
|
||||
irr::video::IImage* const raw_image = driver->createScreenShot();
|
||||
video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
|
||||
video::IImage* const raw_image = driver->createScreenShot();
|
||||
|
||||
if (!raw_image)
|
||||
if (!raw_image) {
|
||||
errorstream << "Could not take screenshot" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
const struct tm tm = mt_localtime();
|
||||
|
||||
char timetstamp_c[64];
|
||||
strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm);
|
||||
char timestamp_c[64];
|
||||
strftime(timestamp_c, sizeof(timestamp_c), "%Y%m%d_%H%M%S", &tm);
|
||||
|
||||
std::string screenshot_dir;
|
||||
|
||||
if (fs::IsPathAbsolute(g_settings->get("screenshot_path")))
|
||||
screenshot_dir = g_settings->get("screenshot_path");
|
||||
else
|
||||
screenshot_dir = porting::path_user + DIR_DELIM + g_settings->get("screenshot_path");
|
||||
std::string screenshot_dir = g_settings->get("screenshot_path");
|
||||
if (!fs::IsPathAbsolute(screenshot_dir))
|
||||
screenshot_dir = porting::path_user + DIR_DELIM + screenshot_dir;
|
||||
|
||||
std::string filename_base = screenshot_dir
|
||||
+ DIR_DELIM
|
||||
+ std::string("screenshot_")
|
||||
+ std::string(timetstamp_c);
|
||||
+ timestamp_c;
|
||||
std::string filename_ext = "." + g_settings->get("screenshot_format");
|
||||
std::string filename;
|
||||
|
||||
// Create the directory if it doesn't already exist.
|
||||
// Otherwise, saving the screenshot would fail.
|
||||
fs::CreateDir(screenshot_dir);
|
||||
fs::CreateAllDirs(screenshot_dir);
|
||||
|
||||
u32 quality = (u32)g_settings->getS32("screenshot_quality");
|
||||
quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255;
|
||||
quality = rangelim(quality, 0, 100) / 100.0f * 255;
|
||||
|
||||
// Try to find a unique filename
|
||||
std::string filename;
|
||||
unsigned serial = 0;
|
||||
|
||||
while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
|
||||
|
@ -1940,23 +1934,23 @@ void Client::makeScreenshot()
|
|||
}
|
||||
|
||||
if (serial == SCREENSHOT_MAX_SERIAL_TRIES) {
|
||||
infostream << "Could not find suitable filename for screenshot" << std::endl;
|
||||
errorstream << "Could not find suitable filename for screenshot" << std::endl;
|
||||
} else {
|
||||
irr::video::IImage* const image =
|
||||
video::IImage* const image =
|
||||
driver->createImage(video::ECF_R8G8B8, raw_image->getDimension());
|
||||
|
||||
if (image) {
|
||||
raw_image->copyTo(image);
|
||||
|
||||
std::ostringstream sstr;
|
||||
std::string msg;
|
||||
if (driver->writeImageToFile(image, filename.c_str(), quality)) {
|
||||
sstr << "Saved screenshot to '" << filename << "'";
|
||||
msg = fmtgettext("Saved screenshot to \"%s\"", filename.c_str());
|
||||
} else {
|
||||
sstr << "Failed to save screenshot '" << filename << "'";
|
||||
msg = fmtgettext("Failed to save screenshot to \"%s\"", filename.c_str());
|
||||
}
|
||||
pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
|
||||
utf8_to_wide(sstr.str())));
|
||||
infostream << sstr.str() << std::endl;
|
||||
utf8_to_wide(msg)));
|
||||
infostream << msg << std::endl;
|
||||
image->drop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -540,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,
|
||||
|
|
|
@ -464,8 +464,6 @@ void MapblockMeshGenerator::drawSolidNode()
|
|||
for (auto &layer : tiles[face].layers) {
|
||||
if (backface_culling)
|
||||
layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
|
||||
layer.material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL;
|
||||
layer.material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL;
|
||||
}
|
||||
if (!data->m_smooth_lighting) {
|
||||
lights[face] = getFaceLight(cur_node.n, neighbor, nodedef);
|
||||
|
@ -474,7 +472,6 @@ void MapblockMeshGenerator::drawSolidNode()
|
|||
if (!faces)
|
||||
return;
|
||||
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));
|
||||
box.MinEdge += cur_node.origin;
|
||||
box.MaxEdge += cur_node.origin;
|
||||
|
@ -1775,6 +1772,7 @@ void MapblockMeshGenerator::errorUnknownDrawtype()
|
|||
|
||||
void MapblockMeshGenerator::drawNode()
|
||||
{
|
||||
cur_node.origin = intToFloat(cur_node.p, BS);
|
||||
switch (cur_node.f->drawtype) {
|
||||
case NDT_AIRLIKE: // Not drawn at all
|
||||
return;
|
||||
|
@ -1785,7 +1783,6 @@ void MapblockMeshGenerator::drawNode()
|
|||
default:
|
||||
break;
|
||||
}
|
||||
cur_node.origin = intToFloat(cur_node.p, BS);
|
||||
if (data->m_smooth_lighting) {
|
||||
getSmoothLightFrame();
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,7 @@ struct LightInfo {
|
|||
float light_night;
|
||||
float light_boosted;
|
||||
|
||||
LightPair getPair(float sunlight_boost = 0.0) const
|
||||
LightPair getPair(float sunlight_boost = 0.0f) const
|
||||
{
|
||||
return LightPair(
|
||||
(1 - sunlight_boost) * light_day
|
||||
|
|
|
@ -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,
|
||||
|
@ -1923,7 +1925,7 @@ void Game::processKeyInput()
|
|||
if (g_settings->getBool("continuous_forward"))
|
||||
toggleAutoforward();
|
||||
} else if (wasKeyDown(KeyType::INVENTORY)) {
|
||||
m_game_formspec.showPlayerInventory();
|
||||
m_game_formspec.showPlayerInventory(nullptr);
|
||||
} else if (input->cancelPressed()) {
|
||||
#ifdef __ANDROID__
|
||||
m_android_chat_open = false;
|
||||
|
@ -2712,11 +2714,16 @@ void Game::handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrienta
|
|||
|
||||
void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
|
||||
{
|
||||
m_game_formspec.showFormSpec(*event->show_formspec.formspec,
|
||||
*event->show_formspec.formname);
|
||||
auto &fs = event->show_formspec;
|
||||
|
||||
delete event->show_formspec.formspec;
|
||||
delete event->show_formspec.formname;
|
||||
if (fs.formname->empty() && !fs.formspec->empty()) {
|
||||
m_game_formspec.showPlayerInventory(fs.formspec);
|
||||
} else {
|
||||
m_game_formspec.showFormSpec(*fs.formspec, *fs.formname);
|
||||
}
|
||||
|
||||
delete fs.formspec;
|
||||
delete fs.formname;
|
||||
}
|
||||
|
||||
void Game::handleClientEvent_ShowCSMFormSpec(ClientEvent *event, CameraOrientation *cam)
|
||||
|
@ -4225,7 +4232,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,
|
||||
|
|
|
@ -178,6 +178,10 @@ public:
|
|||
const std::string &getForm() const
|
||||
{
|
||||
LocalPlayer *player = m_client->getEnv().getLocalPlayer();
|
||||
|
||||
if (!player->inventory_formspec_override.empty())
|
||||
return player->inventory_formspec_override;
|
||||
|
||||
return player->inventory_formspec;
|
||||
}
|
||||
|
||||
|
@ -304,7 +308,7 @@ void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &no
|
|||
m_formspec->setFormSpec(formspec, inventoryloc);
|
||||
}
|
||||
|
||||
void GameFormSpec::showPlayerInventory()
|
||||
void GameFormSpec::showPlayerInventory(const std::string *fs_override)
|
||||
{
|
||||
/*
|
||||
* Don't permit to open inventory is CAO or player doesn't exists.
|
||||
|
@ -317,28 +321,35 @@ void GameFormSpec::showPlayerInventory()
|
|||
|
||||
infostream << "Game: Launching inventory" << std::endl;
|
||||
|
||||
PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(m_client);
|
||||
auto fs_src = std::make_unique<PlayerInventoryFormSource>(m_client);
|
||||
|
||||
InventoryLocation inventoryloc;
|
||||
inventoryloc.setCurrentPlayer();
|
||||
|
||||
if (m_client->modsLoaded() && m_client->getScript()->on_inventory_open(m_client->getInventory(inventoryloc))) {
|
||||
delete fs_src;
|
||||
return;
|
||||
if (fs_override) {
|
||||
// Temporary overwrite for this specific formspec.
|
||||
player->inventory_formspec_override = *fs_override;
|
||||
} else {
|
||||
// Show the regular inventory formspec
|
||||
player->inventory_formspec_override.clear();
|
||||
}
|
||||
|
||||
if (fs_src->getForm().empty()) {
|
||||
delete fs_src;
|
||||
// If prevented by Client-Side Mods
|
||||
if (m_client->modsLoaded() && m_client->getScript()->on_inventory_open(m_client->getInventory(inventoryloc)))
|
||||
return;
|
||||
|
||||
// Empty formspec -> do not show.
|
||||
if (fs_src->getForm().empty())
|
||||
return;
|
||||
}
|
||||
|
||||
TextDest *txt_dst = new TextDestPlayerInventory(m_client);
|
||||
|
||||
GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(),
|
||||
&m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(),
|
||||
&m_input->joystick, fs_src.get(), txt_dst, m_client->getFormspecPrepend(),
|
||||
m_client->getSoundManager());
|
||||
|
||||
m_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
|
||||
fs_src.release(); // owned by GUIFormSpecMenu
|
||||
}
|
||||
|
||||
#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode)
|
||||
|
|
|
@ -34,7 +34,9 @@ struct GameFormSpec
|
|||
// Currently only used for the in-game settings menu.
|
||||
void showPauseMenuFormSpec(const std::string &formspec, const std::string &formname);
|
||||
void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos);
|
||||
void showPlayerInventory();
|
||||
/// If `!fs_override`: Uses `player->inventory_formspec`.
|
||||
/// If ` fs_override`: Uses a temporary formspec until an update is received.
|
||||
void showPlayerInventory(const std::string *fs_override);
|
||||
void showDeathFormspecLegacy();
|
||||
// Shows the hardcoded "main" pause menu.
|
||||
void showPauseMenu();
|
||||
|
|
|
@ -137,6 +137,7 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
|
|||
// This is separate from other keyboard handling so that it also works in menus.
|
||||
if (event.EventType == EET_KEY_INPUT_EVENT) {
|
||||
KeyPress keyCode(event.KeyInput);
|
||||
|
||||
if (keyCode == getKeySetting("keymap_fullscreen")) {
|
||||
if (event.KeyInput.PressedDown && !fullscreen_is_down) {
|
||||
IrrlichtDevice *device = RenderingEngine::get_raw_device();
|
||||
|
@ -150,8 +151,15 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
|
|||
}
|
||||
fullscreen_is_down = event.KeyInput.PressedDown;
|
||||
return true;
|
||||
} else if (keyCode == EscapeKey &&
|
||||
event.KeyInput.PressedDown && event.KeyInput.Shift) {
|
||||
|
||||
} else if (keyCode == getKeySetting("keymap_close_world")) {
|
||||
close_world_down = event.KeyInput.PressedDown;
|
||||
|
||||
} else if (keyCode == EscapeKey) {
|
||||
esc_down = event.KeyInput.PressedDown;
|
||||
}
|
||||
|
||||
if (esc_down && close_world_down) {
|
||||
g_gamecallback->disconnect();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -128,6 +128,9 @@ private:
|
|||
// Intentionally not reset by clearInput/releaseAllKeys.
|
||||
bool fullscreen_is_down = false;
|
||||
|
||||
bool close_world_down = false;
|
||||
bool esc_down = false;
|
||||
|
||||
PointerType last_pointer_type = PointerType::Mouse;
|
||||
};
|
||||
|
||||
|
|
|
@ -99,6 +99,8 @@ public:
|
|||
|
||||
std::string hotbar_image = "";
|
||||
std::string hotbar_selected_image = "";
|
||||
/// Temporary player inventory formspec. Empty value = feature inactive.
|
||||
std::string inventory_formspec_override;
|
||||
|
||||
video::SColor light_color = video::SColor(255, 255, 255, 255);
|
||||
|
||||
|
|
|
@ -51,15 +51,6 @@ void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
|
|||
m_vmanip.addArea(voxel_area);
|
||||
}
|
||||
|
||||
void MeshMakeData::fillBlockData(const v3s16 &bp, MapNode *data)
|
||||
{
|
||||
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
|
||||
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
|
||||
|
||||
v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
|
||||
m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
|
||||
}
|
||||
|
||||
void MeshMakeData::fillSingleNode(MapNode data, MapNode padding)
|
||||
{
|
||||
m_blockpos = {0, 0, 0};
|
||||
|
@ -626,7 +617,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data):
|
|||
if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
|
||||
MinimapMapblock *block = new MinimapMapblock;
|
||||
m_minimap_mapblocks[mesh_grid.getOffsetIndex(ofs)] = block;
|
||||
block->getMinimapNodes(&data->m_vmanip, p);
|
||||
block->getMinimapNodes(&data->m_vmanip, data->m_nodedef, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ struct MeshMakeData
|
|||
Copy block data manually (to allow optimizations by the caller)
|
||||
*/
|
||||
void fillBlockDataBegin(const v3s16 &blockpos);
|
||||
void fillBlockData(const v3s16 &bp, MapNode *data);
|
||||
|
||||
/*
|
||||
Prepare block data for rendering a single node located at (0,0,0).
|
||||
|
@ -212,6 +211,20 @@ public:
|
|||
return minimap_mapblocks;
|
||||
}
|
||||
|
||||
/// @return true if the mesh contains nothing to draw
|
||||
bool isEmpty() const
|
||||
{
|
||||
if (!m_transparent_triangles.empty())
|
||||
return false;
|
||||
for (auto &mesh : m_mesh) {
|
||||
for (u32 i = 0; i < mesh->getMeshBufferCount(); i++) {
|
||||
if (mesh->getMeshBuffer(i)->getIndexCount() != 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isAnimationForced() const
|
||||
{
|
||||
return m_animation_force_timer == 0;
|
||||
|
@ -242,7 +255,7 @@ public:
|
|||
/// get the list of transparent buffers
|
||||
const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
|
||||
{
|
||||
return this->m_transparent_buffers;
|
||||
return m_transparent_buffers;
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -10,10 +10,9 @@
|
|||
#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)
|
||||
|
@ -97,11 +96,8 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
|
|||
mesh->addMeshBuffer(buf);
|
||||
buf->drop();
|
||||
}
|
||||
|
||||
scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
|
||||
mesh->drop();
|
||||
scaleMesh(anim_mesh, scale); // also recalculates bounding box
|
||||
return anim_mesh;
|
||||
scaleMesh(mesh, scale); // also recalculates bounding box
|
||||
return mesh;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
|
|
|
@ -11,18 +11,6 @@
|
|||
#include "util/directiontables.h"
|
||||
#include "porting.h"
|
||||
|
||||
// Data placeholder used for copying from non-existent blocks
|
||||
static struct BlockPlaceholder {
|
||||
MapNode data[MapBlock::nodecount];
|
||||
|
||||
BlockPlaceholder()
|
||||
{
|
||||
for (std::size_t i = 0; i < MapBlock::nodecount; i++)
|
||||
data[i] = MapNode(CONTENT_IGNORE);
|
||||
}
|
||||
|
||||
} block_placeholder;
|
||||
|
||||
/*
|
||||
QueuedMeshUpdate
|
||||
*/
|
||||
|
@ -32,6 +20,38 @@ QueuedMeshUpdate::~QueuedMeshUpdate()
|
|||
delete data;
|
||||
}
|
||||
|
||||
void QueuedMeshUpdate::retrieveBlocks(Map *map, u16 cell_size)
|
||||
{
|
||||
const size_t total = (cell_size+2)*(cell_size+2)*(cell_size+2);
|
||||
if (map_blocks.empty())
|
||||
map_blocks.resize(total);
|
||||
else
|
||||
assert(map_blocks.size() == total); // must not change
|
||||
size_t i = 0;
|
||||
v3s16 pos;
|
||||
for (pos.X = p.X - 1; pos.X <= p.X + cell_size; pos.X++)
|
||||
for (pos.Z = p.Z - 1; pos.Z <= p.Z + cell_size; pos.Z++)
|
||||
for (pos.Y = p.Y - 1; pos.Y <= p.Y + cell_size; pos.Y++) {
|
||||
if (!map_blocks[i]) {
|
||||
MapBlock *block = map->getBlockNoCreateNoEx(pos);
|
||||
if (block) {
|
||||
block->refGrab();
|
||||
map_blocks[i] = block;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void QueuedMeshUpdate::dropBlocks()
|
||||
{
|
||||
for (auto *block : map_blocks) {
|
||||
if (block)
|
||||
block->refDrop();
|
||||
}
|
||||
map_blocks.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
MeshUpdateQueue
|
||||
*/
|
||||
|
@ -48,26 +68,28 @@ MeshUpdateQueue::~MeshUpdateQueue()
|
|||
MutexAutoLock lock(m_mutex);
|
||||
|
||||
for (QueuedMeshUpdate *q : m_queue) {
|
||||
for (auto block : q->map_blocks)
|
||||
if (block)
|
||||
block->refDrop();
|
||||
q->dropBlocks();
|
||||
delete q;
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent)
|
||||
bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server,
|
||||
bool urgent, bool from_neighbor)
|
||||
{
|
||||
// FIXME: with cell_size > 1 there isn't a "main block" and this check is
|
||||
// probably incorrect and broken
|
||||
MapBlock *main_block = map->getBlockNoCreateNoEx(p);
|
||||
if (!main_block)
|
||||
return false;
|
||||
|
||||
MutexAutoLock lock(m_mutex);
|
||||
|
||||
MeshGrid mesh_grid = m_client->getMeshGrid();
|
||||
|
||||
// Mesh is placed at the corner block of a chunk
|
||||
// (where all coordinate are divisible by the chunk size)
|
||||
v3s16 mesh_position(mesh_grid.getMeshPos(p));
|
||||
v3s16 mesh_position = mesh_grid.getMeshPos(p);
|
||||
|
||||
MutexAutoLock lock(m_mutex);
|
||||
|
||||
/*
|
||||
Mark the block as urgent if requested
|
||||
*/
|
||||
|
@ -80,44 +102,26 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
|
|||
*/
|
||||
for (QueuedMeshUpdate *q : m_queue) {
|
||||
if (q->p == mesh_position) {
|
||||
// NOTE: We are not adding a new position to the queue, thus
|
||||
// refcount_from_queue stays the same.
|
||||
if(ack_block_to_server)
|
||||
if (ack_block_to_server)
|
||||
q->ack_list.push_back(p);
|
||||
q->crack_level = m_client->getCrackLevel();
|
||||
q->crack_pos = m_client->getCrackPos();
|
||||
q->urgent |= urgent;
|
||||
v3s16 pos;
|
||||
int i = 0;
|
||||
for (pos.X = q->p.X - 1; pos.X <= q->p.X + mesh_grid.cell_size; pos.X++)
|
||||
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++)
|
||||
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) {
|
||||
if (!q->map_blocks[i]) {
|
||||
MapBlock *block = map->getBlockNoCreateNoEx(pos);
|
||||
if (block) {
|
||||
block->refGrab();
|
||||
q->map_blocks[i] = block;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
q->retrieveBlocks(map, mesh_grid.cell_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Make a list of blocks necessary for mesh generation and lock the blocks in memory.
|
||||
Air blocks won't suddenly become visible due to a neighbor update, so
|
||||
skip those.
|
||||
Note: this can be extended with more precise checks in the future
|
||||
*/
|
||||
std::vector<MapBlock *> map_blocks;
|
||||
map_blocks.reserve((mesh_grid.cell_size+2)*(mesh_grid.cell_size+2)*(mesh_grid.cell_size+2));
|
||||
v3s16 pos;
|
||||
for (pos.X = mesh_position.X - 1; pos.X <= mesh_position.X + mesh_grid.cell_size; pos.X++)
|
||||
for (pos.Z = mesh_position.Z - 1; pos.Z <= mesh_position.Z + mesh_grid.cell_size; pos.Z++)
|
||||
for (pos.Y = mesh_position.Y - 1; pos.Y <= mesh_position.Y + mesh_grid.cell_size; pos.Y++) {
|
||||
MapBlock *block = map->getBlockNoCreateNoEx(pos);
|
||||
map_blocks.push_back(block);
|
||||
if (block)
|
||||
block->refGrab();
|
||||
if (from_neighbor && mesh_grid.cell_size == 1 && main_block->isAir()) {
|
||||
assert(!ack_block_to_server);
|
||||
m_urgents.erase(mesh_position);
|
||||
g_profiler->add("MeshUpdateQueue: updates skipped", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -125,12 +129,12 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
|
|||
*/
|
||||
QueuedMeshUpdate *q = new QueuedMeshUpdate;
|
||||
q->p = mesh_position;
|
||||
if(ack_block_to_server)
|
||||
if (ack_block_to_server)
|
||||
q->ack_list.push_back(p);
|
||||
q->crack_level = m_client->getCrackLevel();
|
||||
q->crack_pos = m_client->getCrackPos();
|
||||
q->urgent = urgent;
|
||||
q->map_blocks = std::move(map_blocks);
|
||||
q->retrieveBlocks(map, mesh_grid.cell_size);
|
||||
m_queue.push_back(q);
|
||||
|
||||
return true;
|
||||
|
@ -188,7 +192,8 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
|
|||
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++)
|
||||
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) {
|
||||
MapBlock *block = q->map_blocks[i++];
|
||||
data->fillBlockData(pos, block ? block->getData() : block_placeholder.data);
|
||||
if (block)
|
||||
block->copyTo(data->m_vmanip);
|
||||
}
|
||||
|
||||
data->setCrack(q->crack_level, q->crack_pos);
|
||||
|
@ -205,20 +210,16 @@ MeshUpdateWorkerThread::MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *
|
|||
UpdateThread("Mesh"), m_client(client), m_queue_in(queue_in), m_manager(manager)
|
||||
{
|
||||
m_generation_interval = g_settings->getU16("mesh_generation_interval");
|
||||
m_generation_interval = rangelim(m_generation_interval, 0, 50);
|
||||
m_generation_interval = rangelim(m_generation_interval, 0, 25);
|
||||
}
|
||||
|
||||
void MeshUpdateWorkerThread::doUpdate()
|
||||
{
|
||||
QueuedMeshUpdate *q;
|
||||
while ((q = m_queue_in->pop())) {
|
||||
if (m_generation_interval)
|
||||
sleep_ms(m_generation_interval);
|
||||
|
||||
porting::TriggerMemoryTrim();
|
||||
|
||||
ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)");
|
||||
|
||||
// This generates the mesh:
|
||||
MapBlockMesh *mesh_new = new MapBlockMesh(m_client, q->data);
|
||||
|
||||
MeshUpdateResult r;
|
||||
|
@ -227,11 +228,19 @@ void MeshUpdateWorkerThread::doUpdate()
|
|||
r.solid_sides = get_solid_sides(q->data);
|
||||
r.ack_list = std::move(q->ack_list);
|
||||
r.urgent = q->urgent;
|
||||
r.map_blocks = q->map_blocks;
|
||||
r.map_blocks = std::move(q->map_blocks);
|
||||
|
||||
m_manager->putResult(r);
|
||||
m_queue_in->done(q->p);
|
||||
delete q;
|
||||
sp.stop();
|
||||
|
||||
porting::TriggerMemoryTrim();
|
||||
|
||||
// do this after we're done so the interval is enforced without
|
||||
// adding extra latency.
|
||||
if (m_generation_interval)
|
||||
sleep_ms(m_generation_interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,12 +253,12 @@ MeshUpdateManager::MeshUpdateManager(Client *client):
|
|||
{
|
||||
int number_of_threads = rangelim(g_settings->getS32("mesh_generation_threads"), 0, 8);
|
||||
|
||||
// Automatically use 33% of the system cores for mesh generation, max 4
|
||||
// Automatically use 25% of the system cores for mesh generation, max 3
|
||||
if (number_of_threads == 0)
|
||||
number_of_threads = MYMIN(4, Thread::getNumberOfProcessors() / 3);
|
||||
number_of_threads = std::min(3U, Thread::getNumberOfProcessors() / 4);
|
||||
|
||||
// use at least one thread
|
||||
number_of_threads = MYMAX(1, number_of_threads);
|
||||
number_of_threads = std::max(1, number_of_threads);
|
||||
infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl;
|
||||
|
||||
for (int i = 0; i < number_of_threads; i++)
|
||||
|
@ -262,7 +271,7 @@ void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
|
|||
static thread_local const bool many_neighbors =
|
||||
g_settings->getBool("smooth_lighting")
|
||||
&& !g_settings->getFlag("performance_tradeoffs");
|
||||
if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) {
|
||||
if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent, false)) {
|
||||
warningstream << "Update requested for non-existent block at "
|
||||
<< p << std::endl;
|
||||
return;
|
||||
|
@ -270,10 +279,10 @@ void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
|
|||
if (update_neighbors) {
|
||||
if (many_neighbors) {
|
||||
for (v3s16 dp : g_26dirs)
|
||||
m_queue_in.addBlock(map, p + dp, false, urgent);
|
||||
m_queue_in.addBlock(map, p + dp, false, urgent, true);
|
||||
} else {
|
||||
for (v3s16 dp : g_6dirs)
|
||||
m_queue_in.addBlock(map, p + dp, false, urgent);
|
||||
m_queue_in.addBlock(map, p + dp, false, urgent, true);
|
||||
}
|
||||
}
|
||||
deferUpdate();
|
||||
|
|
|
@ -22,11 +22,24 @@ struct QueuedMeshUpdate
|
|||
int crack_level = -1;
|
||||
v3s16 crack_pos;
|
||||
MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop()
|
||||
std::vector<MapBlock *> map_blocks;
|
||||
std::vector<MapBlock*> map_blocks;
|
||||
bool urgent = false;
|
||||
|
||||
QueuedMeshUpdate() = default;
|
||||
~QueuedMeshUpdate();
|
||||
|
||||
/**
|
||||
* Get blocks needed for this mesh update from the map.
|
||||
* Blocks that were already loaded are skipped.
|
||||
* @param map Map
|
||||
* @param cell_size mesh grid cell size
|
||||
*/
|
||||
void retrieveBlocks(Map *map, u16 cell_size);
|
||||
/**
|
||||
* Drop block references.
|
||||
* @note not done by destructor, since this is only safe on main thread
|
||||
*/
|
||||
void dropBlocks();
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -45,9 +58,16 @@ public:
|
|||
|
||||
~MeshUpdateQueue();
|
||||
|
||||
// Caches the block at p and its neighbors (if needed) and queues a mesh
|
||||
// update for the block at p
|
||||
bool addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
|
||||
/**
|
||||
* Caches the block at p and its neighbors (if needed) and queues a mesh
|
||||
* update for the block p.
|
||||
* @param map Map
|
||||
* @param p block position
|
||||
* @param ack_to_server Should be acked to server when done?
|
||||
* @param urget High-priority?
|
||||
* @param from_neighbor was this update only necessary due to a neighbor change?
|
||||
*/
|
||||
bool addBlock(Map *map, v3s16 p, bool ack_to_server, bool urgent, bool from_neighbor);
|
||||
|
||||
// Returned pointer must be deleted
|
||||
// Returns NULL if queue is empty
|
||||
|
@ -56,7 +76,7 @@ public:
|
|||
// Marks a position as finished, unblocking the next update
|
||||
void done(v3s16 pos);
|
||||
|
||||
u32 size()
|
||||
size_t size()
|
||||
{
|
||||
MutexAutoLock lock(m_mutex);
|
||||
return m_queue.size();
|
||||
|
@ -83,7 +103,7 @@ struct MeshUpdateResult
|
|||
u8 solid_sides;
|
||||
std::vector<v3s16> ack_list;
|
||||
bool urgent = false;
|
||||
std::vector<MapBlock *> map_blocks;
|
||||
std::vector<MapBlock*> map_blocks;
|
||||
|
||||
MeshUpdateResult() = default;
|
||||
};
|
||||
|
@ -117,6 +137,7 @@ public:
|
|||
void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent,
|
||||
bool update_neighbors = false);
|
||||
void putResult(const MeshUpdateResult &r);
|
||||
/// @note caller needs to refDrop() the affected map_blocks
|
||||
bool getNextResult(MeshUpdateResult &r);
|
||||
|
||||
|
||||
|
|
|
@ -713,9 +713,8 @@ void Minimap::updateActiveMarkers()
|
|||
//// MinimapMapblock
|
||||
////
|
||||
|
||||
void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos)
|
||||
void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const NodeDefManager *nodedef, const v3s16 &pos)
|
||||
{
|
||||
|
||||
for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
|
||||
for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
|
||||
s16 air_count = 0;
|
||||
|
@ -725,11 +724,12 @@ void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos
|
|||
for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) {
|
||||
v3s16 p(x, y, z);
|
||||
MapNode n = vmanip->getNodeNoEx(pos + p);
|
||||
if (!surface_found && n.getContent() != CONTENT_AIR) {
|
||||
const ContentFeatures &f = nodedef->get(n);
|
||||
if (!surface_found && f.drawtype != NDT_AIRLIKE) {
|
||||
mmpixel->height = y;
|
||||
mmpixel->n = n;
|
||||
surface_found = true;
|
||||
} else if (n.getContent() == CONTENT_AIR) {
|
||||
} else if (f.drawtype == NDT_AIRLIKE) {
|
||||
air_count++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ struct MinimapPixel {
|
|||
};
|
||||
|
||||
struct MinimapMapblock {
|
||||
void getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos);
|
||||
void getMinimapNodes(VoxelManipulator *vmanip, const NodeDefManager *nodedef, const v3s16 &pos);
|
||||
|
||||
MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE];
|
||||
};
|
||||
|
|
|
@ -187,6 +187,7 @@ void set_default_settings()
|
|||
USEKEY2("keymap_fullscreen", "SYSTEM_SCANCODE_68", "KEY_F11");
|
||||
USEKEY2("keymap_increase_viewing_range_min", "SYSTEM_SCANCODE_46", "+");
|
||||
USEKEY2("keymap_decrease_viewing_range_min", "SYSTEM_SCANCODE_45", "-");
|
||||
settings->setDefault("keymap_close_world", "");
|
||||
USEKEY2("keymap_slot1", "SYSTEM_SCANCODE_30", "KEY_KEY_1");
|
||||
USEKEY2("keymap_slot2", "SYSTEM_SCANCODE_31", "KEY_KEY_2");
|
||||
USEKEY2("keymap_slot3", "SYSTEM_SCANCODE_32", "KEY_KEY_3");
|
||||
|
|
|
@ -31,9 +31,11 @@ public:
|
|||
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++)
|
||||
data[i] = n;
|
||||
for (s16 zn=0; zn < MAP_BLOCKSIZE; zn++)
|
||||
for (s16 yn=0; yn < MAP_BLOCKSIZE; yn++)
|
||||
for (s16 xn=0; xn < MAP_BLOCKSIZE; xn++) {
|
||||
block->setNodeNoCheck(xn, yn, zn, n);
|
||||
}
|
||||
block->expireIsAirCache();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ inline struct tm mt_localtime()
|
|||
#endif
|
||||
});
|
||||
|
||||
struct tm ret;
|
||||
struct tm ret{};
|
||||
time_t t = time(NULL);
|
||||
// TODO we should check if the function returns NULL, which would mean error
|
||||
#ifdef _WIN32
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#include "client/sound/sound_openal.h"
|
||||
#endif
|
||||
|
||||
#include <csignal>
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
void TextDestGuiEngine::gotText(const StringMap &fields)
|
||||
|
@ -104,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),
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "util/enriched_string.h"
|
||||
#include "translation.h"
|
||||
|
||||
#include <csignal>
|
||||
|
||||
/******************************************************************************/
|
||||
/* Structs and macros */
|
||||
/******************************************************************************/
|
||||
|
@ -124,7 +126,7 @@ public:
|
|||
RenderingEngine *rendering_engine,
|
||||
IMenuManager *menumgr,
|
||||
MainMenuData *data,
|
||||
bool &kill);
|
||||
volatile std::sig_atomic_t &kill);
|
||||
|
||||
/** default destructor */
|
||||
virtual ~GUIEngine();
|
||||
|
@ -193,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;
|
||||
|
|
|
@ -2852,12 +2852,13 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element)
|
|||
e->enableContinuousRotation(inf_rotation);
|
||||
e->enableMouseControl(mousectrl);
|
||||
|
||||
s32 frame_loop_begin = 0;
|
||||
s32 frame_loop_end = 0x7FFFFFFF;
|
||||
f32 frame_loop_begin = 0;
|
||||
// This will be clamped to the animation duration.
|
||||
f32 frame_loop_end = std::numeric_limits<f32>::infinity();
|
||||
|
||||
if (frame_loop.size() == 2) {
|
||||
frame_loop_begin = stoi(frame_loop[0]);
|
||||
frame_loop_end = stoi(frame_loop[1]);
|
||||
frame_loop_begin = stof(frame_loop[0]);
|
||||
frame_loop_end = stof(frame_loop[1]);
|
||||
}
|
||||
|
||||
e->setFrameLoop(frame_loop_begin, frame_loop_end);
|
||||
|
@ -4068,6 +4069,31 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
|||
{
|
||||
if (event.EventType==EET_KEY_INPUT_EVENT) {
|
||||
KeyPress kp(event.KeyInput);
|
||||
// Ctrl (+ Shift) + Tab: Select the (previous or) next tab of a TabControl instance.
|
||||
bool shift = event.KeyInput.Shift;
|
||||
bool ctrl = event.KeyInput.Control;
|
||||
if (event.KeyInput.PressedDown && (event.KeyInput.Key == KEY_TAB && ctrl)) {
|
||||
// Try to find a tab control among our elements
|
||||
for (const FieldSpec &s : m_fields) {
|
||||
if (s.ftype != f_TabHeader)
|
||||
continue;
|
||||
|
||||
IGUIElement *element = getElementFromId(s.fid, true);
|
||||
if (!element || element->getType() != gui::EGUIET_TAB_CONTROL)
|
||||
continue;
|
||||
|
||||
gui::IGUITabControl *tabs = static_cast<gui::IGUITabControl *>(element);
|
||||
s32 num_tabs = tabs->getTabCount();
|
||||
if (num_tabs <= 1)
|
||||
continue;
|
||||
|
||||
s32 active = tabs->getActiveTab();
|
||||
// Shift: Previous tab, No shift: Next tab
|
||||
active = (active + (shift ? -1 : 1) + num_tabs) % num_tabs;
|
||||
tabs->setActiveTab(active);
|
||||
return true; // handled
|
||||
}
|
||||
}
|
||||
if (event.KeyInput.PressedDown && (
|
||||
(kp == EscapeKey) ||
|
||||
((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
|
||||
|
|
|
@ -205,6 +205,9 @@ bool GUIInventoryList::OnEvent(const SEvent &event)
|
|||
|
||||
bool ret = hovered->OnEvent(event);
|
||||
|
||||
// Set visible again *after* processing the event. Otherwise, hovered could
|
||||
// be another GUIInventoryList, which will call this one again, resulting in
|
||||
// an infinite loop.
|
||||
IsVisible = was_visible;
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -831,8 +831,10 @@ bool GUITable::OnEvent(const SEvent &event)
|
|||
return true;
|
||||
}
|
||||
else if (event.KeyInput.Key == KEY_ESCAPE ||
|
||||
event.KeyInput.Key == KEY_SPACE) {
|
||||
event.KeyInput.Key == KEY_SPACE ||
|
||||
(event.KeyInput.Key == KEY_TAB && event.KeyInput.Control)) {
|
||||
// pass to parent
|
||||
return IGUIElement::OnEvent(event);
|
||||
}
|
||||
else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
|
||||
// change selection based on text as it is typed
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include "gamedef.h"
|
||||
#include "irrlicht_changes/printing.h"
|
||||
#include "log.h"
|
||||
#include "nameidmapping.h"
|
||||
#include "content_mapnode.h" // For legacy name-id mapping
|
||||
#include "content_nodemeta.h" // For legacy deserialization
|
||||
#include "serialization.h"
|
||||
|
@ -258,7 +257,7 @@ void MapBlock::expireIsAirCache()
|
|||
// Renumbers the content IDs (starting at 0 and incrementing)
|
||||
// Note that there's no technical reason why we *have to* renumber the IDs,
|
||||
// but we do it anyway as it also helps compressability.
|
||||
static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
||||
void MapBlock::getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
||||
const NodeDefManager *nodedef)
|
||||
{
|
||||
IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance();
|
||||
|
@ -288,7 +287,7 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
|||
// Correct ids in the block to match nodedef based on names.
|
||||
// Unknown ones are added to nodedef.
|
||||
// Will not update itself to match id-name pairs in nodedef.
|
||||
static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
|
||||
void MapBlock::correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
|
||||
IGameDef *gamedef)
|
||||
{
|
||||
const NodeDefManager *nodedef = gamedef->ndef();
|
||||
|
|
|
@ -21,6 +21,7 @@ class NodeMetadataList;
|
|||
class IGameDef;
|
||||
class MapBlockMesh;
|
||||
class VoxelManipulator;
|
||||
class NameIdMapping;
|
||||
|
||||
#define BLOCK_TIMESTAMP_UNDEFINED 0xffffffff
|
||||
|
||||
|
@ -80,11 +81,6 @@ public:
|
|||
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE);
|
||||
}
|
||||
|
||||
MapNode* getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
////
|
||||
//// Modification tracking methods
|
||||
////
|
||||
|
@ -427,18 +423,23 @@ public:
|
|||
// clearObject and return removed objects count
|
||||
u32 clearObjects();
|
||||
|
||||
private:
|
||||
static const u32 ystride = MAP_BLOCKSIZE;
|
||||
static const u32 zstride = MAP_BLOCKSIZE * MAP_BLOCKSIZE;
|
||||
|
||||
static const u32 nodecount = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
|
||||
|
||||
private:
|
||||
/*
|
||||
Private methods
|
||||
*/
|
||||
|
||||
void deSerialize_pre22(std::istream &is, u8 version, bool disk);
|
||||
|
||||
static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
||||
const NodeDefManager *nodedef);
|
||||
static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
|
||||
IGameDef *gamedef);
|
||||
|
||||
/*
|
||||
* PLEASE NOTE: When adding something here be mindful of position and size
|
||||
* of member variables! This is also the reason for the weird public-private
|
||||
|
|
|
@ -902,6 +902,7 @@ void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt)
|
|||
|
||||
// Store formspec in LocalPlayer
|
||||
player->inventory_formspec = pkt->readLongString();
|
||||
player->inventory_formspec_override.clear();
|
||||
}
|
||||
|
||||
void Client::handleCommand_DetachedInventory(NetworkPacket* pkt)
|
||||
|
|
|
@ -178,6 +178,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
|
|||
{ "TOCLIENT_STOP_SOUND", 0, true }, // 0x40
|
||||
{ "TOCLIENT_PRIVILEGES", 0, true }, // 0x41
|
||||
{ "TOCLIENT_INVENTORY_FORMSPEC", 0, true }, // 0x42
|
||||
// ^ `channel` MUST be the same as TOCLIENT_SHOW_FORMSPEC
|
||||
{ "TOCLIENT_DETACHED_INVENTORY", 0, true }, // 0x43
|
||||
{ "TOCLIENT_SHOW_FORMSPEC", 0, true }, // 0x44
|
||||
{ "TOCLIENT_MOVEMENT", 0, true }, // 0x45
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include "nodedef.h"
|
||||
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "itemdef.h"
|
||||
#if CHECK_CLIENT_BUILD()
|
||||
#include "client/mesh.h"
|
||||
|
@ -964,13 +963,6 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
|
|||
// Note: By freshly reading, we get an unencumbered mesh.
|
||||
if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
|
||||
bool apply_bs = false;
|
||||
// For frame-animated meshes, always get the first frame,
|
||||
// which holds a model for which we can eventually get the static pose.
|
||||
while (auto *src_meshes = dynamic_cast<scene::SAnimatedMesh *>(src_mesh)) {
|
||||
src_mesh = src_meshes->getMesh(0.0f);
|
||||
src_mesh->grab();
|
||||
src_meshes->drop();
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -17,7 +17,7 @@ ScopeProfiler::ScopeProfiler(Profiler *profiler, const std::string &name,
|
|||
m_time1 = porting::getTime(prec);
|
||||
}
|
||||
|
||||
ScopeProfiler::~ScopeProfiler()
|
||||
void ScopeProfiler::stop() noexcept
|
||||
{
|
||||
if (!m_profiler)
|
||||
return;
|
||||
|
@ -38,6 +38,8 @@ ScopeProfiler::~ScopeProfiler()
|
|||
m_profiler->max(m_name, duration);
|
||||
break;
|
||||
}
|
||||
|
||||
m_profiler = nullptr; // don't stop a second time
|
||||
}
|
||||
|
||||
Profiler::Profiler()
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "threading/mutex_auto_lock.h"
|
||||
#include "util/timetaker.h"
|
||||
#include "util/numeric.h" // paging()
|
||||
/* FIXME: ^ move this to the .cpp file, it's not needed here */
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
// Global profiler
|
||||
class Profiler;
|
||||
|
@ -108,7 +110,12 @@ public:
|
|||
ScopeProfiler(Profiler *profiler, const std::string &name,
|
||||
ScopeProfilerType type = SPT_ADD,
|
||||
TimePrecision precision = PRECISION_MILLI);
|
||||
~ScopeProfiler();
|
||||
inline ~ScopeProfiler() { stop(); }
|
||||
|
||||
// End profiled scope early
|
||||
void stop() noexcept;
|
||||
|
||||
DISABLE_CLASS_COPY(ScopeProfiler)
|
||||
|
||||
private:
|
||||
Profiler *m_profiler = nullptr;
|
||||
|
|
|
@ -121,6 +121,8 @@ public:
|
|||
u16 protocol_version = 0;
|
||||
u16 formspec_version = 0;
|
||||
|
||||
bool inventory_formspec_overridden = false;
|
||||
|
||||
/// returns PEER_ID_INEXISTENT when PlayerSAO is not ready
|
||||
session_t getPeerId() const { return m_peer_id; }
|
||||
|
||||
|
|
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