diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 5c27183668..7d33ddade0 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -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/)*
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 14d63f4fb3..88026cca00 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -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: |
diff --git a/README.md b/README.md
index 6abd6d22c9..9e5e7e7c40 100644
--- a/README.md
+++ b/README.md
@@ -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/)
diff --git a/builtin/common/settings/components.lua b/builtin/common/settings/components.lua
index 99fb0cd76c..38af1b0862 100644
--- a/builtin/common/settings/components.lua
+++ b/builtin/common/settings/components.lua
@@ -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"),
diff --git a/builtin/common/settings/generate_from_settingtypes.lua b/builtin/common/settings/generate_from_settingtypes.lua
index 13b3035cef..1c1535a171 100644
--- a/builtin/common/settings/generate_from_settingtypes.lua
+++ b/builtin/common/settings/generate_from_settingtypes.lua
@@ -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/
]]
diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua
index 44d2ab3d89..31b104ed30 100644
--- a/builtin/fstk/ui.lua
+++ b/builtin/fstk/ui.lua
@@ -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
diff --git a/builtin/game/item.lua b/builtin/game/item.lua
index 4d68f11360..3e810c3f06 100644
--- a/builtin/game/item.lua
+++ b/builtin/game/item.lua
@@ -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
diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua
index 500fb3f6c2..1103f42a75 100644
--- a/builtin/mainmenu/content/dlg_package.lua
+++ b/builtin/mainmenu/content/dlg_package.lua
@@ -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 = "" .. core.hypertext_escape(package.short_description) .. "\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 .. ""
+ if i ~= #screenshots then
+ hypertext = hypertext .. ""
+ 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 .. "" .. label .. ""
+ 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("\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 .. ""
- if i ~= #info.screenshots then
- hypertext = hypertext .. ""
- 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 .. "" .. core.hypertext_escape(label) .. ""
- 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(".
+- [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.
diff --git a/doc/android.md b/doc/android.md
index 353a7d1c81..f51bce3ee9 100644
--- a/doc/android.md
+++ b/doc/android.md
@@ -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
diff --git a/doc/developing/README.md b/doc/developing/README.md
index 419e41edd8..a2d19cd9a7 100644
--- a/doc/developing/README.md
+++ b/doc/developing/README.md
@@ -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**.
diff --git a/doc/developing/misc.md b/doc/developing/profiling.md
similarity index 99%
rename from doc/developing/misc.md
rename to doc/developing/profiling.md
index 5992946cb2..2c334183ba 100644
--- a/doc/developing/misc.md
+++ b/doc/developing/profiling.md
@@ -1,4 +1,4 @@
-# Miscellaneous
+# Profiling
## Profiling Luanti on Linux with perf
diff --git a/doc/direction.md b/doc/direction.md
index bd70003d1a..4119f7d8fc 100644
--- a/doc/direction.md
+++ b/doc/direction.md
@@ -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
diff --git a/doc/fst_api.txt b/doc/fst_api.txt
index c12093d91c..9170ecf8eb 100644
--- a/doc/fst_api.txt
+++ b/doc/fst_api.txt
@@ -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)
diff --git a/doc/lua_api.md b/doc/lua_api.md
index a7219e2020..3d8dda5b24 100644
--- a/doc/lua_api.md
+++ b/doc/lua_api.md
@@ -10,7 +10,7 @@ safely without breaking backwards compatibility.
* More information at
* Additional documentation:
-* (Unofficial) Minetest Modding Book by rubenwardy:
+* (Unofficial) Luanti Modding Book by rubenwardy:
* Modding tools:
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:"` 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:"` 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 `` times `` 2D array of 2D noise
diff --git a/doc/protocol.txt b/doc/protocol.txt
index b0d4a78f76..7aafb0de91 100644
--- a/doc/protocol.txt
+++ b/doc/protocol.txt
@@ -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:
diff --git a/doc/world_format.md b/doc/world_format.md
index c519361d99..119114c7c8 100644
--- a/doc/world_format.md
+++ b/doc/world_format.md
@@ -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
diff --git a/games/devtest/mods/benchmarks/init.lua b/games/devtest/mods/benchmarks/init.lua
index 8f6bb1ee4a..9397f427d0 100644
--- a/games/devtest/mods/benchmarks/init.lua
+++ b/games/devtest/mods/benchmarks/init.lua
@@ -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,
})
diff --git a/games/devtest/mods/testentities/models/LICENSE.txt b/games/devtest/mods/testentities/models/LICENSE.txt
index 19ffffc5cc..6ec445db69 100644
--- a/games/devtest/mods/testentities/models/LICENSE.txt
+++ b/games/devtest/mods/testentities/models/LICENSE.txt
@@ -12,4 +12,10 @@ Jordach (CC BY-SA 3.0):
Zeg9 (CC BY-SA 3.0):
testentities_lava_flan.x
- testentities_lava_flan.png
\ No newline at end of file
+ testentities_lava_flan.png
+
+"Cool Guy":
+
+hecks (refer to irr/LICENSE):
+ testentities_cool_guy.x
+ testentities_cool_guy.png
diff --git a/games/devtest/mods/testentities/models/testentities_cool_guy.png b/games/devtest/mods/testentities/models/testentities_cool_guy.png
new file mode 100644
index 0000000000..84cc12e442
Binary files /dev/null and b/games/devtest/mods/testentities/models/testentities_cool_guy.png differ
diff --git a/games/devtest/mods/testentities/models/testentities_cool_guy.x b/games/devtest/mods/testentities/models/testentities_cool_guy.x
new file mode 100755
index 0000000000..e806d8315d
--- /dev/null
+++ b/games/devtest/mods/testentities/models/testentities_cool_guy.x
@@ -0,0 +1,2 @@
+xof 0303txt 0032
+AnimationSet{Animation{{Armature}AnimationKey{0;2;0;4;1,0,0,0;;,29;4;1,0,0,0;;;}AnimationKey{2;2;0;3;0,0,0;;,29;3;0,0,0;;;}}Animation{{Armature_knee_r}AnimationKey{0;16;0;4;0.864183,0.503177,0,0;;,1;4;0.829812,0.558043,0,0;;,3;4;0.708698,0.705512,0,0;;,5;4;0.589108,0.808054,0,0;;,7;4;0.593659,0.804717,0,0;;,9;4;0.748627,0.662991,0,0;;,11;4;0.910305,0.413938,0,0;;,13;4;0.975925,0.218107,0,0;;,15;4;0.981302,0.192476,0,0;;,17;4;0.975476,0.220108,0,0;;,19;4;0.963662,0.267124,0,0;;,21;4;0.945893,0.324478,0,0;;,23;4;0.923816,0.382838,0,0;;,25;4;0.901205,0.433394,0,0;;,27;4;0.883429,0.468566,0,0;;,29;4;0.876305,0.481757,0,0;;;}AnimationKey{2;2;0;3;0,0,1.10139;;,29;3;0,0,1.10139;;;}}Animation{{Armature_elbow_r}AnimationKey{0;16;0;4;0.756295,0.004619,-0.619265,0.210967;;,1;4;0.771977,0.005599,-0.60257,0.202311;;,3;4;0.825501,0.009164,-0.538259,0.169533;;,5;4;0.891859,0.014253,-0.436142,0.119019;;,7;4;0.949154,0.019821,-0.308768,0.058108;;,9;4;0.983251,0.024703,-0.18057,-0.001258;;,11;4;0.995416,0.028143,-0.07812,-0.047458;;,13;4;0.996672,0.02991,-0.020368,-0.073041;;,15;4;0.996672,0.02991,-0.020368,-0.073041;;,17;4;0.995416,0.028143,-0.07812,-0.047458;;,19;4;0.983251,0.024703,-0.18057,-0.001258;;,21;4;0.949154,0.019821,-0.308768,0.058108;;,23;4;0.891859,0.014253,-0.436142,0.119019;;,25;4;0.825501,0.009164,-0.538259,0.169533;;,27;4;0.771977,0.005599,-0.60257,0.202311;;,29;4;0.750682,0.004275,-0.625038,0.213976;;;}AnimationKey{2;2;0;3;0,0,0.754892;;,29;3;0,0,0.754892;;;}}Animation{{Armature_arm_r}AnimationKey{0;16;0;4;0.28219,0.629905,0.723388,-0.017285;;,1;4;0.277641,0.632543,0.722699,-0.022614;;,3;4;0.261375,0.641615,0.719924,-0.041507;;,5;4;0.238321,0.653533,0.715186,-0.067874;;,7;4;0.212026,0.665838,0.708676,-0.097381;;,9;4;0.186345,0.676585,0.701229,-0.125643;;,11;4;0.165298,0.684491,0.694351,-0.14841;;,13;4;0.152894,0.688778,0.68998,-0.161665;;,15;4;0.152894,0.688779,0.68998,-0.161665;;,17;4;0.165298,0.684491,0.694351,-0.14841;;,19;4;0.186345,0.676585,0.701229,-0.125643;;,21;4;0.212026,0.665838,0.708676,-0.097381;;,23;4;0.238321,0.653533,0.715186,-0.067874;;,25;4;0.261375,0.641615,0.719924,-0.041507;;,27;4;0.277641,0.632543,0.722699,-0.022614;;,29;4;0.283802,0.628959,0.723623,-0.015394;;;}AnimationKey{2;2;0;3;-0.545315,0,1;;,29;3;-0.545315,0,1;;;}}Animation{{Armature_knee_l}AnimationKey{0;16;0;4;0.981896,0.189423,0,0;;,1;4;0.9814,0.191974,0,0;;,3;4;0.979127,0.203251,0,0;;,5;4;0.974526,0.224276,0,0;;,7;4;0.96645,0.256853,0,0;;,9;4;0.953088,0.302692,0,0;;,11;4;0.931731,0.36315,0,0;;,13;4;0.898645,0.438676,0,0;;,15;4;0.848226,0.529634,0,0;;,17;4;0.773692,0.633562,0,0;;,19;4;0.689831,0.72397,0,0;;,21;4;0.629304,0.777159,0,0;;,23;4;0.648685,0.761057,0,0;;,25;4;0.812268,0.583284,0,0;;,27;4;0.948066,0.318074,0,0;;,29;4;0.982049,0.188624,0,0;;;}AnimationKey{2;2;0;3;0,0,1.10139;;,29;3;0,0,1.10139;;;}}Animation{{Armature_Bone_007}AnimationKey{0;16;0;4;0.993671,-0.112331,0,0;;,1;4;0.994784,-0.102002,0,0;;,3;4;0.997507,-0.070564,0,0;;,5;4;0.999237,-0.039056,0,0;;,7;4;0.999694,-0.024737,0,0;;,9;4;0.999079,-0.042907,0,0;;,11;4;0.99677,-0.080308,0,0;;,13;4;0.993798,-0.111199,0,0;;,15;4;0.993599,-0.112965,0,0;;,17;4;0.995813,-0.091409,0,0;;,19;4;0.998181,-0.060285,0,0;;,21;4;0.999479,-0.032286,0,0;;,23;4;0.999797,-0.020142,0,0;;,25;4;0.998983,-0.045097,0,0;;,27;4;0.995813,-0.091409,0,0;;,29;4;0.993221,-0.116243,0,0;;;}AnimationKey{2;2;0;3;0,0,1.221802;;,29;3;0,0,1.221802;;;}}Animation{{Armature_elbow_l}AnimationKey{0;16;0;4;0.995195,-0.034868,-0.015799,-0.090119;;,1;4;0.993465,-0.046368,-0.030155,-0.099838;;,3;4;0.983557,-0.0879,-0.082099,-0.134715;;,5;4;0.959324,-0.146904,-0.156177,-0.183648;;,7;4;0.917546,-0.212233,-0.238611,-0.236921;;,9;4;0.864109,-0.271657,-0.314022,-0.284443;;,11;4;0.813172,-0.315829,-0.370387,-0.319087;;,13;4;0.781004,-0.339668,-0.400938,-0.337501;;,15;4;0.781004,-0.339668,-0.400938,-0.337501;;,17;4;0.813172,-0.315829,-0.370387,-0.319087;;,19;4;0.864109,-0.271657,-0.314022,-0.284443;;,21;4;0.917546,-0.212233,-0.238611,-0.236921;;,23;4;0.959324,-0.146904,-0.156177,-0.183648;;,25;4;0.983557,-0.0879,-0.082099,-0.134715;;,27;4;0.993465,-0.046368,-0.030155,-0.099838;;,29;4;0.995701,-0.030812,-0.010739,-0.086685;;;}AnimationKey{2;2;0;3;0,0,0.754892;;,29;3;0,0,0.754892;;;}}Animation{{Armature_body}AnimationKey{0;16;0;4;-0,0,0.601298,0.799025;;,1;4;-0,0,0.608144,0.793827;;,3;4;-0,0,0.627465,0.778645;;,5;4;-0,0,0.643183,0.765712;;,7;4;-0,0,0.643755,0.765231;;,9;4;-0,0,0.631076,0.775721;;,11;4;-0,0,0.613775,0.789481;;,13;4;-0,0,0.6007,0.799474;;,15;4;-0,0,0.601488,0.798882;;,17;4;-0,0,0.619499,0.784997;;,19;4;-0,0,0.643196,0.765702;;,21;4;-0,0,0.660441,0.750878;;,23;4;-0,0,0.659666,0.751559;;,25;4;-0,0,0.638264,0.769817;;,27;4;-0,0,0.611752,0.791049;;,29;4;-0,0,0.598631,0.801025;;;}AnimationKey{2;2;0;3;0,2.580534,0;;,29;3;0,2.571201,0;;;}}Animation{{Armature_leg_l}AnimationKey{0;16;0;4;0.390287,0.920693,0,0;;,1;4;0.362565,0.931959,0,0;;,3;4;0.266163,0.963928,0,0;;,5;4;0.138294,0.990391,0,0;;,7;4;0.012725,0.999919,0,0;;,9;4;-0.090194,0.995924,0,0;;,11;4;-0.162502,0.986708,0,0;;,13;4;-0.201466,0.979496,0,0;;,15;4;-0.185641,0.982618,0,0;;,17;4;-0.013697,0.999906,0,0;;,19;4;0.24238,0.970181,0,0;;,21;4;0.417271,0.908782,0,0;;,23;4;0.439308,0.898336,0,0;;,25;4;0.424255,0.905543,0,0;;,27;4;0.407664,0.913132,0,0;;,29;4;0.400263,0.9164,0,0;;;}AnimationKey{2;2;0;3;0.246294,0,-0.171352;;,29;3;0.246294,0,-0.171351;;;}}Animation{{Armature_leg_r}AnimationKey{0;16;0;4;0.174933,-0.98458,0,0;;,1;4;0.082829,-0.996564,0,0;;,3;4;-0.21147,-0.977384,0,0;;,5;4;-0.442802,-0.89662,0,0;;,7;4;-0.47604,-0.879424,0,0;;,9;4;-0.47279,-0.881175,0,0;;,11;4;-0.459567,-0.888143,0,0;;,13;4;-0.427425,-0.904051,0,0;;,15;4;-0.361724,-0.932285,0,0;;,17;4;-0.251362,-0.967893,0,0;;,19;4;-0.114531,-0.99342,0,0;;,21;4;0.021053,-0.999778,0,0;;,23;4;0.12473,-0.992191,0,0;;,25;4;0.181473,-0.983396,0,0;;,27;4;0.204037,-0.978963,0,0;;,29;4;0.208187,-0.978089,0,0;;;}AnimationKey{2;2;0;3;-0.246294,0,-0.171352;;,29;3;-0.246294,0,-0.171351;;;}}Animation{{Armature_arm_l}AnimationKey{0;16;0;4;0.200754,-0.659656,-0.716264,-0.107316;;,1;4;0.192268,-0.660735,-0.716526,-0.114246;;,3;4;0.161871,-0.663925,-0.716753,-0.138802;;,5;4;0.118745,-0.666682,-0.715211,-0.17294;;,7;4;0.069733,-0.667364,-0.710872,-0.210767;;,9;4;0.022313,-0.665594,-0.704111,-0.246404;;,11;4;-0.016046,-0.662426,-0.696821,-0.274543;;,13;4;-0.038374,-0.659874,-0.691824,-0.290643;;,15;4;-0.038373,-0.659874,-0.691824,-0.290643;;,17;4;-0.016044,-0.662427,-0.696822,-0.274543;;,19;4;0.022312,-0.665594,-0.70411,-0.246404;;,21;4;0.069733,-0.667365,-0.710872,-0.210767;;,23;4;0.118745,-0.666682,-0.715211,-0.17294;;,25;4;0.161871,-0.663925,-0.716753,-0.138802;;,27;4;0.192268,-0.660735,-0.716526,-0.114246;;,29;4;0.203757,-0.659255,-0.716151,-0.104856;;;}AnimationKey{2;2;0;3;0.545315,0,1;;,29;3;0.545315,0,1;;;}}}Frame Root{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;;}Frame Armature{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;;}Frame Armature_body{FrameTransformMatrix{-1,0,0,0,0,0,1,0,0,1,0,0,0,2.571201,0,1;;}Frame Armature_arm_r{FrameTransformMatrix{-0.047733,0.997488,-0.05233,0,0.901521,0.020464,-0.432251,0,-0.430095,-0.067809,-0.900233,0,-0.545315,0,1,1;;}Frame Armature_elbow_r{FrameTransformMatrix{0.987983,0.151721,-0.029519,0,-0.153228,0.986478,-0.058162,0,0.020295,0.061987,0.997871,0,0,0,0.754892,1;;}}}Frame Armature_arm_l{FrameTransformMatrix{-0.047732,0.994072,-0.097683,0,0.901521,0.084983,0.424309,0,0.430095,-0.067809,-0.900233,0,0.545315,0,1,1;;}Frame Armature_elbow_l{FrameTransformMatrix{0.984741,0.173286,-0.016044,0,-0.171963,0.983073,0.063221,0,0.026727,-0.059497,0.99787,0,0,0,0.754892,1;;}}}Frame Armature_leg_l{FrameTransformMatrix{1,0,0,0,0,-0.998426,-0.056453,0,0,0.056453,-0.998405,0,0.246294,0,-0.171351,1;;}Frame Armature_knee_l{FrameTransformMatrix{1,0,0,0,0,0.993861,-0.110639,0,0,0.110639,0.993861,0,0,0,1.10139,1;;}}}Frame Armature_leg_r{FrameTransformMatrix{1,0,0,0,0,-0.998426,-0.056453,0,0,0.056453,-0.998405,0,-0.246294,0,-0.171351,1;;}Frame Armature_knee_r{FrameTransformMatrix{1,0,0,0,0,0.993861,-0.110639,0,0,0.110639,0.993861,0,0,0,1.10139,1;;}}}Frame Armature_Bone_007{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,1.221802,1;;}}}Frame cool_dude{FrameTransformMatrix{-1,0,0,0,0,1,0,0,0,0,-1,0,0,0,0,1;;}Mesh{272;0;2.440814;0.219926;,0;3.688199;0.219926;,0.466212;3.688199;0.219926;,0.466212;2.440814;0.219926;,0.466212;2.440814;0.219926;,0.466212;3.688199;0.219926;,0.466212;3.688199;-0.219926;,0.466212;2.440814;-0.219926;,0;2.440814;0.219926;,0.466212;2.440814;0.219926;,0.466212;2.440814;-0.219926;,0;2.440814;-0.219926;,0.055633;1.27575;-0.190081;,0.055633;2.35741;-0.190081;,0.055633;2.35741;0.190081;,0.055633;1.27575;0.190081;,0.055633;1.27575;0.190081;,0.055633;2.35741;0.190081;,0.43017;2.35741;0.190081;,0.43017;1.27575;0.190081;,0.43017;1.27575;0.190081;,0.43017;2.35741;0.190081;,0.43017;2.35741;-0.190081;,0.43017;1.27575;-0.190081;,0.43017;1.27575;-0.190081;,0.43017;2.35741;-0.190081;,0.055633;2.35741;-0.190081;,0.055633;1.27575;-0.190081;,0.055633;1.27575;0.190081;,0.43017;1.27575;0.190081;,0.43017;1.27575;-0.190081;,0.055633;1.27575;-0.190081;,0.43017;2.35741;0.190081;,0.055633;2.35741;0.190081;,0.055633;2.35741;-0.190081;,0.43017;2.35741;-0.190081;,0.466212;3.688199;0.219926;,0;3.688199;0.219926;,0;3.688199;-0.219926;,0.466212;3.688199;-0.219926;,0.466212;2.440814;-0.219926;,0.466212;3.688199;-0.219926;,0;3.688199;-0.219926;,0;2.440814;-0.219926;,0.769341;2.834949;-0.041122;,0.440953;3.555781;-0.041122;,0.440953;3.555781;0.207294;,0.769341;2.834949;0.207294;,0.769341;2.834949;0.207294;,0.440953;3.555781;0.207294;,0.616273;3.635651;0.207294;,0.944661;2.914819;0.207294;,0.944661;2.914819;0.207294;,0.616273;3.635651;0.207294;,0.616273;3.635651;-0.041122;,0.944661;2.914819;-0.041122;,0.944661;2.914819;-0.041122;,0.616273;3.635651;-0.041122;,0.440953;3.555781;-0.041122;,0.769341;2.834949;-0.041122;,0.769341;2.834949;0.207294;,0.944661;2.914819;0.207294;,0.944661;2.914819;-0.041122;,0.769341;2.834949;-0.041122;,0.616273;3.635651;0.207294;,0.440953;3.555781;0.207294;,0.440953;3.555781;-0.041122;,0.616273;3.635651;-0.041122;,1.104504;2.080977;-0.086788;,0.776116;2.801809;-0.086788;,0.776116;2.801809;0.161627;,1.104504;2.080977;0.161627;,1.104504;2.080977;0.161627;,0.776116;2.801809;0.161627;,0.951436;2.881679;0.161627;,1.279824;2.160847;0.161627;,1.279824;2.160847;0.161627;,0.951436;2.881679;0.161627;,0.951436;2.881679;-0.086788;,1.279824;2.160847;-0.086788;,1.279824;2.160847;-0.086788;,0.951436;2.881679;-0.086788;,0.776116;2.801809;-0.086788;,1.104504;2.080977;-0.086788;,1.104504;2.080977;0.161627;,1.279824;2.160847;0.161627;,1.279824;2.160847;-0.086788;,1.104504;2.080977;-0.086788;,0.951436;2.881679;0.161627;,0.776116;2.801809;0.161627;,0.776116;2.801809;-0.086788;,0.951436;2.881679;-0.086788;,0.055633;0.093601;-0.190081;,0.055633;1.205294;-0.190081;,0.055633;1.205294;0.190081;,0.055633;0.093601;0.190081;,0.055633;0.093601;0.190081;,0.055633;1.205294;0.190081;,0.43017;1.205294;0.190081;,0.43017;0.093601;0.190081;,0.43017;0.093601;0.190081;,0.43017;1.205294;0.190081;,0.43017;1.205294;-0.190081;,0.43017;0.093601;-0.190081;,0.43017;0.093601;-0.190081;,0.43017;1.205294;-0.190081;,0.055633;1.205294;-0.190081;,0.055633;0.093601;-0.190081;,0.055633;0.093601;0.190081;,0.43017;0.093601;0.190081;,0.43017;0.093601;-0.190081;,0.055633;0.093601;-0.190081;,0.43017;1.205294;0.190081;,0.055633;1.205294;0.190081;,0.055633;1.205294;-0.190081;,0.43017;1.205294;-0.190081;,0;3.790919;0.428464;,0;4.579204;0.428464;,0.43344;4.560537;0.409797;,0.43344;3.809586;0.409797;,0.43344;3.809586;0.409797;,0.43344;4.560537;0.409797;,0.43344;4.560537;-0.284975;,0.43344;3.809586;-0.284975;,0;3.790919;0.428464;,0.43344;3.809586;0.409797;,0.43344;3.809586;-0.284975;,0;3.790919;-0.303642;,0.43344;4.560537;0.409797;,0;4.579204;0.428464;,0;4.579204;-0.303642;,0.43344;4.560537;-0.284975;,0.43344;3.809586;-0.284975;,0.43344;4.560537;-0.284975;,0;4.579204;-0.303642;,0;3.790919;-0.303642;,0;2.440814;0.219926;,-0.466212;2.440814;0.219926;,-0.466212;3.688199;0.219926;,0;3.688199;0.219926;,-0.466212;2.440814;0.219926;,-0.466212;2.440814;-0.219926;,-0.466212;3.688199;-0.219926;,-0.466212;3.688199;0.219926;,0;2.440814;0.219926;,0;2.440814;-0.219926;,-0.466212;2.440814;-0.219926;,-0.466212;2.440814;0.219926;,-0.055633;1.27575;-0.190081;,-0.055633;1.27575;0.190081;,-0.055633;2.35741;0.190081;,-0.055633;2.35741;-0.190081;,-0.055633;1.27575;0.190081;,-0.43017;1.27575;0.190081;,-0.43017;2.35741;0.190081;,-0.055633;2.35741;0.190081;,-0.43017;1.27575;0.190081;,-0.43017;1.27575;-0.190081;,-0.43017;2.35741;-0.190081;,-0.43017;2.35741;0.190081;,-0.43017;1.27575;-0.190081;,-0.055633;1.27575;-0.190081;,-0.055633;2.35741;-0.190081;,-0.43017;2.35741;-0.190081;,-0.055633;1.27575;0.190081;,-0.055633;1.27575;-0.190081;,-0.43017;1.27575;-0.190081;,-0.43017;1.27575;0.190081;,-0.43017;2.35741;0.190081;,-0.43017;2.35741;-0.190081;,-0.055633;2.35741;-0.190081;,-0.055633;2.35741;0.190081;,-0.466212;3.688199;0.219926;,-0.466212;3.688199;-0.219926;,0;3.688199;-0.219926;,0;3.688199;0.219926;,-0.466212;2.440814;-0.219926;,0;2.440814;-0.219926;,0;3.688199;-0.219926;,-0.466212;3.688199;-0.219926;,-0.769341;2.834949;-0.041122;,-0.769341;2.834949;0.207294;,-0.440953;3.555781;0.207294;,-0.440953;3.555781;-0.041122;,-0.769341;2.834949;0.207294;,-0.944661;2.914819;0.207294;,-0.616273;3.635651;0.207294;,-0.440953;3.555781;0.207294;,-0.944661;2.914819;0.207294;,-0.944661;2.914819;-0.041122;,-0.616273;3.635651;-0.041122;,-0.616273;3.635651;0.207294;,-0.944661;2.914819;-0.041122;,-0.769341;2.834949;-0.041122;,-0.440953;3.555781;-0.041122;,-0.616273;3.635651;-0.041122;,-0.769341;2.834949;0.207294;,-0.769341;2.834949;-0.041122;,-0.944661;2.914819;-0.041122;,-0.944661;2.914819;0.207294;,-0.616273;3.635651;0.207294;,-0.616273;3.635651;-0.041122;,-0.440953;3.555781;-0.041122;,-0.440953;3.555781;0.207294;,-1.104504;2.080977;-0.086788;,-1.104504;2.080977;0.161627;,-0.776116;2.801809;0.161627;,-0.776116;2.801809;-0.086788;,-1.104504;2.080977;0.161627;,-1.279824;2.160847;0.161627;,-0.951436;2.881679;0.161627;,-0.776116;2.801809;0.161627;,-1.279824;2.160847;0.161627;,-1.279824;2.160847;-0.086788;,-0.951436;2.881679;-0.086788;,-0.951436;2.881679;0.161627;,-1.279824;2.160847;-0.086788;,-1.104504;2.080977;-0.086788;,-0.776116;2.801809;-0.086788;,-0.951436;2.881679;-0.086788;,-1.104504;2.080977;0.161627;,-1.104504;2.080977;-0.086788;,-1.279824;2.160847;-0.086788;,-1.279824;2.160847;0.161627;,-0.951436;2.881679;0.161627;,-0.951436;2.881679;-0.086788;,-0.776116;2.801809;-0.086788;,-0.776116;2.801809;0.161627;,-0.055633;0.093601;-0.190081;,-0.055633;0.093601;0.190081;,-0.055633;1.205294;0.190081;,-0.055633;1.205294;-0.190081;,-0.055633;0.093601;0.190081;,-0.43017;0.093601;0.190081;,-0.43017;1.205294;0.190081;,-0.055633;1.205294;0.190081;,-0.43017;0.093601;0.190081;,-0.43017;0.093601;-0.190081;,-0.43017;1.205294;-0.190081;,-0.43017;1.205294;0.190081;,-0.43017;0.093601;-0.190081;,-0.055633;0.093601;-0.190081;,-0.055633;1.205294;-0.190081;,-0.43017;1.205294;-0.190081;,-0.055633;0.093601;0.190081;,-0.055633;0.093601;-0.190081;,-0.43017;0.093601;-0.190081;,-0.43017;0.093601;0.190081;,-0.43017;1.205294;0.190081;,-0.43017;1.205294;-0.190081;,-0.055633;1.205294;-0.190081;,-0.055633;1.205294;0.190081;,0;3.790919;0.428464;,-0.43344;3.809586;0.409797;,-0.43344;4.560537;0.409797;,0;4.579204;0.428464;,-0.43344;3.809586;0.409797;,-0.43344;3.809586;-0.284975;,-0.43344;4.560537;-0.284975;,-0.43344;4.560537;0.409797;,0;3.790919;0.428464;,0;3.790919;-0.303642;,-0.43344;3.809586;-0.284975;,-0.43344;3.809586;0.409797;,-0.43344;4.560537;0.409797;,-0.43344;4.560537;-0.284975;,0;4.579204;-0.303642;,0;4.579204;0.428464;,-0.43344;3.809586;-0.284975;,0;3.790919;-0.303642;,0;4.579204;-0.303642;,-0.43344;4.560537;-0.284975;;68;4;3,2,1,0;,4;7,6,5,4;,4;11,10,9,8;,4;15,14,13,12;,4;19,18,17,16;,4;23,22,21,20;,4;27,26,25,24;,4;31,30,29,28;,4;35,34,33,32;,4;39,38,37,36;,4;43,42,41,40;,4;47,46,45,44;,4;51,50,49,48;,4;55,54,53,52;,4;59,58,57,56;,4;63,62,61,60;,4;67,66,65,64;,4;71,70,69,68;,4;75,74,73,72;,4;79,78,77,76;,4;83,82,81,80;,4;87,86,85,84;,4;91,90,89,88;,4;95,94,93,92;,4;99,98,97,96;,4;103,102,101,100;,4;107,106,105,104;,4;111,110,109,108;,4;115,114,113,112;,4;119,118,117,116;,4;123,122,121,120;,4;127,126,125,124;,4;131,130,129,128;,4;135,134,133,132;,4;139,138,137,136;,4;143,142,141,140;,4;147,146,145,144;,4;151,150,149,148;,4;155,154,153,152;,4;159,158,157,156;,4;163,162,161,160;,4;167,166,165,164;,4;171,170,169,168;,4;175,174,173,172;,4;179,178,177,176;,4;183,182,181,180;,4;187,186,185,184;,4;191,190,189,188;,4;195,194,193,192;,4;199,198,197,196;,4;203,202,201,200;,4;207,206,205,204;,4;211,210,209,208;,4;215,214,213,212;,4;219,218,217,216;,4;223,222,221,220;,4;227,226,225,224;,4;231,230,229,228;,4;235,234,233,232;,4;239,238,237,236;,4;243,242,241,240;,4;247,246,245,244;,4;251,250,249,248;,4;255,254,253,252;,4;259,258,257,256;,4;263,262,261,260;,4;267,266,265,264;,4;271,270,269,268;;MeshNormals{272;0;-0.707083;0.707083;,0;0.707083;0.707083;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0;-0.707083;0.707083;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,0;-0.707083;-0.707083;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,0;0.707083;0.707083;,0;0.707083;-0.707083;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0;0.707083;-0.707083;,0;-0.707083;-0.707083;,-0.286019;-0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.764733;0.286019;0.577349;,-0.286019;-0.764733;0.577349;,-0.286019;-0.764733;0.577349;,-0.764733;0.286019;0.577349;,0.286019;0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;0.577349;,0.286019;0.764733;0.577349;,0.286019;0.764733;-0.577349;,0.764733;-0.286019;-0.577349;,0.764733;-0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.286019;-0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,0.286019;0.764733;0.577349;,-0.764733;0.286019;0.577349;,-0.764733;0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.764733;0.286019;0.577349;,-0.286019;-0.764733;0.577349;,-0.286019;-0.764733;0.577349;,-0.764733;0.286019;0.577349;,0.286019;0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;0.577349;,0.286019;0.764733;0.577349;,0.286019;0.764733;-0.577349;,0.764733;-0.286019;-0.577349;,0.764733;-0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.286019;-0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,0.286019;0.764733;0.577349;,-0.764733;0.286019;0.577349;,-0.764733;0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0;-0.707083;0.707083;,0;0.707083;0.707083;,0.599902;0.565722;0.565722;,0.599902;-0.565722;0.565722;,0.599902;-0.565722;0.565722;,0.599902;0.565722;0.565722;,0.599902;0.565722;-0.565722;,0.599902;-0.565722;-0.565722;,0;-0.707083;0.707083;,0.599902;-0.565722;0.565722;,0.599902;-0.565722;-0.565722;,0;-0.707083;-0.707083;,0.599902;0.565722;0.565722;,0;0.707083;0.707083;,0;0.707083;-0.707083;,0.599902;0.565722;-0.565722;,0.599902;-0.565722;-0.565722;,0.599902;0.565722;-0.565722;,0;0.707083;-0.707083;,0;-0.707083;-0.707083;,0;-0.707083;0.707083;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0;0.707083;0.707083;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,0;-0.707083;0.707083;,0;-0.707083;-0.707083;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0;0.707083;-0.707083;,0;0.707083;0.707083;,-0.577349;-0.577349;-0.577349;,0;-0.707083;-0.707083;,0;0.707083;-0.707083;,-0.577349;0.577349;-0.577349;,0.286019;-0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.764733;0.286019;0.577349;,0.764733;0.286019;-0.577349;,0.286019;-0.764733;0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,0.764733;0.286019;0.577349;,-0.764733;-0.286019;0.577349;,-0.764733;-0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,-0.286019;0.764733;0.577349;,-0.764733;-0.286019;-0.577349;,0.286019;-0.764733;-0.577349;,0.764733;0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.286019;-0.764733;-0.577349;,-0.764733;-0.286019;-0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,-0.286019;0.764733;-0.577349;,0.764733;0.286019;-0.577349;,0.764733;0.286019;0.577349;,0.286019;-0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.764733;0.286019;0.577349;,0.764733;0.286019;-0.577349;,0.286019;-0.764733;0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,0.764733;0.286019;0.577349;,-0.764733;-0.286019;0.577349;,-0.764733;-0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,-0.286019;0.764733;0.577349;,-0.764733;-0.286019;-0.577349;,0.286019;-0.764733;-0.577349;,0.764733;0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.286019;-0.764733;-0.577349;,-0.764733;-0.286019;-0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,-0.286019;0.764733;-0.577349;,0.764733;0.286019;-0.577349;,0.764733;0.286019;0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,0;-0.707083;0.707083;,-0.599902;-0.565722;0.565722;,-0.599872;0.565722;0.565722;,0;0.707083;0.707083;,-0.599902;-0.565722;0.565722;,-0.599902;-0.565722;-0.565722;,-0.599872;0.565722;-0.565722;,-0.599872;0.565722;0.565722;,0;-0.707083;0.707083;,0;-0.707083;-0.707083;,-0.599902;-0.565722;-0.565722;,-0.599902;-0.565722;0.565722;,-0.599872;0.565722;0.565722;,-0.599872;0.565722;-0.565722;,0;0.707083;-0.707083;,0;0.707083;0.707083;,-0.599902;-0.565722;-0.565722;,0;-0.707083;-0.707083;,0;0.707083;-0.707083;,-0.599872;0.565722;-0.565722;;68;4;3,2,1,0;,4;7,6,5,4;,4;11,10,9,8;,4;15,14,13,12;,4;19,18,17,16;,4;23,22,21,20;,4;27,26,25,24;,4;31,30,29,28;,4;35,34,33,32;,4;39,38,37,36;,4;43,42,41,40;,4;47,46,45,44;,4;51,50,49,48;,4;55,54,53,52;,4;59,58,57,56;,4;63,62,61,60;,4;67,66,65,64;,4;71,70,69,68;,4;75,74,73,72;,4;79,78,77,76;,4;83,82,81,80;,4;87,86,85,84;,4;91,90,89,88;,4;95,94,93,92;,4;99,98,97,96;,4;103,102,101,100;,4;107,106,105,104;,4;111,110,109,108;,4;115,114,113,112;,4;119,118,117,116;,4;123,122,121,120;,4;127,126,125,124;,4;131,130,129,128;,4;135,134,133,132;,4;139,138,137,136;,4;143,142,141,140;,4;147,146,145,144;,4;151,150,149,148;,4;155,154,153,152;,4;159,158,157,156;,4;163,162,161,160;,4;167,166,165,164;,4;171,170,169,168;,4;175,174,173,172;,4;179,178,177,176;,4;183,182,181,180;,4;187,186,185,184;,4;191,190,189,188;,4;195,194,193,192;,4;199,198,197,196;,4;203,202,201,200;,4;207,206,205,204;,4;211,210,209,208;,4;215,214,213,212;,4;219,218,217,216;,4;223,222,221,220;,4;227,226,225,224;,4;231,230,229,228;,4;235,234,233,232;,4;239,238,237,236;,4;243,242,241,240;,4;247,246,245,244;,4;251,250,249,248;,4;255,254,253,252;,4;259,258,257,256;,4;263,262,261,260;,4;267,266,265,264;,4;271,270,269,268;;}MeshTextureCoords{272;0.849264;0.899246;,0.849264;0.931916;,0.861547;0.931916;,0.861547;0.899246;,0.916988;0.931916;,0.916988;0.899246;,0.9054;0.899246;,0.9054;0.931916;,0.84857;0.844707;,0.84857;0.83254;,0.836981;0.83254;,0.836981;0.844707;,0.927004;0.903587;,0.927004;0.931916;,0.937019;0.931916;,0.937019;0.903587;,0.937019;0.903587;,0.937019;0.931916;,0.946887;0.931916;,0.946887;0.903587;,0.888533;0.856954;,0.888533;0.828625;,0.878517;0.828625;,0.878517;0.856954;,0.939292;0.870917;,0.939292;0.899246;,0.949159;0.899246;,0.949159;0.870917;,0.946887;0.91117;,0.956719;0.91117;,0.956719;0.901213;,0.946887;0.901213;,0.865118;0.813135;,0.855286;0.813135;,0.855286;0.823092;,0.865118;0.823092;,0.866874;0.847426;,0.866874;0.835259;,0.855286;0.835259;,0.855286;0.847426;,0.598002;0.973516;,0.598002;0.206739;,0.309722;0.206739;,0.309722;0.973516;,0.909393;0.822135;,0.909393;0.841014;,0.915938;0.841014;,0.915938;0.822135;,0.951962;0.931916;,0.951962;0.91117;,0.946887;0.91117;,0.946887;0.931916;,0.948762;0.841801;,0.948762;0.822921;,0.942217;0.822921;,0.942217;0.841801;,0.893608;0.838075;,0.893608;0.817329;,0.888533;0.817329;,0.888533;0.838075;,0.900724;0.909292;,0.90515;0.909292;,0.90515;0.902786;,0.900724;0.902786;,0.953585;0.871994;,0.949159;0.871994;,0.949159;0.8785;,0.953585;0.8785;,0.84857;0.837995;,0.84857;0.856874;,0.855114;0.856874;,0.855114;0.837995;,0.902881;0.83746;,0.902881;0.816714;,0.897805;0.816714;,0.897805;0.83746;,0.942217;0.841801;,0.942217;0.822921;,0.935673;0.822921;,0.935673;0.841801;,0.949159;0.8785;,0.949159;0.899246;,0.954235;0.899246;,0.954235;0.8785;,0.919226;0.822135;,0.923651;0.822135;,0.923651;0.815629;,0.919226;0.815629;,0.928077;0.815629;,0.923651;0.815629;,0.923651;0.822135;,0.928077;0.822135;,0.865301;0.847426;,0.865301;0.876542;,0.875317;0.876542;,0.875317;0.847426;,0.909393;0.841014;,0.909393;0.87013;,0.919261;0.87013;,0.919261;0.841014;,0.855286;0.847426;,0.855286;0.876542;,0.865301;0.876542;,0.865301;0.847426;,0.919261;0.841014;,0.919261;0.87013;,0.929128;0.87013;,0.929128;0.841014;,0.878517;0.828625;,0.888349;0.828625;,0.88835;0.818668;,0.878517;0.818668;,0.836981;0.83254;,0.846814;0.83254;,0.846814;0.822583;,0.836981;0.822583;,0.857749;0.887894;,0.836981;0.887894;,0.837473;0.899246;,0.857257;0.899246;,0.855286;0.876542;,0.855286;0.856874;,0.836981;0.856874;,0.836981;0.876542;,0.897805;0.887893;,0.897313;0.876622;,0.879009;0.876622;,0.878517;0.887893;,0.886604;0.909292;,0.886112;0.920645;,0.9054;0.920645;,0.904908;0.909292;,0.977665;0.442421;,0.977665;0.131438;,0.799225;0.123708;,0.799225;0.450151;,0.849264;0.899246;,0.836981;0.899246;,0.836981;0.931916;,0.849264;0.931916;,0.909393;0.866576;,0.897805;0.866576;,0.897805;0.899246;,0.909393;0.899246;,0.84857;0.844707;,0.836981;0.844707;,0.836981;0.856874;,0.84857;0.856874;,0.929276;0.899246;,0.939292;0.899246;,0.939292;0.870917;,0.929276;0.870917;,0.876741;0.819096;,0.866874;0.819096;,0.866874;0.847426;,0.876741;0.847426;,0.939144;0.841801;,0.929128;0.841801;,0.929128;0.87013;,0.939144;0.87013;,0.949011;0.841801;,0.939144;0.841801;,0.939144;0.87013;,0.949011;0.87013;,0.836981;0.812626;,0.836981;0.822583;,0.846814;0.822583;,0.846814;0.812626;,0.909393;0.812178;,0.909393;0.822135;,0.919226;0.822135;,0.919226;0.812178;,0.866874;0.823092;,0.855286;0.823092;,0.855286;0.835259;,0.866874;0.835259;,0.021442;0.973516;,0.309722;0.973516;,0.309722;0.206739;,0.021442;0.206739;,0.916039;0.841014;,0.922583;0.841014;,0.922583;0.822135;,0.916039;0.822135;,0.907956;0.816714;,0.902881;0.816714;,0.902881;0.83746;,0.907956;0.83746;,0.929128;0.822135;,0.922583;0.822135;,0.922583;0.841014;,0.929128;0.841014;,0.853645;0.817249;,0.84857;0.817249;,0.84857;0.837995;,0.853645;0.837995;,0.900724;0.909292;,0.900724;0.902786;,0.895944;0.902786;,0.895944;0.909292;,0.93896;0.816415;,0.93896;0.822921;,0.94374;0.822921;,0.94374;0.816415;,0.935673;0.822921;,0.929128;0.822921;,0.929128;0.841801;,0.935673;0.841801;,0.954087;0.849384;,0.949011;0.849384;,0.949011;0.87013;,0.954087;0.87013;,0.895077;0.838075;,0.888533;0.838075;,0.888533;0.856954;,0.895077;0.856954;,0.948762;0.841801;,0.953838;0.841801;,0.953838;0.821055;,0.948762;0.821055;,0.94374;0.816415;,0.94374;0.822921;,0.94852;0.822921;,0.94852;0.816415;,0.949011;0.842878;,0.949011;0.849384;,0.953791;0.849384;,0.953791;0.842878;,0.919409;0.87013;,0.909393;0.87013;,0.909393;0.899246;,0.919409;0.899246;,0.897805;0.866576;,0.907672;0.866576;,0.907672;0.83746;,0.897805;0.83746;,0.927004;0.9028;,0.916988;0.9028;,0.916988;0.931916;,0.927004;0.931916;,0.929276;0.87013;,0.919409;0.87013;,0.919409;0.899246;,0.929276;0.899246;,0.93896;0.822921;,0.93896;0.812965;,0.929128;0.812965;,0.929128;0.822921;,0.886112;0.899336;,0.886112;0.909292;,0.895944;0.909292;,0.895944;0.899336;,0.857749;0.887894;,0.857257;0.876542;,0.837473;0.876542;,0.836981;0.887894;,0.896821;0.856954;,0.878517;0.856954;,0.878517;0.876622;,0.896821;0.876622;,0.897805;0.887893;,0.878517;0.887893;,0.879009;0.899246;,0.897313;0.899246;,0.886604;0.931916;,0.904908;0.931916;,0.9054;0.920645;,0.886112;0.920645;,0.620785;0.44242;,0.799225;0.450151;,0.799225;0.123708;,0.620785;0.131438;;}XSkinMeshHeader{3;9;10;}SkinWeights{"Armature_arm_l";24;44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,66;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;-0.047733,0.901521,0.430095,0,-0.097683,0.424309,-0.900233,0,-0.994073,-0.084983,0.06781,0,0.374873,-2.006904,2.980378,1;;}SkinWeights{"Armature_elbow_r";24;216,219,218,213,212,215,214,209,224,208,227,211,226,210,206,221,207,220,204,223,205,222,225,217;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;0.102316,0.92166,-0.374266,0,-0.090709,-0.366028,-0.926173,0,-0.990608,0.128712,0.046152,0,0.402018,1.853661,2.350172,1;;}SkinWeights{"Armature_arm_r";24;186,187,184,185,182,183,180,194,195,203,202,192,193,201,200,199,190,198,191,197,188,196,189,181;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;-0.047733,0.901521,-0.430095,0,-0.05233,-0.432251,-0.900234,0,-0.997489,-0.020464,0.067809,0,0.160852,2.035269,2.980378,1;;}SkinWeights{"Armature_knee_l";24;105,99,114,106,98,115,107,101,93,108,100,92,109,103,95,110,102,94,111,97,112,104,113,96;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0.054357,-0.998522,0,0,0.998501,0.054355,0,-0.246294,-0.008592,1.301673,1;;}SkinWeights{"Armature_Bone_007";40;132,133,134,135,124,125,126,252,253,254,255,121,122,264,265,123,267,268,269,270,116,256,258,259,260,261,262,263,271,266,120,119,117,128,129,127,130,118,131,257;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0,1,0,0,-1,0,0,0,0,-3.793003,1;;}SkinWeights{"Armature_elbow_l";24;88,80,72,91,83,75,90,82,74,70,85,77,71,84,76,68,87,79,69,86,78,89,81,73;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;0.102316,0.92166,0.374266,0,-0.008222,0.377011,-0.926173,0,-0.994719,0.091686,0.046152,0,-0.014321,-1.896701,2.350171,1;;}SkinWeights{"Armature_knee_r";24;249,235,250,234,251,229,244,228,245,231,246,230,247,240,241,242,243,237,236,239,238,233,248,232;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0.054357,-0.998522,0,0,0.998501,0.054355,0,0.246294,-0.008592,1.301673,1;;}SkinWeights{"Armature_leg_l";38;0,3,4,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,40,43,136,145,177,144;0.055873,0.852304,0.852304,0.82998,0.055873,0.852304,0.82998,0.054606,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.82998,0.054606,0.055873,0.054606,0.054606,0.055873;1,0,0,0,0,-0.056452,-0.998405,0,0,0.998385,-0.056452,0,-0.246294,0.135476,2.396023,1;;}SkinWeights{"Armature_leg_r";38;0,170,169,11,168,151,150,149,148,147,146,176,145,177,144,159,158,157,156,155,154,153,167,136,166,137,165,164,163,140,162,141,161,43,160,152,8,171;0.055873,1,1,0.054606,1,1,1,1,1,0.852304,0.82998,0.82998,0.054606,0.054606,0.055873,1,1,1,1,1,1,1,1,0.055873,1,0.852304,1,1,1,0.852304,1,0.82998,1,0.054606,1,1,0.055873,1;1,0,0,0,0,-0.056452,-0.998405,0,0,0.998385,-0.056452,0,0.246294,0.135476,2.396023,1;;}SkinWeights{"Armature_body";40;0,1,2,3,4,5,6,7,8,9,10,11,36,37,38,39,40,41,42,43,136,137,138,139,140,147,141,146,142,145,143,144,179,174,178,173,177,172,176,175;0.888255,1,1,0.147696,0.147696,1,1,0.17002,0.888255,0.147696,0.17002,0.890788,1,1,1,1,0.17002,1,1,0.890788,0.888255,0.147696,1,1,0.147696,0.147696,0.17002,0.17002,1,0.890788,1,0.888255,1,1,1,1,0.890788,1,0.17002,1;1,0,0,0,0,0,1,0,0,-1,0,0,0,0,-2.571201,1;;}}}}}
\ No newline at end of file
diff --git a/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua
index dfbf655ea7..26b538e7f6 100644
--- a/games/devtest/mods/testentities/visuals.lua
+++ b/games/devtest/mods/testentities/visuals.lua
@@ -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
diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua
index f6f8513ce1..f92465cc57 100644
--- a/games/devtest/mods/unittests/inside_mapgen_env.lua
+++ b/games/devtest/mods/unittests/inside_mapgen_env.lua
@@ -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)
diff --git a/irr/README.md b/irr/README.md
index 449903cfe0..eb7f14809f 100644
--- a/irr/README.md
+++ b/irr/README.md
@@ -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
-----
diff --git a/irr/include/IAnimatedMesh.h b/irr/include/IAnimatedMesh.h
index 62568bf6b3..0ef0971bd8 100644
--- a/irr/include/IAnimatedMesh.h
+++ b/irr/include/IAnimatedMesh.h
@@ -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
diff --git a/irr/include/IAnimatedMeshSceneNode.h b/irr/include/IAnimatedMeshSceneNode.h
index 8f9f6d661f..2df8da9174 100644
--- a/irr/include/IAnimatedMeshSceneNode.h
+++ b/irr/include/IAnimatedMeshSceneNode.h
@@ -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 &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. */
diff --git a/irr/include/IBoneSceneNode.h b/irr/include/IBoneSceneNode.h
index eef55f6e03..d169b8d30b 100644
--- a/irr/include/IBoneSceneNode.h
+++ b/irr/include/IBoneSceneNode.h
@@ -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 &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 &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 &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 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
diff --git a/irr/include/IMesh.h b/irr/include/IMesh.h
index 8ee180d5d3..e8656e8682 100644
--- a/irr/include/IMesh.h
+++ b/irr/include/IMesh.h
@@ -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
diff --git a/irr/include/IMeshManipulator.h b/irr/include/IMeshManipulator.h
index c9d989cae8..0312a38c86 100644
--- a/irr/include/IMeshManipulator.h
+++ b/irr/include/IMeshManipulator.h
@@ -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.
diff --git a/irr/include/IMeshSceneNode.h b/irr/include/IMeshSceneNode.h
index 1fb0054054..3065f4c07d 100644
--- a/irr/include/IMeshSceneNode.h
+++ b/irr/include/IMeshSceneNode.h
@@ -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
diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h
index f91fd64997..5737158617 100644
--- a/irr/include/ISceneNode.h
+++ b/irr/include/ISceneNode.h
@@ -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.
diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h
deleted file mode 100644
index dcc65410f7..0000000000
--- a/irr/include/SAnimatedMesh.h
+++ /dev/null
@@ -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
-#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(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(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 &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 Meshes;
-
- //! The bounding box of this mesh
- core::aabbox3d 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
diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h
index 5e76fafc51..b22bb77494 100644
--- a/irr/include/SMesh.h
+++ b/irr/include/SMesh.h
@@ -5,7 +5,7 @@
#pragma once
#include
-#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 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
diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h
index a527db76e5..aa718c882b 100644
--- a/irr/include/SkinnedMesh.h
+++ b/irr/include/SkinnedMesh.h
@@ -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
#include
+#include
+#include
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 &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 &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 &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 &jointChildSceneNodes);
-
- //! Transfers the joint data to the mesh
- void transferJointsToMesh(const std::vector &jointChildSceneNodes);
-
//! Creates an array of joints from this mesh as children of node
- void addJoints(std::vector &jointChildSceneNodes,
- IAnimatedMeshSceneNode *node,
- ISceneManager *smgr);
+ std::vector 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 Name;
- //! Local matrix of this joint
- core::matrix4 LocalMatrix;
+ //! Local transformation to be set by loaders. Mutated by animation.
+ using VariantTransform = std::variant;
+ VariantTransform transform{core::Transform{}};
+
+ VariantTransform animate(f32 frame) const {
+ if (keys.empty())
+ return transform;
+
+ if (std::holds_alternative(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(transform);
+ keys.updateTransform(frame, trs);
+ return {trs};
+ }
- //! List of child joints
- std::vector Children;
//! List of attached meshes
std::vector AttachedMeshes;
@@ -329,42 +338,49 @@ public:
//! Skin weights
std::vector 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 GlobalInversedMatrix;
- private:
- //! Internal members used by SkinnedMesh
- friend class SkinnedMesh;
- bool GlobalSkinningSpace;
+ void setParent(SJoint *parent) {
+ ParentJointID = parent ? parent->JointID : std::optional{};
+ }
+
+ u16 JointID; // TODO refactor away: pointers -> IDs (problem: .x loader abuses SJoint)
+ std::optional ParentJointID;
};
+ //! Animates joints based on frame input
+ std::vector animateMesh(f32 frame);
+
+ //! Calculates a bounding box given an animation in the form of global joint transforms.
+ core::aabbox3df calculateBoundingBox(
+ const std::vector &global_transforms);
+
+ void recalculateBaseBoundingBoxes();
+
const std::vector &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 TextureSlots;
+ //! Joints, topologically sorted (parents come before their children).
std::vector AllJoints;
- std::vector RootJoints;
// bool can't be used here because std::vector
// doesn't allow taking a reference to individual elements.
std::vector> Vertices_Moved;
- core::aabbox3d 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;
};
diff --git a/irr/include/Transform.h b/irr/include/Transform.h
new file mode 100644
index 0000000000..1e96e183d2
--- /dev/null
+++ b/irr/include/Transform.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "irrMath.h"
+#include
+#include
+#include
+
+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
diff --git a/irr/include/quaternion.h b/irr/include/quaternion.h
index e23b1317d0..42e0428a90 100644
--- a/irr/include/quaternion.h
+++ b/irr/include/quaternion.h
@@ -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.
diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp
index 09d83038b7..7b1d9053a9 100644
--- a/irr/src/CAnimatedMeshSceneNode.cpp
+++ b/irr/src/CAnimatedMeshSceneNode.cpp
@@ -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
+#include
+#include
+#include
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(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(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(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(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(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(&joint->transform);
+ PerJoint.SceneNodes.push_back(new CBoneSceneNode(
+ parent, SceneManager, 0, i, joint->Name,
+ matrix ? core::Transform{} : std::get(joint->transform),
+ matrix ? *matrix : std::optional{}));
+ }
+}
- SkinnedMesh *skinnedMesh = static_cast(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 &transforms)
+{
+ for (size_t i = 0; i < transforms.size(); ++i) {
+ const auto &transform = transforms[i];
+ auto *node = static_cast(PerJoint.SceneNodes[i]);
+ if (const auto *trs = std::get_if(&transform)) {
+ node->setTransform(*trs);
+ // .x lets animations override matrix transforms entirely.
+ node->Matrix = std::nullopt;
+ } else {
+ node->Matrix = std::get(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(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;
diff --git a/irr/src/CAnimatedMeshSceneNode.h b/irr/src/CAnimatedMeshSceneNode.h
index 5149f7618f..c67479481e 100644
--- a/irr/src/CAnimatedMeshSceneNode.h
+++ b/irr/src/CAnimatedMeshSceneNode.h
@@ -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 &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 &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 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 OnAnimateCallback;
- std::vector JointChildSceneNodes;
- core::array PretransitingSave;
+ struct PerJointData {
+ std::vector SceneNodes;
+ std::vector GlobalMatrices;
+ std::vector> 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
diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp
index 51342f4517..6e3e147916 100644
--- a/irr/src/CB3DMeshFileLoader.cpp
+++ b/irr/src/CB3DMeshFileLoader.cpp
@@ -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
{
diff --git a/irr/src/CBoneSceneNode.cpp b/irr/src/CBoneSceneNode.cpp
deleted file mode 100644
index 7aa637094b..0000000000
--- a/irr/src/CBoneSceneNode.cpp
+++ /dev/null
@@ -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
-
-namespace irr
-{
-namespace scene
-{
-
-//! constructor
-CBoneSceneNode::CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id,
- u32 boneIndex, const std::optional &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 &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
diff --git a/irr/src/CBoneSceneNode.h b/irr/src/CBoneSceneNode.h
index d900570db0..4151f73721 100644
--- a/irr/src/CBoneSceneNode.h
+++ b/irr/src/CBoneSceneNode.h
@@ -7,6 +7,8 @@
// Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
#include "IBoneSceneNode.h"
+#include "Transform.h"
+#include "matrix4.h"
#include
@@ -21,49 +23,48 @@ public:
//! constructor
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
- const std::optional &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 &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 &boneName = std::nullopt,
+ const core::Transform &transform = {},
+ const std::optional &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 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 Matrix;
};
} // end namespace scene
diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp
index 3f2096f40e..53ae1b5808 100644
--- a/irr/src/CGLTFMeshFileLoader.cpp
+++ b/irr/src/CGLTFMeshFileLoader.cpp
@@ -539,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes
mat[i] = static_cast(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> 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(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::make(m_gltf_model, sampler.output);
diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt
index 83da89c116..c752d7d31c 100644
--- a/irr/src/CMakeLists.txt
+++ b/irr/src/CMakeLists.txt
@@ -320,7 +320,6 @@ set(IRRMESHLOADER
add_library(IRRMESHOBJ OBJECT
SkinnedMesh.cpp
- CBoneSceneNode.cpp
CMeshSceneNode.cpp
CAnimatedMeshSceneNode.cpp
${IRRMESHLOADER}
diff --git a/irr/src/CMeshCache.cpp b/irr/src/CMeshCache.cpp
index 4f7e1203c2..9a41aa5826 100644
--- a/irr/src/CMeshCache.cpp
+++ b/irr/src/CMeshCache.cpp
@@ -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;
diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp
index 659e8dff8a..535b74686b 100644
--- a/irr/src/CMeshManipulator.cpp
+++ b/irr/src/CMeshManipulator.cpp
@@ -6,7 +6,6 @@
#include "SkinnedMesh.h"
#include "SMesh.h"
#include "CMeshBuffer.h"
-#include "SAnimatedMesh.h"
#include "os.h"
#include
@@ -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
diff --git a/irr/src/CMeshManipulator.h b/irr/src/CMeshManipulator.h
index 0377d6a3a0..c5632512d2 100644
--- a/irr/src/CMeshManipulator.h
+++ b/irr/src/CMeshManipulator.h
@@ -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
diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp
index 246f414de6..f7d0129131 100644
--- a/irr/src/CNullDriver.cpp
+++ b/irr/src/CNullDriver.cpp
@@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh
else if (node->getType() == scene::ESNT_MESH)
mesh = static_cast(node)->getMesh();
else
- mesh = static_cast(node)->getMesh()->getMesh(0);
+ mesh = static_cast(node)->getMesh();
if (!mesh)
return;
}
diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp
index 5c7f389504..bc51c10f21 100644
--- a/irr/src/COBJMeshFileLoader.cpp
+++ b/irr/src/COBJMeshFileLoader.cpp
@@ -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
diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp
index 05f8de71a5..b0b82b7726 100644
--- a/irr/src/CSceneManager.cpp
+++ b/irr/src/CSceneManager.cpp
@@ -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"
diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp
index d93502d6b5..f2c4e94e6f 100644
--- a/irr/src/CXMeshFileLoader.cpp
+++ b/irr/src/CXMeshFileLoader.cpp
@@ -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());
diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp
index 938a50e178..cf58121ba6 100644
--- a/irr/src/SkinnedMesh.cpp
+++ b/irr/src/SkinnedMesh.cpp
@@ -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
+#include
+#include
#include
#include
@@ -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 SkinnedMesh::animateMesh(f32 frame)
+{
+ assert(HasAnimation);
+ std::vector 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 &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 &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 &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 &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 &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> animated(getMeshBufferCount());
+ for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
+ animated[mb] = std::vector(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 new_to_old_id;
+
+ std::vector> 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 old_to_new_id(n);
+ for (u16 i = 0; i < n; ++i)
+ old_to_new_id[new_to_old_id[i]] = i;
+
+ std::vector 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 matrices;
+ matrices.reserve(AllJoints.size());
for (auto *joint : AllJoints) {
+ if (const auto *matrix = std::get_if(&joint->transform))
+ matrices.push_back(*matrix);
+ else
+ matrices.push_back(std::get(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 &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 &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 &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
diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp
index 06e2f53566..81ac10ec8f 100644
--- a/lib/tiniergltf/tiniergltf.hpp
+++ b/lib/tiniergltf/tiniergltf.hpp
@@ -916,12 +916,7 @@ struct Node {
std::optional skin;
std::optional> 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")) {
diff --git a/misc/org.luanti.luanti.metainfo.xml b/misc/org.luanti.luanti.metainfo.xml
index 3e99aa36b9..6759cdc1f0 100644
--- a/misc/org.luanti.luanti.metainfo.xml
+++ b/misc/org.luanti.luanti.metainfo.xml
@@ -158,10 +158,10 @@
https://www.luanti.orghttps://www.luanti.org/get-involved/#reporting-issues
- https://dev.luanti.org/Translation/
+ https://docs.luanti.org/for-creators/translation/https://www.luanti.org/get-involved/#donate
- https://wiki.luanti.org/FAQ
- https://wiki.luanti.org
+ https://docs.luanti.org/about/faq
+ https://docs.luanti.orghttps://github.com/luanti-org/luantihttps://www.luanti.org/get-involved
diff --git a/misc/redirect.html b/misc/redirect.html
index f08a49ae14..4a219f7f00 100644
--- a/misc/redirect.html
+++ b/misc/redirect.html
@@ -4,11 +4,11 @@
Luanti API documentation
-
-
+
+