diff --git a/.gitattributes b/.gitattributes index ecd9a7a29..41a6c0979 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,4 @@ *.h diff=cpp *.gltf binary +*.x binary diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fe0c97324..0fb780464 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -49,6 +49,8 @@ jobs: env: CC: gcc-7 CXX: g++-7 + # Test fallback SHA implementations + CMAKE_FLAGS: '-DENABLE_OPENSSL=0' - name: Test run: | diff --git a/CNAME b/CNAME index c8f58d469..cd4ccafcc 100644 --- a/CNAME +++ b/CNAME @@ -1 +1 @@ -api.minetest.net +api.luanti.org diff --git a/LICENSE.txt b/LICENSE.txt index e562a0b4c..f7930f528 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -63,6 +63,8 @@ Zughy: textures/base/pack/settings_btn.png textures/base/pack/settings_info.png textures/base/pack/settings_reset.png + textures/base/pack/server_url.png + textures/base/pack/server_view_clients.png appgurueu: textures/base/pack/server_incompatible.png diff --git a/README.md b/README.md index b21153d54..aa8faccd5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Table of Contents Further documentation ---------------------- - Website: https://www.luanti.org/ -- Wiki: https://wiki.minetest.net/ +- Wiki: https://wiki.luanti.org/ - Forum: https://forum.luanti.org/ - GitHub: https://github.com/minetest/minetest/ - [Developer documentation](doc/developing/) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 8d7753839..ea80a09fb 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -44,6 +44,7 @@ core.features = { bulk_lbms = true, abm_without_neighbors = true, biome_weights = true, + particle_blend_clip = true, } function core.has_feature(arg) diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua index 90092952d..07ab09b37 100644 --- a/builtin/game/misc_s.lua +++ b/builtin/game/misc_s.lua @@ -36,11 +36,7 @@ end function core.setting_get_pos(name) - local value = core.settings:get(name) - if not value then - return nil - end - return core.string_to_pos(value) + return core.settings:get_pos(name) end diff --git a/builtin/game/static_spawn.lua b/builtin/game/static_spawn.lua index 5b834310f..2d535251a 100644 --- a/builtin/game/static_spawn.lua +++ b/builtin/game/static_spawn.lua @@ -1,7 +1,7 @@ local static_spawnpoint_string = core.settings:get("static_spawnpoint") if static_spawnpoint_string and static_spawnpoint_string ~= "" and - not core.setting_get_pos("static_spawnpoint") then + not core.settings:get_pos("static_spawnpoint") then error('The static_spawnpoint setting is invalid: "' .. static_spawnpoint_string .. '"') end diff --git a/builtin/mainmenu/dlg_clients_list.lua b/builtin/mainmenu/dlg_clients_list.lua new file mode 100644 index 000000000..2ea021c5e --- /dev/null +++ b/builtin/mainmenu/dlg_clients_list.lua @@ -0,0 +1,49 @@ +-- Luanti +-- Copyright (C) 2024 siliconsniffer +-- SPDX-License-Identifier: LGPL-2.1-or-later + + +local function clients_list_formspec(dialogdata) + local TOUCH_GUI = core.settings:get_bool("touch_gui") + local clients_list = dialogdata.server.clients_list + local servername = dialogdata.server.name + + local function fmt_formspec_list(clients_list) + local escaped = {} + for i, str in ipairs(clients_list) do + escaped[i] = core.formspec_escape(str) + end + return table.concat(escaped, ",") + end + + local formspec = { + "formspec_version[8]", + "size[6,9.5]", + TOUCH_GUI and "padding[0.01,0.01]" or "", + "hypertext[0,0;6,1.5;;", + fgettext("This is the list of clients connected to\n$1", + "" .. core.hypertext_escape(servername) .. "") .. "]", + "textlist[0.5,1.5;5,6.8;;" .. fmt_formspec_list(clients_list) .. "]", + "button[1.5,8.5;3,0.8;quit;OK]" + } + return table.concat(formspec, "") +end + + +local function clients_list_buttonhandler(this, fields) + if fields.quit then + this:delete() + return true + end + return false +end + + +function create_clientslist_dialog(server) + local retval = dialog_create("dlg_clients_list", + clients_list_formspec, + clients_list_buttonhandler, + nil) + retval.data.server = server + return retval +end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 41519b2ee..4e1c201cd 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -55,6 +55,7 @@ dofile(menupath .. DIR_DELIM .. "dlg_register.lua") dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua") dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.lua") +dofile(menupath .. DIR_DELIM .. "dlg_clients_list.lua") local tabs = { content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"), diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index ef28398b1..f1861879a 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -123,6 +123,22 @@ local function load() end, } + local touchscreen_layout = { + query_text = "Touchscreen layout", + requires = { + touchscreen = true, + }, + get_formspec = function(self, avail_w) + local btn_w = math.min(avail_w, 6) + return ("button[0,0;%f,0.8;btn_touch_layout;%s]"):format(btn_w, fgettext("Touchscreen layout")), 0.8 + end, + on_submit = function(self, fields) + if fields.btn_touch_layout then + core.show_touchscreen_layout() + end + end, + } + add_page({ id = "accessibility", title = fgettext_ne("Accessibility"), @@ -151,6 +167,8 @@ local function load() load_settingtypes() table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) + -- insert after "touch_controls" + table.insert(page_by_id.controls_touchscreen.content, 2, touchscreen_layout) do local content = page_by_id.graphics_and_audio_effects.content local idx = table.indexof(content, "enable_dynamic_shadows") diff --git a/builtin/mainmenu/settings/generate_from_settingtypes.lua b/builtin/mainmenu/settings/generate_from_settingtypes.lua index 198037776..52dfe71b1 100644 --- a/builtin/mainmenu/settings/generate_from_settingtypes.lua +++ b/builtin/mainmenu/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.minetest.net/ +# https://wiki.luanti.org/ ]] diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua index a73b863ce..12192715f 100644 --- a/builtin/mainmenu/tab_online.lua +++ b/builtin/mainmenu/tab_online.lua @@ -55,6 +55,51 @@ local function get_sorted_servers() return servers end +local function is_selected_fav(server) + local address = core.settings:get("address") + local port = tonumber(core.settings:get("remote_port")) + + for _, fav in ipairs(serverlistmgr.get_favorites()) do + if address == fav.address and port == fav.port then + return true + end + end + return false +end + +-- Persists the selected server in the "address" and "remote_port" settings + +local function set_selected_server(server) + if server == nil then -- reset selection + core.settings:remove("address") + core.settings:remove("remote_port") + return + end + local address = server.address + local port = server.port + gamedata.serverdescription = server.description + + if address and port then + core.settings:set("address", address) + core.settings:set("remote_port", port) + end +end + +local function find_selected_server() + local address = core.settings:get("address") + local port = tonumber(core.settings:get("remote_port")) + for _, server in ipairs(serverlistmgr.servers) do + if server.address == address and server.port == port then + return server + end + end + for _, server in ipairs(serverlistmgr.get_favorites()) do + if server.address == address and server.port == port then + return server + end + end +end + local function get_formspec(tabview, name, tabdata) -- Update the cached supported proto info, -- it may have changed after a change by the settings menu. @@ -107,17 +152,51 @@ local function get_formspec(tabview, name, tabdata) retval = retval .. "button[0.25,6;2.5,0.75;btn_mp_register;" .. fgettext("Register") .. "]" end - if tabdata.selected then - if gamedata.fav then - retval = retval .. "tooltip[btn_delete_favorite;" .. fgettext("Remove favorite") .. "]" - retval = retval .. "style[btn_delete_favorite;padding=6]" - retval = retval .. "image_button[5,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir .. - "server_favorite_delete.png") .. ";btn_delete_favorite;]" - end + local selected_server = find_selected_server() + + if selected_server then if gamedata.serverdescription then retval = retval .. "textarea[0.25,1.85;5.25,2.7;;;" .. core.formspec_escape(gamedata.serverdescription) .. "]" end + + local clients_list = selected_server.clients_list + local can_view_clients_list = clients_list and #clients_list > 0 + if can_view_clients_list then + table.sort(clients_list, function(a, b) + return a:lower() < b:lower() + end) + local max_clients = 5 + if #clients_list > max_clients then + retval = retval .. "tooltip[btn_view_clients;" .. + fgettext("Clients:\n$1", table.concat(clients_list, "\n", 1, max_clients)) .. "\n..." .. "]" + else + retval = retval .. "tooltip[btn_view_clients;" .. + fgettext("Clients:\n$1", table.concat(clients_list, "\n")) .. "]" + end + retval = retval .. "style[btn_view_clients;padding=6]" + retval = retval .. "image_button[4.5,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir .. + "server_view_clients.png") .. ";btn_view_clients;]" + end + + if selected_server.url then + retval = retval .. "tooltip[btn_server_url;" .. fgettext("Open server website") .. "]" + retval = retval .. "style[btn_server_url;padding=6]" + retval = retval .. "image_button[" .. (can_view_clients_list and "4" or "4.5") .. ",1.3;0.5,0.5;" .. + core.formspec_escape(defaulttexturedir .. "server_url.png") .. ";btn_server_url;]" + end + + if is_selected_fav() then + retval = retval .. "tooltip[btn_delete_favorite;" .. fgettext("Remove favorite") .. "]" + retval = retval .. "style[btn_delete_favorite;padding=6]" + retval = retval .. "image_button[5,1.3;0.5,0.5;" .. + core.formspec_escape(defaulttexturedir .. "server_favorite_delete.png") .. ";btn_delete_favorite;]" + else + retval = retval .. "tooltip[btn_add_favorite;" .. fgettext("Add favorite") .. "]" + retval = retval .. "style[btn_add_favorite;padding=6]" + retval = retval .. "image_button[5,1.3;0.5,0.5;" .. + core.formspec_escape(defaulttexturedir .. "server_favorite.png") .. ";btn_add_favorite;]" + end end retval = retval .. "container_end[]" @@ -175,11 +254,17 @@ local function get_formspec(tabview, name, tabdata) retval = retval .. table.concat(rows, ",") - if tabdata.selected then - retval = retval .. ";" .. tabdata.selected .. "]" - else - retval = retval .. ";0]" + local selected_row_idx = 0 + if selected_server then + for i, server in pairs(tabdata.lookup) do + if selected_server.address == server.address and + selected_server.port == server.port then + selected_row_idx = i + break + end + end end + retval = retval .. ";" .. selected_row_idx .. "]" return retval end @@ -231,35 +316,6 @@ local function search_server_list(input) menudata.search_result = search_result end -local function set_selected_server(tabdata, idx, server) - -- reset selection - if idx == nil or server == nil then - tabdata.selected = nil - - core.settings:set("address", "") - core.settings:set("remote_port", "30000") - return - end - - local address = server.address - local port = server.port - gamedata.serverdescription = server.description - - gamedata.fav = false - for _, fav in ipairs(serverlistmgr.get_favorites()) do - if address == fav.address and port == fav.port then - gamedata.fav = true - break - end - end - - if address and port then - core.settings:set("address", address) - core.settings:set("remote_port", port) - end - tabdata.selected = idx -end - local function main_button_handler(tabview, fields, name, tabdata) if fields.te_name then gamedata.playername = fields.te_name @@ -290,19 +346,23 @@ local function main_button_handler(tabview, fields, name, tabdata) gamedata.serverdescription = server.description if gamedata.address and gamedata.port then - core.settings:set("address", gamedata.address) - core.settings:set("remote_port", gamedata.port) + set_selected_server(server) core.start() end return true end if event.type == "CHG" then - set_selected_server(tabdata, event.row, server) + set_selected_server(server) return true end end end + if fields.btn_add_favorite then + serverlistmgr.add_favorite(find_selected_server()) + return true + end + if fields.btn_delete_favorite then local idx = core.get_table_index("servers") if not idx then return end @@ -310,8 +370,20 @@ local function main_button_handler(tabview, fields, name, tabdata) if not server then return end serverlistmgr.delete_favorite(server) - -- the server at [idx+1] will be at idx once list is refreshed - set_selected_server(tabdata, idx, tabdata.lookup[idx+1]) + set_selected_server(server) + return true + end + + if fields.btn_server_url then + core.open_url_dialog(find_selected_server().url) + return true + end + + if fields.btn_view_clients then + local dlg = create_clientslist_dialog(find_selected_server()) + dlg:set_parent(tabview) + tabview:hide() + dlg:show() return true end @@ -325,8 +397,8 @@ local function main_button_handler(tabview, fields, name, tabdata) tabdata.search_for = fields.te_search search_server_list(fields.te_search:lower()) if menudata.search_result then - -- first server in row 2 due to header - set_selected_server(tabdata, 2, menudata.search_result[1]) + -- Note: This clears the selection if there are no results + set_selected_server(menudata.search_result[1]) end return true @@ -353,8 +425,6 @@ local function main_button_handler(tabview, fields, name, tabdata) local idx = core.get_table_index("servers") local server = idx and tabdata.lookup[idx] - set_selected_server(tabdata) - if server and server.address == gamedata.address and server.port == gamedata.port then diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 41cb2cf8c..6fd3bee92 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -77,8 +77,11 @@ # Sections are marked by a single line in the format: [Section Name] # Sub-section are marked by adding * in front of the section name: [*Sub-section] # Sub-sub-sections have two * etc. -# There shouldn't be too much settings per category; settings that shouldn't be -# modified by the "average user" should be in (sub-)categories called "Advanced". +# There shouldn't be too many settings per category. +# +# The top-level categories "Advanced", "Client and Server" and "Mapgen" are +# handled specially and its contents only shown when a checkbox is checked. +# They contain settings not intended for the "average user". [Controls] @@ -410,10 +413,12 @@ anisotropic_filter (Anisotropic filtering) bool false # * None - No antialiasing (default) # # * FSAA - Hardware-provided full-screen antialiasing -# (incompatible with Post Processing and Undersampling) # A.K.A multi-sample antialiasing (MSAA) # Smoothens out block edges but does not affect the insides of textures. -# A restart is required to change this option. +# +# If Post Processing is disabled, changing FSAA requires a restart. +# Also, if Post Processing is disabled, FSAA will not work together with +# undersampling or a non-default "3d_mode" setting. # # * FXAA - Fast approximate antialiasing # Applies a post-processing filter to detect and smoothen high-contrast edges. @@ -469,6 +474,7 @@ performance_tradeoffs (Tradeoffs for performance) bool false # Adds particles when digging a node. enable_particles (Digging particles) bool true + [**Waving Nodes] # Set to true to enable waving leaves. @@ -535,12 +541,6 @@ shadow_map_texture_size (Shadow map texture size) int 2048 128 8192 # Requires: enable_dynamic_shadows, opengl shadow_map_texture_32bit (Shadow map texture in 32 bits) bool true -# Enable Poisson disk filtering. -# On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering. -# -# Requires: enable_dynamic_shadows, opengl -shadow_poisson_filter (Poisson filtering) bool true - # Define shadow filtering quality. # This simulates the soft shadows effect by applying a PCF or Poisson disk # but also uses more resources. @@ -554,14 +554,6 @@ shadow_filters (Shadow filter quality) enum 1 0,1,2 # Requires: enable_dynamic_shadows, opengl shadow_map_color (Colored shadows) bool false -# Spread a complete update of shadow map over given number of frames. -# Higher values might make shadows laggy, lower values -# will consume more resources. -# Minimum value: 1; maximum value: 16 -# -# Requires: enable_dynamic_shadows, opengl -shadow_update_frames (Map shadows update frames) int 8 1 16 - # Set the soft shadow radius size. # Lower values mean sharper shadows, bigger values mean softer shadows. # Minimum value: 1.0; maximum value: 15.0 @@ -805,7 +797,7 @@ contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3 1 enable_local_map_saving (Saving map received from server) bool false # URL to the server list displayed in the Multiplayer Tab. -serverlist_url (Serverlist URL) string servers.luanti.org +serverlist_url (Serverlist URL) string https://servers.luanti.org # If enabled, account registration is separate from login in the UI. # If disabled, new accounts will be registered automatically when logging in. @@ -843,7 +835,7 @@ server_announce (Announce server) bool false server_announce_send_players (Send player names to the server list) bool true # Announce to this serverlist. -serverlist_url (Serverlist URL) string servers.luanti.org +serverlist_url (Serverlist URL) string https://servers.luanti.org # Message of the day displayed to players connecting. motd (Message of the day) string @@ -1853,6 +1845,9 @@ ignore_world_load_errors (Ignore world errors) bool false [**Graphics] +# Enables debug and error-checking in the OpenGL driver. +opengl_debug (OpenGL debug) bool false + # Path to shader directory. If no path is defined, default location will be used. shader_path (Shader path) path @@ -1866,6 +1861,11 @@ video_driver (Video driver) enum ,opengl,opengl3,ogles2 # Set to 0 to disable it entirely. transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128 +# Draw transparency sorted triangles grouped by their mesh buffers. +# This breaks transparency sorting between mesh buffers, but avoids situations +# where transparency sorting would be very slow otherwise. +transparency_sorting_group_by_buffers (Transparency Sorting Group by Buffers) bool true + # Radius of cloud area stated in number of 64 node cloud squares. # Values larger than 26 will start to produce sharp cutoffs at cloud area corners. cloud_radius (Cloud radius) int 12 1 62 @@ -1919,8 +1919,26 @@ texture_min_size (Base texture size) int 64 1 32768 # Systems with a low-end GPU (or no GPU) would benefit from smaller values. client_mesh_chunk (Client Mesh Chunksize) int 1 1 16 -# Enables debug and error-checking in the OpenGL driver. -opengl_debug (OpenGL debug) bool false +# Decide the color depth of the texture used for the post-processing pipeline. +# Reducing this can improve performance, but might cause some effects (e.g. bloom) +# to not work. +# +# Requires: enable_post_processing +post_processing_texture_bits (Color depth for post-processing texture) enum 16 8,10,16 + +# Enable Poisson disk filtering. +# On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering. +# +# Requires: enable_dynamic_shadows, opengl +shadow_poisson_filter (Poisson filtering) bool true + +# Spread a complete update of shadow map over given number of frames. +# Higher values might make shadows laggy, lower values +# will consume more resources. +# Minimum value: 1; maximum value: 16 +# +# Requires: enable_dynamic_shadows, opengl +shadow_update_frames (Map shadows update frames) int 8 1 16 # Set to true to render debugging breakdown of the bloom effect. # In debug mode, the screen is split into 4 quadrants: diff --git a/client/shaders/default_shader/opengl_fragment.glsl b/client/shaders/default_shader/opengl_fragment.glsl deleted file mode 100644 index 300c0c589..000000000 --- a/client/shaders/default_shader/opengl_fragment.glsl +++ /dev/null @@ -1,6 +0,0 @@ -varying lowp vec4 varColor; - -void main(void) -{ - gl_FragData[0] = varColor; -} diff --git a/client/shaders/default_shader/opengl_vertex.glsl b/client/shaders/default_shader/opengl_vertex.glsl deleted file mode 100644 index d95a3c2d3..000000000 --- a/client/shaders/default_shader/opengl_vertex.glsl +++ /dev/null @@ -1,7 +0,0 @@ -varying lowp vec4 varColor; - -void main(void) -{ - gl_Position = mWorldViewProj * inVertexPosition; - varColor = inVertexColor; -} diff --git a/client/shaders/extract_bloom/opengl_fragment.glsl b/client/shaders/extract_bloom/opengl_fragment.glsl index 36671b06c..281884cee 100644 --- a/client/shaders/extract_bloom/opengl_fragment.glsl +++ b/client/shaders/extract_bloom/opengl_fragment.glsl @@ -23,7 +23,7 @@ void main(void) vec2 uv = varTexCoord.st; vec3 color = texture2D(rendered, uv).rgb; // translate to linear colorspace (approximate) - color = pow(color, vec3(2.2)); + color = pow(clamp(color, 0.0, 1.0), vec3(2.2)); color *= exposureParams.compensationFactor * bloomStrength; diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index edf5d5af7..871fbc2e4 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -1,10 +1,3 @@ -#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT) -#define MATERIAL_WAVING_LIQUID 1 -#define MATERIAL_LIQUID 1 -#elif (MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE) -#define MATERIAL_LIQUID 1 -#endif - uniform sampler2D baseTexture; uniform vec2 texelSize0; @@ -60,7 +53,7 @@ varying highp vec3 eyeVec; varying float nightRatio; #ifdef ENABLE_DYNAMIC_SHADOWS -#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS)) +#if (MATERIAL_WAVING_LIQUID && defined(ENABLE_WATER_REFLECTIONS)) vec4 perm(vec4 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); @@ -511,8 +504,7 @@ void main(void) vec3 reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0); // Water reflections -#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS)) - +#if (MATERIAL_WAVING_LIQUID && defined(ENABLE_WATER_REFLECTIONS)) vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0); float off = animationTimer * WATER_WAVE_SPEED * 10.0; wavePos.x /= WATER_WAVE_LENGTH * 3.0; @@ -542,7 +534,7 @@ void main(void) col.rgb += water_reflect_color * brightness_factor; #endif -#if (defined(ENABLE_NODE_SPECULAR) && !defined(MATERIAL_WAVING_LIQUID)) +#if (defined(ENABLE_NODE_SPECULAR) && !MATERIAL_WAVING_LIQUID) // Apply specular to blocks. if (dot(v_LightDirection, vNormal) < 0.0) { // This intensity is a placeholder and should be replaced by proper specular maps. diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 1d59fb99e..7b8f1a106 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -113,8 +113,7 @@ float smoothTriangleWave(float x) return smoothCurve(triangleWave(x)) * 2.0 - 1.0; } -// OpenGL < 4.3 does not support continued preprocessor lines -#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC) && ENABLE_WAVING_WATER +#if MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER // // Simple, fast noise function. @@ -180,8 +179,7 @@ void main(void) #endif vec4 pos = inVertexPosition; -// OpenGL < 4.3 does not support continued preprocessor lines -#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC) && ENABLE_WAVING_WATER +#if MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER // Generate waves with Perlin-type noise. // The constants are calibrated such that they roughly // correspond to the old sine waves. diff --git a/doc/android.md b/doc/android.md index 65cc0440c..353a7d1c8 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.minetest.net/Accessing_Android_Data_Directory) +* [Learn more about Android directory](https://wiki.luanti.org/Accessing_Android_Data_Directory) ## Useful settings diff --git a/doc/breakages.md b/doc/breakages.md index 0aded3d92..2d2994385 100644 --- a/doc/breakages.md +++ b/doc/breakages.md @@ -21,3 +21,4 @@ This list is largely advisory and items may be reevaluated once the time comes. * merge `sound` and `sounds` table in itemdef * remove `DIR_DELIM` from Lua * stop reading initial properties from bare entity def +* change particle default blend mode to `clip` diff --git a/doc/compiling/README.md b/doc/compiling/README.md index 55357adf6..16167977b 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -30,6 +30,7 @@ General options and their default values: ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended) ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores + ENABLE_OPENSSL=ON - Build with OpenSSL; Speeds up SHA1 and SHA2 hashing ENABLE_SOUND=ON - Build with OpenAL, libogg & libvorbis; in-game sounds ENABLE_LTO= - Build with IPO/LTO optimizations (smaller and more efficient than regular build) ENABLE_LUAJIT=ON - Build with LuaJIT (much faster than non-JIT Lua) diff --git a/doc/compiling/linux.md b/doc/compiling/linux.md index 573c6908e..54a44d501 100644 --- a/doc/compiling/linux.md +++ b/doc/compiling/linux.md @@ -18,6 +18,7 @@ | JsonCPP | 1.0.0+ | Bundled JsonCPP is used if not present | | Curl | 7.56.0+ | Optional | | gettext | - | Optional | +| OpenSSL | 3.0+ | Optional (only libcrypto used) | For Debian/Ubuntu users: @@ -33,7 +34,7 @@ For openSUSE users: For Arch users: - sudo pacman -S --needed base-devel libcurl-gnutls cmake libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd gettext sdl2 + sudo pacman -S --needed base-devel libcurl-gnutls cmake libpng libjpeg-turbo sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd gettext sdl2 For Alpine users: diff --git a/doc/developing/README.md b/doc/developing/README.md index 7e84de904..1aa424b00 100644 --- a/doc/developing/README.md +++ b/doc/developing/README.md @@ -23,4 +23,4 @@ Notable pages: 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.minetest.net/IRC) and ask questions related to **engine development**. +Feel free to join the [#minetest-dev IRC](https://wiki.luanti.org/IRC) and ask questions related to **engine development**. diff --git a/doc/lua_api.md b/doc/lua_api.md index ec22aed99..cad87a1a2 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -309,17 +309,24 @@ it unlocks no special rendering features. Binary glTF (`.glb`) files are supported and recommended over `.gltf` files due to their space savings. -This means that many glTF features are not supported *yet*, including: +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). + +You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator) +to check whether a model is a valid glTF file. + +Many glTF features are not supported *yet*, including: * Animations * Only a single animation is supported, use frame ranges within this animation. + * Only linear interpolation is supported. * Cameras * Materials * Only base color textures are supported * Backface culling is overridden * Double-sided materials don't work * Alternative means of supplying data - * Embedded images + * Embedded images. You can use `gltfutil.py` from the + [modding tools](https://github.com/minetest/modtools) to strip or extract embedded images. * References to files via URIs Textures are supplied solely via the same means as for the other model file formats: @@ -4183,14 +4190,14 @@ Two functions are provided to translate strings: `core.translate` and * `core.get_translator(textdomain)` is a simple wrapper around `core.translate` and `core.translate_n`. - After `local S, NS = core.get_translator(textdomain)`, we have + After `local S, PS = core.get_translator(textdomain)`, we have `S(str, ...)` equivalent to `core.translate(textdomain, str, ...)`, and - `NS(str, str_plural, n, ...)` to `core.translate_n(textdomain, str, str_plural, n, ...)`. + `PS(str, str_plural, n, ...)` to `core.translate_n(textdomain, str, str_plural, n, ...)`. It is intended to be used in the following way, so that it avoids verbose repetitions of `core.translate`: ```lua - local S, NS = core.get_translator(textdomain) + local S, PS = core.get_translator(textdomain) S(str, ...) ``` @@ -4224,7 +4231,7 @@ command that shows the amount of time since the player joined. We can do the following: ```lua -local S, NS = core.get_translator("hello") +local S, PS = core.get_translator("hello") core.register_on_joinplayer(function(player) local name = player:get_player_name() core.chat_send_player(name, S("Hello @1, how are you today?", name)) @@ -4233,7 +4240,7 @@ core.register_chatcommand("playtime", { func = function(name) local last_login = core.get_auth_handler().get_auth(name).last_login local playtime = math.floor((last_login-os.time())/60) - return true, NS( + return true, PS( "You have been playing for @1 minute.", "You have been playing for @1 minutes.", minutes, tostring(minutes)) @@ -4280,7 +4287,7 @@ After creating the `locale` directory, a translation template for the above example using the following command: ```sh -xgettext -L lua -kS -kNS:1,2 -kcore.translate:1c,2 -kcore.translate_n:1c,2,3 \ +xgettext -L lua -kS -kPS:1,2 -kcore.translate:1c,2 -kcore.translate_n:1c,2,3 \ -d hello -o locale/hello.pot *.lua ``` @@ -5657,6 +5664,8 @@ Utilities abm_without_neighbors = true, -- biomes have a weight parameter (5.11.0) biome_weights = true, + -- Particles can specify a "clip" blend mode (5.11.0) + particle_blend_clip = true, } ``` @@ -6178,7 +6187,7 @@ Setting-related * `core.settings`: Settings object containing all of the settings from the main config file (`minetest.conf`). See [`Settings`]. * `core.setting_get_pos(name)`: Loads a setting from the main settings and - parses it as a position (in the format `(1,2,3)`). Returns a position or nil. + parses it as a position (in the format `(1,2,3)`). Returns a position or nil. **Deprecated: use `core.settings:get_pos()` instead** Authentication -------------- @@ -6557,7 +6566,7 @@ Environment access * `pointabilities`: Allows overriding the `pointable` property of nodes and objects. Uses the same format as the `pointabilities` property of item definitions. Default is `nil`. -* `core.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)` +* `core.find_path(pos1, pos2, searchdistance, max_jump, max_drop, algorithm)` * returns table containing path that can be walked on * returns a table of 3D points representing a path from `pos1` to `pos2` or `nil` on failure. @@ -6577,8 +6586,11 @@ Environment access Difference between `"A*"` and `"A*_noprefetch"` is that `"A*"` will pre-calculate the cost-data, the other will calculate it on-the-fly -* `core.spawn_tree (pos, {treedef})` +* `core.spawn_tree(pos, treedef)` * spawns L-system tree at given `pos` with definition in `treedef` table +* `core.spawn_tree_on_vmanip(vmanip, pos, treedef)` + * analogous to `core.spawn_tree`, but spawns a L-system tree onto the specified + VoxelManip object `vmanip` instead of the map. * `core.transforming_liquid_add(pos)` * add node to liquid flow update queue * `core.get_node_max_level(pos)` @@ -6697,6 +6709,9 @@ Formspec * `core.hypertext_escape(string)`: returns a string * escapes the characters "\", "<", and ">" to show text in a hypertext element. * not safe for use with tag attributes. + * this function does not do formspec escaping, you will likely need to do + `core.formspec_escape(core.hypertext_escape(string))` if the hypertext is + not already being formspec escaped. * `core.explode_table_event(string)`: returns a table * returns e.g. `{type="CHG", row=1, column=2}` * `type` is one of: @@ -8909,7 +8924,7 @@ For `core.get_perlin_map()`, the actual seed used is the noiseparams seed plus the world seed, to create world-specific noise. Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted -for 2D noise, and it must be must be larger than 1 for 3D noise (otherwise +for 2D noise, and it must be larger than 1 for 3D noise (otherwise `nil` is returned). For each of the functions with an optional `buffer` parameter: If `buffer` is @@ -9065,6 +9080,9 @@ means that no defaults will be returned for mod settings. * Is currently limited to mapgen flags `mg_flags` and mapgen-specific flags like `mgv5_spflags`. * Returns `nil` if `key` is not found. +* `get_pos(key)`: + * Returns a `vector` + * Returns `nil` if no value is found or parsing failed. * `set(key, value)` * Setting names can't contain whitespace or any of `="{}#`. * Setting values can't contain the sequence `\n"""`. @@ -9075,6 +9093,9 @@ means that no defaults will be returned for mod settings. * `set_np_group(key, value)` * `value` is a NoiseParams table. * Also, see documentation for `set()` above. +* `set_pos(key, value)` + * `value` is a `vector`. + * Also, see documentation for `set()` above. * `remove(key)`: returns a boolean (`true` for success) * `get_names()`: returns `{key1,...}` * `has(key)`: @@ -11502,6 +11523,14 @@ texture = { -- (default) blends transparent pixels with those they are drawn atop -- according to the alpha channel of the source texture. useful for -- e.g. material objects like rocks, dirt, smoke, or node chunks + -- note: there will be rendering bugs when particles interact with + -- translucent nodes. particles are also not transparency-sorted + -- relative to each other. + blend = "clip", + -- pixels are either fully opaque or fully transparent, + -- depending on whether alpha is greater than or less than 50% + -- (just like `use_texture_alpha = "clip"` for nodes). + -- you should prefer this if you don't need semi-transparency, as it's faster. blend = "add", -- adds the value of pixels to those underneath them, modulo the sources -- alpha channel. useful for e.g. bright light effects like sparks or fire diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index ae4afd998..b17e4b1c8 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -223,6 +223,7 @@ GUI * `core.set_clouds()` * `core.set_topleft_text(text)` * `core.show_keys_menu()` +* `core.show_touchscreen_layout()` * `core.show_path_select_dialog(formname, caption, is_file_select)` * shows a path select dialog * `formname` is base name of dialog response returned in fields diff --git a/games/devtest/mods/testentities/models/LICENSE.txt b/games/devtest/mods/testentities/models/LICENSE.txt index 4317d68d3..19ffffc5c 100644 --- a/games/devtest/mods/testentities/models/LICENSE.txt +++ b/games/devtest/mods/testentities/models/LICENSE.txt @@ -1,3 +1,5 @@ +"Minetest Sam": + Original model by MirceaKitsune (CC BY-SA 3.0). Various alterations and fixes by kilbith, sofar, xunto, Rogier-5, TeTpaAka, Desour, stujones11, An0n3m0us (CC BY-SA 3.0): @@ -5,3 +7,9 @@ stujones11, An0n3m0us (CC BY-SA 3.0): Jordach (CC BY-SA 3.0): testentities_sam.png + +"Lava Flan": + +Zeg9 (CC BY-SA 3.0): + testentities_lava_flan.x + testentities_lava_flan.png \ No newline at end of file diff --git a/games/devtest/mods/testentities/models/testentities_lava_flan.png b/games/devtest/mods/testentities/models/testentities_lava_flan.png new file mode 100644 index 000000000..8cf37238f Binary files /dev/null and b/games/devtest/mods/testentities/models/testentities_lava_flan.png differ diff --git a/games/devtest/mods/testentities/models/testentities_lava_flan.x b/games/devtest/mods/testentities/models/testentities_lava_flan.x new file mode 100644 index 000000000..be78ff97e --- /dev/null +++ b/games/devtest/mods/testentities/models/testentities_lava_flan.x @@ -0,0 +1,3506 @@ +xof 0303txt 0032 + +template XSkinMeshHeader { + <3cf169ce-ff7c-44ab-93c0-f78f62d172e2> + WORD nMaxSkinWeightsPerVertex; + WORD nMaxSkinWeightsPerFace; + WORD nBones; +} + +template SkinWeights { + <6f0d123b-bad2-4167-a0d0-80224f25fabb> + STRING transformNodeName; + DWORD nWeights; + array DWORD vertexIndices[nWeights]; + array float weights[nWeights]; + Matrix4x4 matrixOffset; +} + +Frame Root { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000,-0.000000, 1.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 1.000000;; + } + Frame Armature { + FrameTransformMatrix { + -5.000000, 0.000001, 0.000000, 0.000000, + -0.000001,-5.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 5.000000, 0.000000, + 0.000000, 0.000000,-5.000000, 1.000000;; + } + Frame Armature_Bone { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000,-1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 1.000000;; + } + Frame Armature_Bone_001 { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000, 2.000000, 0.000000, 1.000000;; + } + Frame Armature_Bone_002 { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000, 0.300000, 0.000000, 1.000000;; + } + Frame Armature_Bone_003 { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000, 0.300000, 0.000000, 1.000000;; + } + Frame Armature_Bone_004 { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000, 0.400000, 0.000000, 1.000000;; + } + } // End of Armature_Bone_004 + } // End of Armature_Bone_003 + } // End of Armature_Bone_002 + } // End of Armature_Bone_001 + } // End of Armature_Bone + Frame Cube { + FrameTransformMatrix { + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 1.000000;; + } + Mesh { // Cube mesh + 264; + 1.000000; 1.000000;-1.000000;, + 1.000000;-1.000000;-1.000000;, + -1.000000;-1.000000;-1.000000;, + -1.000000; 1.000000;-1.000000;, + -1.000000; 1.000000; 1.000000;, + -1.000000;-1.000000; 1.000000;, + -0.800000;-0.800000; 1.000000;, + -0.800000; 0.800000; 1.000000;, + 1.000000; 1.000000;-1.000000;, + 1.000000; 0.999999; 1.000000;, + 0.999999;-1.000001; 1.000000;, + 1.000000;-1.000000;-1.000000;, + 1.000000;-1.000000;-1.000000;, + 0.999999;-1.000001; 1.000000;, + -1.000000;-1.000000; 1.000000;, + -1.000000;-1.000000;-1.000000;, + -1.000000;-1.000000;-1.000000;, + -1.000000;-1.000000; 1.000000;, + -1.000000; 1.000000; 1.000000;, + -1.000000; 1.000000;-1.000000;, + 1.000000; 0.999999; 1.000000;, + 1.000000; 1.000000;-1.000000;, + -1.000000; 1.000000;-1.000000;, + -1.000000; 1.000000; 1.000000;, + -0.800000; 0.800000; 1.666667;, + -0.800000;-0.800000; 1.666667;, + -0.800000;-0.800000; 2.000000;, + -0.800000; 0.800000; 2.000000;, + 0.999999;-1.000001; 1.000000;, + 1.000000; 0.999999; 1.000000;, + 0.800000; 0.800000; 1.000000;, + 0.799999;-0.800001; 1.000000;, + 1.000000; 0.999999; 1.000000;, + -1.000000; 1.000000; 1.000000;, + -0.800000; 0.800000; 1.000000;, + 0.800000; 0.800000; 1.000000;, + -1.000000;-1.000000; 1.000000;, + 0.999999;-1.000001; 1.000000;, + 0.799999;-0.800001; 1.000000;, + -0.800000;-0.800000; 1.000000;, + -0.800000;-0.800000; 2.000000;, + 0.799999;-0.800001; 2.000000;, + 0.640000;-0.640000; 2.000000;, + -0.640000;-0.640000; 2.000000;, + 0.799999;-0.800001; 1.666667;, + 0.800000; 0.800000; 1.666667;, + 0.800000; 0.800000; 2.000000;, + 0.799999;-0.800001; 2.000000;, + 0.800000; 0.800000; 1.666667;, + -0.800000; 0.800000; 1.666667;, + -0.800000; 0.800000; 2.000000;, + 0.800000; 0.800000; 2.000000;, + -0.800000;-0.800000; 1.666667;, + 0.799999;-0.800001; 1.666667;, + 0.799999;-0.800001; 2.000000;, + -0.800000;-0.800000; 2.000000;, + 0.640000; 0.640000; 2.000000;, + -0.640000; 0.640000; 2.000000;, + -0.640000; 0.640000; 2.500000;, + 0.640000; 0.640000; 2.500000;, + -0.800000; 0.800000; 2.000000;, + -0.800000;-0.800000; 2.000000;, + -0.640000;-0.640000; 2.000000;, + -0.640000; 0.640000; 2.000000;, + 0.799999;-0.800001; 2.000000;, + 0.800000; 0.800000; 2.000000;, + 0.640000; 0.640000; 2.000000;, + 0.640000;-0.640000; 2.000000;, + 0.800000; 0.800000; 2.000000;, + -0.800000; 0.800000; 2.000000;, + -0.640000; 0.640000; 2.000000;, + 0.640000; 0.640000; 2.000000;, + 0.640000; 0.640000; 2.500000;, + -0.640000; 0.640000; 2.500000;, + -0.640000;-0.640000; 2.500000;, + 0.640000;-0.640000; 2.500000;, + -0.640000;-0.640000; 2.000000;, + 0.640000;-0.640000; 2.000000;, + 0.640000;-0.640000; 2.500000;, + -0.640000;-0.640000; 2.500000;, + -0.640000; 0.640000; 2.000000;, + -0.640000;-0.640000; 2.000000;, + -0.640000;-0.640000; 2.500000;, + -0.640000; 0.640000; 2.500000;, + 0.640000;-0.640000; 2.000000;, + 0.640000; 0.640000; 2.000000;, + 0.640000; 0.640000; 2.500000;, + 0.640000;-0.640000; 2.500000;, + -0.800000; 0.800000; 1.000000;, + -0.800000;-0.800000; 1.000000;, + -0.800000;-0.800000; 1.333333;, + -0.800000; 0.800000; 1.333333;, + -0.800000; 0.800000; 1.333333;, + -0.800000;-0.800000; 1.333333;, + -0.800000;-0.800000; 1.666667;, + -0.800000; 0.800000; 1.666667;, + 0.799999;-0.800001; 1.000000;, + 0.800000; 0.800000; 1.000000;, + 0.800000; 0.800000; 1.333333;, + 0.799999;-0.800001; 1.333333;, + 0.799999;-0.800001; 1.333333;, + 0.800000; 0.800000; 1.333333;, + 0.800000; 0.800000; 1.666667;, + 0.799999;-0.800001; 1.666667;, + 0.800000; 0.800000; 1.000000;, + -0.800000; 0.800000; 1.000000;, + -0.800000; 0.800000; 1.333333;, + 0.800000; 0.800000; 1.333333;, + 0.800000; 0.800000; 1.333333;, + -0.800000; 0.800000; 1.333333;, + -0.800000; 0.800000; 1.666667;, + 0.800000; 0.800000; 1.666667;, + -0.800000;-0.800000; 1.000000;, + 0.799999;-0.800001; 1.000000;, + 0.799999;-0.800001; 1.333333;, + -0.800000;-0.800000; 1.333333;, + 0.799999;-0.800001; 1.333333;, + 0.799999;-0.800001; 1.666667;, + 0.560000;-0.000000; 1.616667;, + 0.560000;-0.000000; 1.383333;, + -0.560000; 0.000000; 1.383333;, + 0.560000;-0.000000; 1.383333;, + 0.560000;-0.000000; 1.616667;, + -0.560000; 0.000000; 1.616667;, + 0.799999;-0.800001; 1.666667;, + -0.800000;-0.800000; 1.666667;, + -0.560000; 0.000000; 1.616667;, + 0.560000;-0.000000; 1.616667;, + -0.800000;-0.800000; 1.666667;, + -0.800000;-0.800000; 1.333333;, + -0.560000; 0.000000; 1.383333;, + -0.560000; 0.000000; 1.616667;, + -0.800000;-0.800000; 1.333333;, + 0.799999;-0.800001; 1.333333;, + 0.560000;-0.000000; 1.383333;, + -0.560000; 0.000000; 1.383333;, + -0.000000;-0.790000; 1.340000;, + -0.000000;-0.690000; 1.540000;, + -0.100000;-0.690000; 1.340000;, + -0.000000;-0.590000; 1.340000;, + -0.000000;-0.690000; 1.540000;, + 0.100000;-0.690000; 1.340000;, + 0.400000;-0.590000; 1.340000;, + 0.500000;-0.690000; 1.340000;, + 0.400000;-0.790000; 1.340000;, + 0.300000;-0.690000; 1.340000;, + -0.100000;-0.690000; 1.340000;, + -0.000000;-0.690000; 1.540000;, + -0.000000;-0.590000; 1.340000;, + 0.100000;-0.690000; 1.340000;, + -0.000000;-0.690000; 1.540000;, + -0.000000;-0.790000; 1.340000;, + 0.200000;-0.590000; 1.340000;, + 0.300000;-0.690000; 1.340000;, + 0.200000;-0.790000; 1.340000;, + 0.100000;-0.690000; 1.340000;, + 0.300000;-0.690000; 1.340000;, + 0.200000;-0.690000; 1.540000;, + 0.200000;-0.790000; 1.340000;, + 0.100000;-0.690000; 1.340000;, + 0.200000;-0.690000; 1.540000;, + 0.200000;-0.590000; 1.340000;, + 0.200000;-0.590000; 1.340000;, + 0.200000;-0.690000; 1.540000;, + 0.300000;-0.690000; 1.340000;, + 0.500000;-0.690000; 1.340000;, + 0.400000;-0.690000; 1.540000;, + 0.400000;-0.790000; 1.340000;, + 0.200000;-0.790000; 1.340000;, + 0.200000;-0.690000; 1.540000;, + 0.100000;-0.690000; 1.340000;, + -0.000000;-0.590000; 1.340000;, + 0.100000;-0.690000; 1.340000;, + -0.000000;-0.790000; 1.340000;, + -0.100000;-0.690000; 1.340000;, + 0.300000;-0.690000; 1.340000;, + 0.400000;-0.690000; 1.540000;, + 0.400000;-0.590000; 1.340000;, + 0.400000;-0.590000; 1.340000;, + 0.400000;-0.690000; 1.540000;, + 0.500000;-0.690000; 1.340000;, + 0.400000;-0.790000; 1.340000;, + 0.400000;-0.690000; 1.540000;, + 0.300000;-0.690000; 1.340000;, + -0.200000;-0.590000; 1.340000;, + -0.100000;-0.690000; 1.340000;, + -0.200000;-0.790000; 1.340000;, + -0.300000;-0.690000; 1.340000;, + -0.100000;-0.690000; 1.340000;, + -0.200000;-0.690000; 1.540000;, + -0.200000;-0.790000; 1.340000;, + -0.300000;-0.690000; 1.340000;, + -0.200000;-0.690000; 1.540000;, + -0.200000;-0.590000; 1.340000;, + -0.200000;-0.590000; 1.340000;, + -0.200000;-0.690000; 1.540000;, + -0.100000;-0.690000; 1.340000;, + -0.200000;-0.790000; 1.340000;, + -0.200000;-0.690000; 1.540000;, + -0.300000;-0.690000; 1.340000;, + -0.400000;-0.590000; 1.340000;, + -0.300000;-0.690000; 1.340000;, + -0.400000;-0.790000; 1.340000;, + -0.500000;-0.690000; 1.340000;, + -0.300000;-0.690000; 1.340000;, + -0.400000;-0.690000; 1.540000;, + -0.400000;-0.790000; 1.340000;, + -0.500000;-0.690000; 1.340000;, + -0.400000;-0.690000; 1.540000;, + -0.400000;-0.590000; 1.340000;, + -0.400000;-0.590000; 1.340000;, + -0.400000;-0.690000; 1.540000;, + -0.300000;-0.690000; 1.340000;, + -0.400000;-0.790000; 1.340000;, + -0.400000;-0.690000; 1.540000;, + -0.500000;-0.690000; 1.340000;, + 0.200000;-0.700000; 2.400000;, + 0.200000;-0.500000; 2.400000;, + 0.200000;-0.500000; 2.200000;, + 0.200000;-0.700000; 2.200000;, + 0.200000;-0.500000; 2.400000;, + 0.400000;-0.500000; 2.400000;, + 0.400000;-0.500000; 2.200000;, + 0.200000;-0.500000; 2.200000;, + 0.400000;-0.500000; 2.400000;, + 0.400000;-0.700000; 2.400000;, + 0.400000;-0.700000; 2.200000;, + 0.400000;-0.500000; 2.200000;, + 0.400000;-0.700000; 2.400000;, + 0.200000;-0.700000; 2.400000;, + 0.200000;-0.700000; 2.200000;, + 0.400000;-0.700000; 2.200000;, + 0.200000;-0.700000; 2.200000;, + 0.200000;-0.500000; 2.200000;, + 0.400000;-0.500000; 2.200000;, + 0.400000;-0.700000; 2.200000;, + 0.400000;-0.700000; 2.400000;, + 0.400000;-0.500000; 2.400000;, + 0.200000;-0.500000; 2.400000;, + 0.200000;-0.700000; 2.400000;, + -0.400000;-0.700000; 2.400000;, + -0.400000;-0.500000; 2.400000;, + -0.400000;-0.500000; 2.200000;, + -0.400000;-0.700000; 2.200000;, + -0.400000;-0.500000; 2.400000;, + -0.200000;-0.500000; 2.400000;, + -0.200000;-0.500000; 2.200000;, + -0.400000;-0.500000; 2.200000;, + -0.200000;-0.500000; 2.400000;, + -0.200000;-0.700000; 2.400000;, + -0.200000;-0.700000; 2.200000;, + -0.200000;-0.500000; 2.200000;, + -0.200000;-0.700000; 2.400000;, + -0.400000;-0.700000; 2.400000;, + -0.400000;-0.700000; 2.200000;, + -0.200000;-0.700000; 2.200000;, + -0.400000;-0.700000; 2.200000;, + -0.400000;-0.500000; 2.200000;, + -0.200000;-0.500000; 2.200000;, + -0.200000;-0.700000; 2.200000;, + -0.200000;-0.700000; 2.400000;, + -0.200000;-0.500000; 2.400000;, + -0.400000;-0.500000; 2.400000;, + -0.400000;-0.700000; 2.400000;; + 71; + 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;, + 3;138;137;136;, + 3;141;140;139;, + 4;145;144;143;142;, + 3;148;147;146;, + 3;151;150;149;, + 4;155;154;153;152;, + 3;158;157;156;, + 3;161;160;159;, + 3;164;163;162;, + 3;167;166;165;, + 3;170;169;168;, + 4;174;173;172;171;, + 3;177;176;175;, + 3;180;179;178;, + 3;183;182;181;, + 4;187;186;185;184;, + 3;190;189;188;, + 3;193;192;191;, + 3;196;195;194;, + 3;199;198;197;, + 4;203;202;201;200;, + 3;206;205;204;, + 3;209;208;207;, + 3;212;211;210;, + 3;215;214;213;, + 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;; + MeshNormals { // Cube normals + 71; + 0.000000; 0.000000;-1.000000;, + 0.000000; 0.000000; 1.000000;, + 1.000000;-0.000000; 0.000000;, + -0.000000;-1.000000;-0.000000;, + -1.000000; 0.000000;-0.000000;, + 0.000000; 1.000000; 0.000000;, + -1.000000; 0.000000; 0.000000;, + -0.000000; 0.000000; 1.000000;, + 0.000000;-0.000000; 1.000000;, + 0.000000; 0.000000; 1.000000;, + 0.000000; 0.000000; 1.000000;, + 1.000000;-0.000001; 0.000000;, + 0.000000; 1.000000; 0.000000;, + -0.000000;-1.000000; 0.000000;, + 0.000000; 1.000000; 0.000000;, + 0.000000; 0.000000; 1.000000;, + -0.000000; 0.000000; 1.000000;, + 0.000000;-0.000000; 1.000000;, + 0.000000;-0.000000; 1.000000;, + -0.000000;-1.000000; 0.000000;, + -1.000000; 0.000000; 0.000000;, + 1.000000;-0.000001; 0.000000;, + -1.000000; 0.000000; 0.000000;, + -1.000000; 0.000000; 0.000000;, + 1.000000;-0.000001; 0.000000;, + 1.000000;-0.000001; 0.000000;, + 0.000000; 1.000000; 0.000000;, + 0.000000; 1.000000; 0.000000;, + -0.000000;-1.000000; 0.000000;, + -0.957826;-0.287348; 0.000000;, + -0.000000;-1.000000; 0.000000;, + -0.000000;-0.062378;-0.998053;, + 0.957826;-0.287348; 0.000000;, + 0.000000;-0.062378; 0.998053;, + -0.666667;-0.666667; 0.333333;, + 0.666667; 0.666666; 0.333333;, + 0.000000; 0.000000;-1.000000;, + -0.666667; 0.666667; 0.333333;, + 0.666667;-0.666667; 0.333333;, + 0.000000; 0.000000;-1.000000;, + 0.666667;-0.666667; 0.333333;, + -0.666667; 0.666667; 0.333333;, + 0.666667; 0.666667; 0.333333;, + 0.666667;-0.666667; 0.333333;, + -0.666667;-0.666667; 0.333333;, + 0.000000; 0.000000;-1.000000;, + -0.666667; 0.666667; 0.333333;, + 0.666667; 0.666667; 0.333333;, + -0.666667;-0.666667; 0.333333;, + 0.000000; 0.000000;-1.000000;, + 0.666667;-0.666667; 0.333333;, + -0.666667; 0.666667; 0.333333;, + 0.666667; 0.666667; 0.333333;, + -0.666667;-0.666667; 0.333333;, + 0.000000; 0.000000;-1.000000;, + 0.666667;-0.666667; 0.333333;, + -0.666667; 0.666667; 0.333333;, + 0.666667; 0.666667; 0.333333;, + -0.666667;-0.666667; 0.333333;, + -1.000000; 0.000000; 0.000000;, + 0.000000; 1.000000;-0.000000;, + 1.000000; 0.000000;-0.000000;, + 0.000000;-1.000000; 0.000000;, + -0.000000; 0.000000;-1.000000;, + -0.000000; 0.000000; 1.000000;, + -1.000000; 0.000000; 0.000000;, + 0.000000; 1.000000;-0.000000;, + 1.000000; 0.000000;-0.000000;, + 0.000000;-1.000000; 0.000000;, + -0.000000; 0.000000;-1.000000;, + -0.000000; 0.000000; 1.000000;; + 71; + 4;0;0;0;0;, + 4;1;1;1;1;, + 4;2;2;2;2;, + 4;3;3;3;3;, + 4;4;4;4;4;, + 4;5;5;5;5;, + 4;6;6;6;6;, + 4;7;7;7;7;, + 4;8;8;8;8;, + 4;9;9;9;9;, + 4;10;10;10;10;, + 4;11;11;11;11;, + 4;12;12;12;12;, + 4;13;13;13;13;, + 4;14;14;14;14;, + 4;15;15;15;15;, + 4;16;16;16;16;, + 4;17;17;17;17;, + 4;18;18;18;18;, + 4;19;19;19;19;, + 4;20;20;20;20;, + 4;21;21;21;21;, + 4;22;22;22;22;, + 4;23;23;23;23;, + 4;24;24;24;24;, + 4;25;25;25;25;, + 4;26;26;26;26;, + 4;27;27;27;27;, + 4;28;28;28;28;, + 4;29;29;29;29;, + 4;30;30;30;30;, + 4;31;31;31;31;, + 4;32;32;32;32;, + 4;33;33;33;33;, + 3;34;34;34;, + 3;35;35;35;, + 4;36;36;36;36;, + 3;37;37;37;, + 3;38;38;38;, + 4;39;39;39;39;, + 3;40;40;40;, + 3;41;41;41;, + 3;42;42;42;, + 3;43;43;43;, + 3;44;44;44;, + 4;45;45;45;45;, + 3;46;46;46;, + 3;47;47;47;, + 3;48;48;48;, + 4;49;49;49;49;, + 3;50;50;50;, + 3;51;51;51;, + 3;52;52;52;, + 3;53;53;53;, + 4;54;54;54;54;, + 3;55;55;55;, + 3;56;56;56;, + 3;57;57;57;, + 3;58;58;58;, + 4;59;59;59;59;, + 4;60;60;60;60;, + 4;61;61;61;61;, + 4;62;62;62;62;, + 4;63;63;63;63;, + 4;64;64;64;64;, + 4;65;65;65;65;, + 4;66;66;66;66;, + 4;67;67;67;67;, + 4;68;68;68;68;, + 4;69;69;69;69;, + 4;70;70;70;70;; + } // End of Cube normals + MeshTextureCoords { // Cube UV coordinates + 264; + 0.000000; 0.500000;, + 0.500000; 0.500000;, + 0.500000; 0.000000;, + 0.000000; 0.000000;, + -0.000104; 0.000450;, + -0.000014; 0.499950;, + 0.058184; 0.442464;, + 0.057363; 0.058619;, + 0.000000; 0.500000;, + 0.500000; 0.500000;, + 0.500000; 0.000000;, + 0.000000; 0.000000;, + 0.000000; 0.500000;, + 0.500000; 0.500000;, + 0.500000; 0.000000;, + 0.000000; 0.000000;, + 0.000000; 0.500000;, + 0.500000; 0.500000;, + 0.500000; 0.000000;, + 0.000000; 0.000000;, + 0.000000; 0.500000;, + 0.500000; 0.500000;, + 0.500000; 0.000000;, + 0.000000; 0.000000;, + 0.499960; 0.416620;, + 0.000040; 0.416620;, + 0.000040; 0.624921;, + 0.499960; 0.624921;, + 0.499489; 0.499550;, + 0.499399; 0.000050;, + 0.441201; 0.057536;, + 0.442021; 0.441381;, + 0.499399; 0.000050;, + -0.000104; 0.000450;, + 0.057363; 0.058619;, + 0.441201; 0.057536;, + -0.000014; 0.499950;, + 0.499489; 0.499550;, + 0.442021; 0.441381;, + 0.058184; 0.442464;, + -0.000014; 0.499950;, + 0.499489; 0.499550;, + 0.442022; 0.441382;, + 0.058184; 0.442464;, + 0.000040; 0.208320;, + 0.499960; 0.208320;, + 0.499960; 0.000020;, + 0.000040; 0.000020;, + 0.499960; 0.416621;, + 0.000040; 0.416620;, + 0.000040; 0.624921;, + 0.499960; 0.624921;, + 0.000046; 0.499908;, + 0.499954; 0.499909;, + 0.499954; 0.291613;, + 0.000046; 0.291613;, + 0.499969;-0.015638;, + 0.000031;-0.015639;, + 0.000031; 0.374937;, + 0.499969; 0.374938;, + -0.000104; 0.000450;, + -0.000014; 0.499950;, + 0.058184; 0.442464;, + 0.057364; 0.058619;, + 0.499489; 0.499550;, + 0.499399; 0.000050;, + 0.441201; 0.057536;, + 0.442022; 0.441382;, + 0.499399; 0.000050;, + -0.000104; 0.000450;, + 0.057364; 0.058619;, + 0.441201; 0.057536;, + 0.499950; 0.000050;, + 0.000050; 0.000050;, + 0.000050; 0.499950;, + 0.499950; 0.499950;, + 0.000031; 0.374937;, + 0.499969; 0.374938;, + 0.499969;-0.015638;, + 0.000031;-0.015639;, + 0.499969;-0.015639;, + 0.000031;-0.015639;, + 0.000031; 0.374937;, + 0.499969; 0.374938;, + 0.000031; 0.374937;, + 0.499969; 0.374938;, + 0.499969;-0.015639;, + 0.000031;-0.015639;, + 0.499960; 0.000019;, + 0.000040; 0.000019;, + 0.000040; 0.208320;, + 0.499960; 0.208320;, + 0.499960; 0.208320;, + 0.000040; 0.208320;, + 0.000040; 0.416620;, + 0.499960; 0.416620;, + 0.000040; 0.624921;, + 0.499960; 0.624921;, + 0.499960; 0.416620;, + 0.000040; 0.416621;, + 0.000040; 0.416621;, + 0.499960; 0.416620;, + 0.499960; 0.208320;, + 0.000040; 0.208320;, + 0.499961; 0.000021;, + 0.000040; 0.000020;, + 0.000040; 0.208320;, + 0.499960; 0.208321;, + 0.499960; 0.208321;, + 0.000040; 0.208320;, + 0.000040; 0.416620;, + 0.499960; 0.416621;, + 0.000046; 0.291430;, + 0.499954; 0.291430;, + 0.499954; 0.083135;, + 0.000046; 0.083134;, + 0.482632; 0.297524;, + 0.499972; 0.143183;, + 0.275451; 0.201614;, + 0.279529; 0.217376;, + 0.000023; 0.249954;, + 0.499977; 0.249954;, + 0.499977; 0.041640;, + 0.000023; 0.041640;, + 0.499972; 0.143183;, + 0.017368; 0.143350;, + 0.220471; 0.223498;, + 0.275451; 0.201614;, + 0.017368; 0.143350;, + 0.000028; 0.297691;, + 0.224549; 0.239260;, + 0.220471; 0.223498;, + 0.000035; 0.499630;, + 0.499965; 0.499457;, + 0.424889; 0.249031;, + 0.074938; 0.249151;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.843750; 0.500000;, + 0.750000; 0.375000;, + 0.656250; 0.500000;, + 0.562500; 0.562500;, + 0.500000; 0.500000;, + 0.500000; 1.000000;, + 0.562500; 0.937500;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 1.000000; 0.500000;, + 0.937500; 0.562500;, + 0.937500; 0.937500;, + 0.999902; 0.999901;, + 0.968750; 0.031250;, + 0.843750; 0.031250;, + 0.843750; 0.156250;, + 0.968750; 0.156250;, + 0.562500; 0.937500;, + 0.500000; 1.000000;, + 0.999902; 0.999901;, + 0.937500; 0.937500;, + 0.937500; 0.562500;, + 1.000000; 0.500000;, + 0.500000; 0.500000;, + 0.562500; 0.562500;, + 0.562500; 0.562500;, + 0.500000; 0.500000;, + 0.500000; 1.000000;, + 0.562500; 0.937500;, + 0.000000; 1.000000;, + 1.000000; 1.000000;, + 1.000000; 0.000000;, + 0.000000; 0.000000;, + 1.000000; 0.500000;, + 0.937500; 0.562500;, + 0.937559; 0.942680;, + 0.999902; 0.999901;, + 0.656250; 0.031250;, + 0.531250; 0.031250;, + 0.531250; 0.156250;, + 0.656250; 0.156250;, + 0.562500; 0.937500;, + 0.500000; 1.000000;, + 0.999902; 0.999901;, + 0.937559; 0.942680;, + 0.937500; 0.562500;, + 1.000000; 0.500000;, + 0.500000; 0.500000;, + 0.562500; 0.562500;; + } // End of Cube UV coordinates + MeshMaterialList { // Cube material list + 1; + 71; + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0;; + Material Material { + 0.640000; 0.640000; 0.640000; 1.000000;; + 96.078431; + 0.500000; 0.500000; 0.500000;; + 0.000000; 0.000000; 0.000000;; + } + } // End of Cube material list + XSkinMeshHeader { + 5; + 15; + 5; + } + SkinWeights { + "Armature_Bone_003"; + 249; + 4, + 5, + 6, + 7, + 9, + 10, + 13, + 14, + 17, + 18, + 20, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 76, + 77, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263; + 0.097855, + 0.076266, + 0.083495, + 0.107234, + 0.089782, + 0.015282, + 0.015282, + 0.076266, + 0.076266, + 0.097855, + 0.089782, + 0.097855, + 0.297901, + 0.287737, + 0.176628, + 0.182485, + 0.015282, + 0.089782, + 0.098610, + 0.018636, + 0.089782, + 0.097855, + 0.107234, + 0.098610, + 0.076266, + 0.015282, + 0.018636, + 0.083495, + 0.176628, + 0.003327, + 0.018794, + 0.127925, + 0.020404, + 0.286977, + 0.174766, + 0.003327, + 0.286977, + 0.297901, + 0.182485, + 0.174766, + 0.287737, + 0.020404, + 0.003327, + 0.176628, + 0.126954, + 0.136859, + 0.062329, + 0.060353, + 0.182485, + 0.176628, + 0.127925, + 0.136859, + 0.003327, + 0.174766, + 0.126954, + 0.018794, + 0.174766, + 0.182485, + 0.136859, + 0.126954, + 0.060353, + 0.062329, + 0.059240, + 0.127925, + 0.018794, + 0.059240, + 0.136859, + 0.127925, + 0.059240, + 0.062329, + 0.018794, + 0.126954, + 0.060353, + 0.107234, + 0.083495, + 0.133455, + 0.171692, + 0.171692, + 0.133455, + 0.287737, + 0.297901, + 0.018636, + 0.098610, + 0.162048, + 0.016914, + 0.016914, + 0.162048, + 0.286977, + 0.020404, + 0.098610, + 0.107234, + 0.171692, + 0.162048, + 0.162048, + 0.171692, + 0.297901, + 0.286977, + 0.083495, + 0.018636, + 0.016914, + 0.133455, + 0.016914, + 0.020404, + 0.031793, + 0.024185, + 0.091978, + 0.024185, + 0.031793, + 0.098530, + 0.020404, + 0.287737, + 0.098530, + 0.031793, + 0.287737, + 0.133455, + 0.091978, + 0.098530, + 0.133455, + 0.016914, + 0.024185, + 0.091978, + 0.500298, + 0.488502, + 0.484399, + 0.484912, + 0.488502, + 0.484399, + 0.574901, + 0.567861, + 0.584345, + 0.581311, + 0.484399, + 0.488502, + 0.484912, + 0.484399, + 0.488502, + 0.500298, + 0.650639, + 0.642159, + 0.660270, + 0.658477, + 0.642159, + 0.652886, + 0.660270, + 0.658477, + 0.652886, + 0.650639, + 0.650639, + 0.652886, + 0.642159, + 0.567861, + 0.577104, + 0.584345, + 0.660270, + 0.652886, + 0.658477, + 0.484912, + 0.484399, + 0.500298, + 0.484399, + 0.581311, + 0.577104, + 0.574901, + 0.574901, + 0.577104, + 0.567861, + 0.584345, + 0.577104, + 0.581311, + 0.650637, + 0.658473, + 0.660266, + 0.642160, + 0.658473, + 0.652884, + 0.660266, + 0.642160, + 0.652884, + 0.650637, + 0.650637, + 0.652884, + 0.658473, + 0.660266, + 0.652884, + 0.642160, + 0.574890, + 0.581306, + 0.584343, + 0.567845, + 0.581306, + 0.577096, + 0.584343, + 0.567845, + 0.577096, + 0.574890, + 0.574890, + 0.577096, + 0.581306, + 0.584343, + 0.577096, + 0.567845, + 0.228406, + 0.235541, + 0.226562, + 0.226110, + 0.235541, + 0.218934, + 0.220409, + 0.226562, + 0.218934, + 0.223567, + 0.223362, + 0.220409, + 0.223567, + 0.228406, + 0.226110, + 0.223362, + 0.226110, + 0.226562, + 0.220409, + 0.223362, + 0.223567, + 0.218934, + 0.235541, + 0.228406, + 0.467092, + 0.474056, + 0.449203, + 0.459631, + 0.474056, + 0.473079, + 0.460570, + 0.449203, + 0.473079, + 0.467589, + 0.462597, + 0.460570, + 0.467589, + 0.467092, + 0.459631, + 0.462597, + 0.459631, + 0.449203, + 0.460570, + 0.462597, + 0.467589, + 0.473079, + 0.474056, + 0.467092; + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000,-1.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000,-1.600000, 0.000000, 1.000000;; + } // End of Armature_Bone_003 skin weights + SkinWeights { + "Armature_Bone_002"; + 210; + 0, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 13, + 14, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215; + 0.026699, + 0.019645, + 0.225152, + 0.090050, + 0.096422, + 0.247592, + 0.026699, + 0.225915, + 0.091110, + 0.091110, + 0.090050, + 0.090050, + 0.225152, + 0.019645, + 0.225915, + 0.026699, + 0.019645, + 0.225152, + 0.378267, + 0.217186, + 0.396865, + 0.489530, + 0.091110, + 0.225915, + 0.248358, + 0.097508, + 0.225915, + 0.225152, + 0.247592, + 0.248358, + 0.090050, + 0.091110, + 0.097508, + 0.096422, + 0.396865, + 0.398905, + 0.438519, + 0.443076, + 0.218141, + 0.380147, + 0.489880, + 0.398905, + 0.380147, + 0.378267, + 0.489530, + 0.489880, + 0.217186, + 0.218141, + 0.398905, + 0.396865, + 0.504112, + 0.502873, + 0.231972, + 0.249788, + 0.489530, + 0.396865, + 0.443076, + 0.502873, + 0.398905, + 0.489880, + 0.504112, + 0.438519, + 0.489880, + 0.489530, + 0.502873, + 0.504112, + 0.249788, + 0.231972, + 0.219728, + 0.214257, + 0.443076, + 0.438519, + 0.214257, + 0.219728, + 0.502873, + 0.443076, + 0.219728, + 0.231972, + 0.438519, + 0.504112, + 0.249788, + 0.214257, + 0.247592, + 0.096422, + 0.123030, + 0.409380, + 0.409380, + 0.123030, + 0.217186, + 0.378267, + 0.097508, + 0.248358, + 0.410435, + 0.124284, + 0.124284, + 0.410435, + 0.380147, + 0.218141, + 0.248358, + 0.247592, + 0.409380, + 0.410435, + 0.410435, + 0.409380, + 0.378267, + 0.380147, + 0.096422, + 0.097508, + 0.124284, + 0.123030, + 0.124284, + 0.218141, + 0.103633, + 0.087149, + 0.092557, + 0.087149, + 0.103633, + 0.094423, + 0.218141, + 0.217186, + 0.094423, + 0.103633, + 0.217186, + 0.123030, + 0.092557, + 0.094423, + 0.123030, + 0.124284, + 0.087149, + 0.092557, + 0.499702, + 0.511498, + 0.515601, + 0.515088, + 0.511498, + 0.515601, + 0.425099, + 0.432139, + 0.415655, + 0.418689, + 0.515601, + 0.511498, + 0.515088, + 0.515601, + 0.511498, + 0.499702, + 0.349361, + 0.357841, + 0.339730, + 0.341523, + 0.357841, + 0.347114, + 0.339730, + 0.341523, + 0.347114, + 0.349361, + 0.349361, + 0.347114, + 0.357841, + 0.432139, + 0.422896, + 0.415655, + 0.339730, + 0.347114, + 0.341523, + 0.515088, + 0.515601, + 0.499702, + 0.515601, + 0.418689, + 0.422896, + 0.425099, + 0.425099, + 0.422896, + 0.432139, + 0.415655, + 0.422896, + 0.418689, + 0.349363, + 0.341527, + 0.339734, + 0.357840, + 0.341527, + 0.347116, + 0.339734, + 0.357840, + 0.347116, + 0.349363, + 0.349363, + 0.347116, + 0.341527, + 0.339734, + 0.347116, + 0.357840, + 0.425110, + 0.418694, + 0.415657, + 0.432155, + 0.418694, + 0.422904, + 0.415657, + 0.432155, + 0.422904, + 0.425110, + 0.425110, + 0.422904, + 0.418694, + 0.415657, + 0.422904, + 0.432155; + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000,-1.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000,-1.300000, 0.000000, 1.000000;; + } // End of Armature_Bone_002 skin weights + SkinWeights { + "Armature_Bone_001"; + 127; + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 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, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 75, + 76, + 77, + 78, + 80, + 81, + 84, + 85, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135; + 0.028090, + 0.044667, + 0.027228, + 0.014924, + 0.187604, + 0.200597, + 0.217670, + 0.204339, + 0.028090, + 0.204480, + 0.310430, + 0.044667, + 0.044667, + 0.310430, + 0.200597, + 0.027228, + 0.027228, + 0.200597, + 0.187604, + 0.014924, + 0.204480, + 0.028090, + 0.014924, + 0.187604, + 0.067482, + 0.064812, + 0.031709, + 0.033965, + 0.310430, + 0.204480, + 0.222012, + 0.341423, + 0.204480, + 0.187604, + 0.204339, + 0.222012, + 0.200597, + 0.310430, + 0.341423, + 0.217670, + 0.031709, + 0.101587, + 0.070428, + 0.017181, + 0.167670, + 0.078366, + 0.047455, + 0.101587, + 0.078366, + 0.067482, + 0.033965, + 0.047455, + 0.064812, + 0.167670, + 0.101587, + 0.031709, + 0.027631, + 0.014453, + 0.033965, + 0.031709, + 0.017181, + 0.014453, + 0.101587, + 0.047455, + 0.027631, + 0.070428, + 0.047455, + 0.033965, + 0.014453, + 0.027631, + 0.008199, + 0.017181, + 0.070428, + 0.008199, + 0.014453, + 0.017181, + 0.070428, + 0.027631, + 0.008199, + 0.204339, + 0.217670, + 0.123770, + 0.116219, + 0.116219, + 0.123770, + 0.064812, + 0.067482, + 0.341423, + 0.222012, + 0.131980, + 0.371812, + 0.371812, + 0.131980, + 0.078366, + 0.167670, + 0.222012, + 0.204339, + 0.116219, + 0.131980, + 0.131980, + 0.116219, + 0.067482, + 0.078366, + 0.217670, + 0.341423, + 0.371812, + 0.123770, + 0.371812, + 0.167670, + 0.122698, + 0.132035, + 0.078042, + 0.132035, + 0.122698, + 0.065986, + 0.167670, + 0.064812, + 0.065986, + 0.122698, + 0.064812, + 0.123770, + 0.078042, + 0.065986, + 0.123770, + 0.371812, + 0.132035, + 0.078042; + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000,-1.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000,-1.000000, 0.000000, 1.000000;; + } // End of Armature_Bone_001 skin weights + SkinWeights { + "Armature_Bone"; + 136; + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 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, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135; + 0.945211, + 0.955333, + 0.972772, + 0.965432, + 0.441407, + 0.557541, + 0.520087, + 0.387790, + 0.945211, + 0.424328, + 0.470645, + 0.955333, + 0.955333, + 0.470645, + 0.557541, + 0.972772, + 0.972772, + 0.557541, + 0.441407, + 0.965432, + 0.424328, + 0.945211, + 0.965432, + 0.441407, + 0.133775, + 0.243816, + 0.149205, + 0.083512, + 0.470645, + 0.424328, + 0.371115, + 0.418447, + 0.424328, + 0.441407, + 0.387790, + 0.371115, + 0.557541, + 0.470645, + 0.418447, + 0.520087, + 0.149205, + 0.102630, + 0.077530, + 0.107912, + 0.165882, + 0.124352, + 0.077496, + 0.102630, + 0.124352, + 0.133775, + 0.083512, + 0.077496, + 0.243816, + 0.165882, + 0.102630, + 0.149205, + 0.060886, + 0.066205, + 0.013250, + 0.011723, + 0.083512, + 0.149205, + 0.107912, + 0.066205, + 0.102630, + 0.077496, + 0.060886, + 0.077530, + 0.077496, + 0.083512, + 0.066205, + 0.060886, + 0.011723, + 0.013250, + 0.048094, + 0.022321, + 0.107912, + 0.077530, + 0.022321, + 0.048094, + 0.066205, + 0.107912, + 0.048094, + 0.013250, + 0.077530, + 0.060886, + 0.011723, + 0.022321, + 0.387790, + 0.520087, + 0.494225, + 0.226848, + 0.226848, + 0.494225, + 0.243816, + 0.133775, + 0.418447, + 0.371115, + 0.212428, + 0.276984, + 0.276984, + 0.212428, + 0.124352, + 0.165882, + 0.371115, + 0.387790, + 0.226848, + 0.212428, + 0.212428, + 0.226848, + 0.133775, + 0.124352, + 0.520087, + 0.418447, + 0.276984, + 0.494225, + 0.276984, + 0.165882, + 0.382664, + 0.483353, + 0.390460, + 0.483353, + 0.382664, + 0.298215, + 0.165882, + 0.243816, + 0.298215, + 0.382664, + 0.243816, + 0.494225, + 0.390460, + 0.298215, + 0.494225, + 0.276984, + 0.483353, + 0.390460; + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000,-1.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 1.000000;; + } // End of Armature_Bone skin weights + SkinWeights { + "Armature_Bone_004"; + 172; + 4, + 5, + 6, + 7, + 9, + 10, + 13, + 14, + 17, + 18, + 20, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263; + 0.047982, + 0.075546, + 0.082326, + 0.053045, + 0.055496, + 0.112532, + 0.112532, + 0.075546, + 0.075546, + 0.047982, + 0.055496, + 0.047982, + 0.122575, + 0.186448, + 0.245593, + 0.210507, + 0.112532, + 0.055496, + 0.059905, + 0.123986, + 0.055496, + 0.047982, + 0.053045, + 0.059905, + 0.075546, + 0.112532, + 0.123986, + 0.082326, + 0.245593, + 0.393551, + 0.394730, + 0.303906, + 0.427903, + 0.130157, + 0.210403, + 0.393551, + 0.130157, + 0.122575, + 0.210507, + 0.210403, + 0.186448, + 0.427903, + 0.393551, + 0.245593, + 0.280416, + 0.279610, + 0.692449, + 0.678135, + 0.210507, + 0.245593, + 0.303906, + 0.279610, + 0.393551, + 0.210403, + 0.280416, + 0.394730, + 0.210403, + 0.210507, + 0.279610, + 0.280416, + 0.678135, + 0.692449, + 0.672937, + 0.755223, + 0.303906, + 0.394730, + 0.755223, + 0.672937, + 0.279610, + 0.303906, + 0.672937, + 0.692449, + 0.394730, + 0.280416, + 0.678135, + 0.755223, + 0.053045, + 0.082326, + 0.125519, + 0.075862, + 0.075862, + 0.125519, + 0.186448, + 0.122575, + 0.123986, + 0.059905, + 0.083109, + 0.210006, + 0.210006, + 0.083109, + 0.130157, + 0.427903, + 0.059905, + 0.053045, + 0.075862, + 0.083109, + 0.083109, + 0.075862, + 0.122575, + 0.130157, + 0.082326, + 0.123986, + 0.210006, + 0.125519, + 0.210006, + 0.427903, + 0.359212, + 0.273279, + 0.346964, + 0.273279, + 0.359212, + 0.442846, + 0.427903, + 0.186448, + 0.442846, + 0.359212, + 0.186448, + 0.125519, + 0.346964, + 0.442846, + 0.125519, + 0.210006, + 0.273279, + 0.346964, + 0.771594, + 0.764459, + 0.773438, + 0.773890, + 0.764459, + 0.781066, + 0.779591, + 0.773438, + 0.781066, + 0.776433, + 0.776638, + 0.779591, + 0.776433, + 0.771594, + 0.773890, + 0.776638, + 0.773890, + 0.773438, + 0.779591, + 0.776638, + 0.776433, + 0.781066, + 0.764459, + 0.771594, + 0.532908, + 0.525944, + 0.550797, + 0.540369, + 0.525944, + 0.526921, + 0.539430, + 0.550797, + 0.526921, + 0.532411, + 0.537403, + 0.539430, + 0.532411, + 0.532908, + 0.540369, + 0.537403, + 0.540369, + 0.550797, + 0.539430, + 0.537403, + 0.532411, + 0.526921, + 0.525944, + 0.532908; + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000,-1.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000,-2.000000, 0.000000, 1.000000;; + } // End of Armature_Bone_004 skin weights + } // End of Cube mesh + } // End of Cube + } // End of Armature +} // End of Root +AnimationSet Global { + Animation { + {Armature} + AnimationKey { // Rotation + 0; + 31; + 0;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 1;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 2;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 3;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 4;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 5;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 6;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 7;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 8;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 9;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 10;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 11;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 12;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 13;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 14;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 15;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 16;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 17;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 18;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 19;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 20;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 21;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 22;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 23;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 24;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 25;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 26;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 27;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 28;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 29;4;-0.000000, 0.000000, 0.000000, 1.000000;;, + 30;4;-0.000000, 0.000000, 0.000000, 1.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 5.000000, 5.000000, 5.000000;;, + 1;3; 5.000000, 5.000000, 5.000000;;, + 2;3; 5.000000, 5.000000, 5.000000;;, + 3;3; 5.000000, 5.000000, 5.000000;;, + 4;3; 5.000000, 5.000000, 5.000000;;, + 5;3; 5.000000, 5.000000, 5.000000;;, + 6;3; 5.000000, 5.000000, 5.000000;;, + 7;3; 5.000000, 5.000000, 5.000000;;, + 8;3; 5.000000, 5.000000, 5.000000;;, + 9;3; 5.000000, 5.000000, 5.000000;;, + 10;3; 5.000000, 5.000000, 5.000000;;, + 11;3; 5.000000, 5.000000, 5.000000;;, + 12;3; 5.000000, 5.000000, 5.000000;;, + 13;3; 5.000000, 5.000000, 5.000000;;, + 14;3; 5.000000, 5.000000, 5.000000;;, + 15;3; 5.000000, 5.000000, 5.000000;;, + 16;3; 5.000000, 5.000000, 5.000000;;, + 17;3; 5.000000, 5.000000, 5.000000;;, + 18;3; 5.000000, 5.000000, 5.000000;;, + 19;3; 5.000000, 5.000000, 5.000000;;, + 20;3; 5.000000, 5.000000, 5.000000;;, + 21;3; 5.000000, 5.000000, 5.000000;;, + 22;3; 5.000000, 5.000000, 5.000000;;, + 23;3; 5.000000, 5.000000, 5.000000;;, + 24;3; 5.000000, 5.000000, 5.000000;;, + 25;3; 5.000000, 5.000000, 5.000000;;, + 26;3; 5.000000, 5.000000, 5.000000;;, + 27;3; 5.000000, 5.000000, 5.000000;;, + 28;3; 5.000000, 5.000000, 5.000000;;, + 29;3; 5.000000, 5.000000, 5.000000;;, + 30;3; 5.000000, 5.000000, 5.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 0.000000,-5.000000;;, + 1;3; 0.000000, 0.000000,-5.000000;;, + 2;3; 0.000000, 0.000000,-5.000000;;, + 3;3; 0.000000, 0.000000,-5.000000;;, + 4;3; 0.000000, 0.000000,-5.000000;;, + 5;3; 0.000000, 0.000000,-5.000000;;, + 6;3; 0.000000, 0.000000,-5.000000;;, + 7;3; 0.000000, 0.000000,-5.000000;;, + 8;3; 0.000000, 0.000000,-5.000000;;, + 9;3; 0.000000, 0.000000,-5.000000;;, + 10;3; 0.000000, 0.000000,-5.000000;;, + 11;3; 0.000000, 0.000000,-5.000000;;, + 12;3; 0.000000, 0.000000,-5.000000;;, + 13;3; 0.000000, 0.000000,-5.000000;;, + 14;3; 0.000000, 0.000000,-5.000000;;, + 15;3; 0.000000, 0.000000,-5.000000;;, + 16;3; 0.000000, 0.000000,-5.000000;;, + 17;3; 0.000000, 0.000000,-5.000000;;, + 18;3; 0.000000, 0.000000,-5.000000;;, + 19;3; 0.000000, 0.000000,-5.000000;;, + 20;3; 0.000000, 0.000000,-5.000000;;, + 21;3; 0.000000, 0.000000,-5.000000;;, + 22;3; 0.000000, 0.000000,-5.000000;;, + 23;3; 0.000000, 0.000000,-5.000000;;, + 24;3; 0.000000, 0.000000,-5.000000;;, + 25;3; 0.000000, 0.000000,-5.000000;;, + 26;3; 0.000000, 0.000000,-5.000000;;, + 27;3; 0.000000, 0.000000,-5.000000;;, + 28;3; 0.000000, 0.000000,-5.000000;;, + 29;3; 0.000000, 0.000000,-5.000000;;, + 30;3; 0.000000, 0.000000,-5.000000;;; + } + } + Animation { + {Armature_Bone} + AnimationKey { // Rotation + 0; + 31; + 0;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 1;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 2;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 3;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 4;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 5;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 6;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 7;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 8;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 9;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 10;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 11;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 12;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 13;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 14;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 15;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 16;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 17;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 18;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 19;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 20;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 21;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 22;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 23;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 24;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 25;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 26;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 27;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 28;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 29;4;-0.707107, 0.707107, 0.000000, 0.000000;;, + 30;4;-0.707107, 0.707107, 0.000000, 0.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 1.000000, 1.000000, 1.000000;;, + 1;3; 1.000000, 1.000000, 1.000000;;, + 2;3; 1.000000, 1.000000, 1.000000;;, + 3;3; 1.000000, 1.000000, 1.000000;;, + 4;3; 1.000000, 1.000000, 1.000000;;, + 5;3; 1.000000, 1.000000, 1.000000;;, + 6;3; 1.000000, 1.000000, 1.000000;;, + 7;3; 1.000000, 1.000000, 1.000000;;, + 8;3; 1.000000, 1.000000, 1.000000;;, + 9;3; 1.000000, 1.000000, 1.000000;;, + 10;3; 1.000000, 1.000000, 1.000000;;, + 11;3; 1.000000, 1.000000, 1.000000;;, + 12;3; 1.000000, 1.000000, 1.000000;;, + 13;3; 1.000000, 1.000000, 1.000000;;, + 14;3; 1.000000, 1.000000, 1.000000;;, + 15;3; 1.000000, 1.000000, 1.000000;;, + 16;3; 1.000000, 1.000000, 1.000000;;, + 17;3; 1.000000, 1.000000, 1.000000;;, + 18;3; 1.000000, 1.000000, 1.000000;;, + 19;3; 1.000000, 1.000000, 1.000000;;, + 20;3; 1.000000, 1.000000, 1.000000;;, + 21;3; 1.000000, 1.000000, 1.000000;;, + 22;3; 1.000000, 1.000000, 1.000000;;, + 23;3; 1.000000, 1.000000, 1.000000;;, + 24;3; 1.000000, 1.000000, 1.000000;;, + 25;3; 1.000000, 1.000000, 1.000000;;, + 26;3; 1.000000, 1.000000, 1.000000;;, + 27;3; 1.000000, 1.000000, 1.000000;;, + 28;3; 1.000000, 1.000000, 1.000000;;, + 29;3; 1.000000, 1.000000, 1.000000;;, + 30;3; 1.000000, 1.000000, 1.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 0.000000, 0.000000;;, + 1;3; 0.000000, 0.000000, 0.000000;;, + 2;3; 0.000000, 0.000000, 0.000000;;, + 3;3; 0.000000, 0.000000, 0.000000;;, + 4;3; 0.000000, 0.000000, 0.000000;;, + 5;3; 0.000000, 0.000000, 0.000000;;, + 6;3; 0.000000, 0.000000, 0.000000;;, + 7;3; 0.000000, 0.000000, 0.000000;;, + 8;3; 0.000000, 0.000000, 0.000000;;, + 9;3; 0.000000, 0.000000, 0.000000;;, + 10;3; 0.000000, 0.000000, 0.000000;;, + 11;3; 0.000000, 0.000000, 0.000000;;, + 12;3; 0.000000, 0.000000, 0.000000;;, + 13;3; 0.000000, 0.000000, 0.000000;;, + 14;3; 0.000000, 0.000000, 0.000000;;, + 15;3; 0.000000, 0.000000, 0.000000;;, + 16;3; 0.000000, 0.000000, 0.000000;;, + 17;3; 0.000000, 0.000000, 0.000000;;, + 18;3; 0.000000, 0.000000, 0.000000;;, + 19;3; 0.000000, 0.000000, 0.000000;;, + 20;3; 0.000000, 0.000000, 0.000000;;, + 21;3; 0.000000, 0.000000, 0.000000;;, + 22;3; 0.000000, 0.000000, 0.000000;;, + 23;3; 0.000000, 0.000000, 0.000000;;, + 24;3; 0.000000, 0.000000, 0.000000;;, + 25;3; 0.000000, 0.000000, 0.000000;;, + 26;3; 0.000000, 0.000000, 0.000000;;, + 27;3; 0.000000, 0.000000, 0.000000;;, + 28;3; 0.000000, 0.000000, 0.000000;;, + 29;3; 0.000000, 0.000000, 0.000000;;, + 30;3; 0.000000, 0.000000, 0.000000;;; + } + } + Animation { + {Armature_Bone_001} + AnimationKey { // Rotation + 0; + 31; + 0;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 1;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 2;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 3;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 4;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 5;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 6;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 7;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 8;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 9;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 10;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 11;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 12;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 13;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 14;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 15;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 16;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 17;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 18;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 19;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 20;4;-0.998539, 0.054035, 0.000000,-0.000000;;, + 21;4;-0.996147, 0.053932, 0.002355, 0.043499;;, + 22;4;-0.998539, 0.054035, 0.000000, 0.000000;;, + 23;4;-0.997589, 0.053983,-0.002357,-0.043556;;, + 24;4;-0.998539, 0.054035, 0.000000, 0.000000;;, + 25;4;-0.996639, 0.053932, 0.003045, 0.056265;;, + 26;4;-0.994740, 0.053829, 0.004709, 0.087028;;, + 27;4;-0.996147, 0.053932, 0.002355, 0.043519;;, + 28;4;-0.998539, 0.054035, 0.000000, 0.000000;;, + 29;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 30;4;-1.000000, 0.000000, 0.000000, 0.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 1.000000, 1.000000, 1.000000;;, + 1;3; 1.000000, 1.000000, 1.000000;;, + 2;3; 1.000000, 1.000000, 1.000000;;, + 3;3; 1.000000, 1.000000, 1.000000;;, + 4;3; 1.000000, 1.000000, 1.000000;;, + 5;3; 1.000000, 1.000000, 1.000000;;, + 6;3; 1.000000, 1.000000, 1.000000;;, + 7;3; 1.000000, 1.000000, 1.000000;;, + 8;3; 1.000000, 1.000000, 1.000000;;, + 9;3; 1.000000, 1.000000, 1.000000;;, + 10;3; 1.000000, 1.000000, 1.000000;;, + 11;3; 1.000000, 1.000000, 1.000000;;, + 12;3; 1.000000, 1.000000, 1.000000;;, + 13;3; 1.000000, 1.000000, 1.000000;;, + 14;3; 1.000000, 1.000000, 1.000000;;, + 15;3; 1.000000, 1.000000, 1.000000;;, + 16;3; 1.000000, 1.000000, 1.000000;;, + 17;3; 1.000000, 1.000000, 1.000000;;, + 18;3; 1.000000, 1.000000, 1.000000;;, + 19;3; 1.000000, 1.000000, 1.000000;;, + 20;3; 1.000000, 1.000000, 1.000000;;, + 21;3; 1.000000, 1.000000, 1.000000;;, + 22;3; 1.000000, 1.000000, 1.000000;;, + 23;3; 1.000000, 1.000000, 1.000000;;, + 24;3; 1.000000, 1.000000, 1.000000;;, + 25;3; 1.000000, 1.000000, 1.000000;;, + 26;3; 1.000000, 1.000000, 1.000000;;, + 27;3; 1.000000, 1.000000, 1.000000;;, + 28;3; 1.000000, 1.000000, 1.000000;;, + 29;3; 1.000000, 1.000000, 1.000000;;, + 30;3; 1.000000, 1.000000, 1.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 2.000000, 0.000000;;, + 1;3; 0.000000, 2.000000, 0.000000;;, + 2;3; 0.000000, 2.000000, 0.000000;;, + 3;3; 0.000000, 2.000000, 0.000000;;, + 4;3; 0.000000, 2.000000, 0.000000;;, + 5;3; 0.000000, 2.000000, 0.000000;;, + 6;3; 0.000000, 2.000000, 0.000000;;, + 7;3; 0.000000, 2.000000, 0.000000;;, + 8;3; 0.000000, 2.000000, 0.000000;;, + 9;3; 0.000000, 2.000000, 0.000000;;, + 10;3; 0.000000, 2.000000, 0.000000;;, + 11;3; 0.000000, 2.000000, 0.000000;;, + 12;3; 0.000000, 2.000000, 0.000000;;, + 13;3; 0.000000, 2.000000, 0.000000;;, + 14;3; 0.000000, 2.000000, 0.000000;;, + 15;3; 0.000000, 2.000000, 0.000000;;, + 16;3; 0.000000, 2.000000, 0.000000;;, + 17;3; 0.000000, 2.000000, 0.000000;;, + 18;3; 0.000000, 2.000000, 0.000000;;, + 19;3; 0.000000, 2.000000, 0.000000;;, + 20;3; 0.000000, 2.000000, 0.000000;;, + 21;3; 0.000000, 2.000000, 0.000000;;, + 22;3; 0.000000, 2.000000, 0.000000;;, + 23;3; 0.000000, 2.000000, 0.000000;;, + 24;3; 0.000000, 2.000000, 0.000000;;, + 25;3; 0.000000, 2.000000, 0.000000;;, + 26;3; 0.000000, 2.000000, 0.000000;;, + 27;3; 0.000000, 2.000000, 0.000000;;, + 28;3; 0.000000, 2.000000, 0.000000;;, + 29;3; 0.000000, 2.000000, 0.000000;;, + 30;3; 0.000000, 2.000000, 0.000000;;; + } + } + Animation { + {Armature_Bone_002} + AnimationKey { // Rotation + 0; + 31; + 0;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 1;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 2;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 3;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 4;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 5;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 6;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 7;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 8;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 9;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 10;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 11;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 12;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 13;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 14;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 15;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 16;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 17;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 18;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 19;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 20;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 21;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 22;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 23;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 24;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 25;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 26;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 27;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 28;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 29;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 30;4;-1.000000, 0.000000, 0.000000, 0.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 1.000000, 1.000000, 1.000000;;, + 1;3; 1.000000, 1.000000, 1.000000;;, + 2;3; 1.000000, 1.000000, 1.000000;;, + 3;3; 1.000000, 1.000000, 1.000000;;, + 4;3; 1.000000, 1.000000, 1.000000;;, + 5;3; 1.000000, 1.000000, 1.000000;;, + 6;3; 1.000000, 1.000000, 1.000000;;, + 7;3; 1.000000, 1.000000, 1.000000;;, + 8;3; 1.000000, 1.000000, 1.000000;;, + 9;3; 1.000000, 1.000000, 1.000000;;, + 10;3; 1.000000, 1.000000, 1.000000;;, + 11;3; 1.000000, 1.000000, 1.000000;;, + 12;3; 1.000000, 1.000000, 1.000000;;, + 13;3; 1.000000, 1.000000, 1.000000;;, + 14;3; 1.000000, 1.000000, 1.000000;;, + 15;3; 1.000000, 1.000000, 1.000000;;, + 16;3; 1.000000, 1.000000, 1.000000;;, + 17;3; 1.000000, 1.000000, 1.000000;;, + 18;3; 1.000000, 1.000000, 1.000000;;, + 19;3; 1.000000, 1.000000, 1.000000;;, + 20;3; 1.000000, 1.000000, 1.000000;;, + 21;3; 1.000000, 1.000000, 1.000000;;, + 22;3; 1.000000, 1.000000, 1.000000;;, + 23;3; 1.000000, 1.000000, 1.000000;;, + 24;3; 1.000000, 1.000000, 1.000000;;, + 25;3; 1.000000, 1.000000, 1.000000;;, + 26;3; 1.000000, 1.000000, 1.000000;;, + 27;3; 1.000000, 1.000000, 1.000000;;, + 28;3; 1.000000, 1.000000, 1.000000;;, + 29;3; 1.000000, 1.000000, 1.000000;;, + 30;3; 1.000000, 1.000000, 1.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 0.300000, 0.000000;;, + 1;3; 0.000000, 0.300000, 0.000000;;, + 2;3; 0.000000, 0.300000, 0.000000;;, + 3;3; 0.000000, 0.300000, 0.000000;;, + 4;3; 0.000000, 0.300000, 0.000000;;, + 5;3; 0.000000, 0.300000, 0.000000;;, + 6;3; 0.000000, 0.300000, 0.000000;;, + 7;3; 0.000000, 0.300000, 0.000000;;, + 8;3; 0.000000, 0.300000, 0.000000;;, + 9;3; 0.000000, 0.300000, 0.000000;;, + 10;3; 0.000000, 0.300000, 0.000000;;, + 11;3; 0.000000, 0.300000, 0.000000;;, + 12;3; 0.000000, 0.300000, 0.000000;;, + 13;3; 0.000000, 0.300000, 0.000000;;, + 14;3; 0.000000, 0.300000, 0.000000;;, + 15;3; 0.000000, 0.300000, 0.000000;;, + 16;3; 0.000000, 0.300000, 0.000000;;, + 17;3; 0.000000, 0.300000, 0.000000;;, + 18;3; 0.000000, 0.300000, 0.000000;;, + 19;3; 0.000000, 0.300000, 0.000000;;, + 20;3; 0.000000, 0.300000, 0.000000;;, + 21;3;-0.000000, 0.300000,-0.000000;;, + 22;3;-0.000000, 0.300000, 0.000000;;, + 23;3;-0.000000, 0.300000, 0.000000;;, + 24;3;-0.000000, 0.300000, 0.000000;;, + 25;3;-0.000000, 0.300000,-0.000000;;, + 26;3;-0.000000, 0.300000, 0.000000;;, + 27;3;-0.000000, 0.300000,-0.000000;;, + 28;3; 0.000000, 0.300000, 0.000000;;, + 29;3; 0.000000, 0.300000, 0.000000;;, + 30;3; 0.000000, 0.300000, 0.000000;;; + } + } + Animation { + {Armature_Bone_003} + AnimationKey { // Rotation + 0; + 31; + 0;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 1;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 2;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 3;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 4;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 5;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 6;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 7;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 8;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 9;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 10;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 11;4;-0.998097,-0.000000, 0.000000,-0.043583;;, + 12;4;-0.996195,-0.000000, 0.000000,-0.087156;;, + 13;4;-0.998097,-0.000000, 0.000000,-0.056342;;, + 14;4;-1.000000,-0.000000,-0.000000, 0.000000;;, + 15;4;-0.998097,-0.000000,-0.000000, 0.056342;;, + 16;4;-0.996195,-0.000000,-0.000000, 0.087156;;, + 17;4;-0.998097,-0.000000,-0.000000, 0.043583;;, + 18;4;-1.000000,-0.000000, 0.000000, 0.000000;;, + 19;4;-1.000000,-0.000000, 0.000000, 0.000000;;, + 20;4;-0.996195, 0.087156, 0.000000, 0.000000;;, + 21;4;-0.996434, 0.081668, 0.000000, 0.000000;;, + 22;4;-0.996936, 0.070172, 0.000000, 0.000000;;, + 23;4;-0.997535, 0.056459, 0.000000, 0.000000;;, + 24;4;-0.998149, 0.042383, 0.000000, 0.000000;;, + 25;4;-0.998729, 0.029106, 0.000000, 0.000000;;, + 26;4;-0.999236, 0.017493, 0.000000, 0.000000;;, + 27;4;-0.999638, 0.008289, 0.000000, 0.000000;;, + 28;4;-0.999904, 0.002208, 0.000000, 0.000000;;, + 29;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 30;4;-1.000000, 0.000000, 0.000000, 0.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 1.000000, 1.000000, 1.000000;;, + 1;3; 1.000000, 1.000000, 1.000000;;, + 2;3; 1.000000, 1.000000, 1.000000;;, + 3;3; 1.000000, 1.000000, 1.000000;;, + 4;3; 1.000000, 1.000000, 1.000000;;, + 5;3; 1.000000, 1.000000, 1.000000;;, + 6;3; 1.000000, 1.000000, 1.000000;;, + 7;3; 1.000000, 1.000000, 1.000000;;, + 8;3; 1.000000, 1.000000, 1.000000;;, + 9;3; 1.000000, 1.000000, 1.000000;;, + 10;3; 1.000000, 1.000000, 1.000000;;, + 11;3; 1.000000, 1.000000, 1.000000;;, + 12;3; 1.000000, 1.000000, 1.000000;;, + 13;3; 1.000000, 1.000000, 1.000000;;, + 14;3; 1.000000, 1.000000, 1.000000;;, + 15;3; 1.000000, 1.000000, 1.000000;;, + 16;3; 1.000000, 1.000000, 1.000000;;, + 17;3; 1.000000, 1.000000, 1.000000;;, + 18;3; 1.000000, 1.000000, 1.000000;;, + 19;3; 1.000000, 1.000000, 1.000000;;, + 20;3; 1.000000, 1.000000, 1.000000;;, + 21;3; 1.000000, 1.000000, 1.000000;;, + 22;3; 1.000000, 1.000000, 1.000000;;, + 23;3; 1.000000, 1.000000, 1.000000;;, + 24;3; 1.000000, 1.000000, 1.000000;;, + 25;3; 1.000000, 1.000000, 1.000000;;, + 26;3; 1.000000, 1.000000, 1.000000;;, + 27;3; 1.000000, 1.000000, 1.000000;;, + 28;3; 1.000000, 1.000000, 1.000000;;, + 29;3; 1.000000, 1.000000, 1.000000;;, + 30;3; 1.000000, 1.000000, 1.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 0.300000, 0.000000;;, + 1;3; 0.000000, 0.300000, 0.000000;;, + 2;3; 0.000000, 0.300000, 0.000000;;, + 3;3; 0.000000, 0.300000, 0.000000;;, + 4;3; 0.000000, 0.300000, 0.000000;;, + 5;3; 0.000000, 0.300000, 0.000000;;, + 6;3; 0.000000, 0.300000, 0.000000;;, + 7;3; 0.000000, 0.300000, 0.000000;;, + 8;3; 0.000000, 0.300000, 0.000000;;, + 9;3; 0.000000, 0.300000, 0.000000;;, + 10;3; 0.000000, 0.300000, 0.000000;;, + 11;3; 0.000000, 0.300000, 0.000000;;, + 12;3; 0.000000, 0.300000, 0.000000;;, + 13;3; 0.000000, 0.300000, 0.000000;;, + 14;3; 0.000000, 0.300000, 0.000000;;, + 15;3; 0.000000, 0.300000, 0.000000;;, + 16;3; 0.000000, 0.300000, 0.000000;;, + 17;3; 0.000000, 0.300000, 0.000000;;, + 18;3; 0.000000, 0.300000, 0.000000;;, + 19;3; 0.000000, 0.300000, 0.000000;;, + 20;3; 0.000000, 0.300000, 0.000000;;, + 21;3;-0.000000, 0.300000,-0.000000;;, + 22;3;-0.000000, 0.300000,-0.000000;;, + 23;3; 0.000000, 0.300000, 0.000000;;, + 24;3;-0.000000, 0.300000,-0.000000;;, + 25;3; 0.000000, 0.300000,-0.000000;;, + 26;3;-0.000000, 0.300000,-0.000000;;, + 27;3;-0.000000, 0.300000,-0.000000;;, + 28;3;-0.000000, 0.300000, 0.000000;;, + 29;3; 0.000000, 0.300000, 0.000000;;, + 30;3; 0.000000, 0.300000, 0.000000;;; + } + } + Animation { + {Armature_Bone_004} + AnimationKey { // Rotation + 0; + 31; + 0;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 1;4;-0.999524, 0.021812, 0.000000,-0.000000;;, + 2;4;-0.999048, 0.043619, 0.000000,-0.000000;;, + 3;4;-0.999524, 0.028196, 0.000000,-0.000000;;, + 4;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 5;4;-0.999524,-0.028196,-0.000000, 0.000000;;, + 6;4;-0.999048,-0.043619,-0.000000, 0.000000;;, + 7;4;-0.999524,-0.021810,-0.000000, 0.000000;;, + 8;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 9;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 10;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 11;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 12;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 13;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 14;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 15;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 16;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 17;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 18;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 19;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 20;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 21;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 22;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 23;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 24;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 25;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 26;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 27;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 28;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 29;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 30;4;-1.000000, 0.000000, 0.000000, 0.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 1.000000, 1.000000, 1.000000;;, + 1;3; 1.000000, 1.000000, 1.000000;;, + 2;3; 1.000000, 1.000000, 1.000000;;, + 3;3; 1.000000, 1.000000, 1.000000;;, + 4;3; 1.000000, 1.000000, 1.000000;;, + 5;3; 1.000000, 1.000000, 1.000000;;, + 6;3; 1.000000, 1.000000, 1.000000;;, + 7;3; 1.000000, 1.000000, 1.000000;;, + 8;3; 1.000000, 1.000000, 1.000000;;, + 9;3; 1.000000, 1.000000, 1.000000;;, + 10;3; 1.000000, 1.000000, 1.000000;;, + 11;3; 1.000000, 1.000000, 1.000000;;, + 12;3; 1.000000, 1.000000, 1.000000;;, + 13;3; 1.000000, 1.000000, 1.000000;;, + 14;3; 1.000000, 1.000000, 1.000000;;, + 15;3; 1.000000, 1.000000, 1.000000;;, + 16;3; 1.000000, 1.000000, 1.000000;;, + 17;3; 1.000000, 1.000000, 1.000000;;, + 18;3; 1.000000, 1.000000, 1.000000;;, + 19;3; 1.000000, 1.000000, 1.000000;;, + 20;3; 1.000000, 1.000000, 1.000000;;, + 21;3; 1.000000, 1.000000, 1.000000;;, + 22;3; 1.000000, 1.000000, 1.000000;;, + 23;3; 1.000000, 1.000000, 1.000000;;, + 24;3; 1.000000, 1.000000, 1.000000;;, + 25;3; 1.000000, 1.000000, 1.000000;;, + 26;3; 1.000000, 1.000000, 1.000000;;, + 27;3; 1.000000, 1.000000, 1.000000;;, + 28;3; 1.000000, 1.000000, 1.000000;;, + 29;3; 1.000000, 1.000000, 1.000000;;, + 30;3; 1.000000, 1.000000, 1.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 0.400000, 0.000000;;, + 1;3; 0.000000, 0.400000, 0.000000;;, + 2;3; 0.000000, 0.400000, 0.000000;;, + 3;3; 0.000000, 0.400000, 0.000000;;, + 4;3; 0.000000, 0.400000, 0.000000;;, + 5;3; 0.000000, 0.400000, 0.000000;;, + 6;3; 0.000000, 0.400000, 0.000000;;, + 7;3; 0.000000, 0.400000, 0.000000;;, + 8;3; 0.000000, 0.400000, 0.000000;;, + 9;3; 0.000000, 0.400000, 0.000000;;, + 10;3; 0.000000, 0.400000, 0.000000;;, + 11;3; 0.000000, 0.400000,-0.000000;;, + 12;3;-0.000000, 0.400000, 0.000000;;, + 13;3; 0.000000, 0.400000, 0.000000;;, + 14;3;-0.000000, 0.400000, 0.000000;;, + 15;3;-0.000000, 0.400000, 0.000000;;, + 16;3; 0.000000, 0.400000, 0.000000;;, + 17;3;-0.000000, 0.400000,-0.000000;;, + 18;3;-0.000000, 0.400000,-0.000000;;, + 19;3;-0.000000, 0.400000,-0.000000;;, + 20;3; 0.000000, 0.400000,-0.000000;;, + 21;3; 0.000000, 0.400000, 0.000000;;, + 22;3;-0.000000, 0.400000,-0.000000;;, + 23;3;-0.000000, 0.400000,-0.000000;;, + 24;3;-0.000000, 0.400000,-0.000000;;, + 25;3; 0.000000, 0.400000, 0.000000;;, + 26;3;-0.000000, 0.400000, 0.000000;;, + 27;3;-0.000000, 0.400000, 0.000000;;, + 28;3; 0.000000, 0.400000, 0.000000;;, + 29;3; 0.000000, 0.400000, 0.000000;;, + 30;3; 0.000000, 0.400000, 0.000000;;; + } + } + Animation { + {Cube} + AnimationKey { // Rotation + 0; + 31; + 0;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 1;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 2;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 3;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 4;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 5;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 6;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 7;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 8;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 9;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 10;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 11;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 12;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 13;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 14;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 15;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 16;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 17;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 18;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 19;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 20;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 21;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 22;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 23;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 24;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 25;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 26;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 27;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 28;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 29;4;-1.000000, 0.000000, 0.000000, 0.000000;;, + 30;4;-1.000000, 0.000000, 0.000000, 0.000000;;; + } + AnimationKey { // Scale + 1; + 31; + 0;3; 1.000000, 1.000000, 1.000000;;, + 1;3; 1.000000, 1.000000, 1.000000;;, + 2;3; 1.000000, 1.000000, 1.000000;;, + 3;3; 1.000000, 1.000000, 1.000000;;, + 4;3; 1.000000, 1.000000, 1.000000;;, + 5;3; 1.000000, 1.000000, 1.000000;;, + 6;3; 1.000000, 1.000000, 1.000000;;, + 7;3; 1.000000, 1.000000, 1.000000;;, + 8;3; 1.000000, 1.000000, 1.000000;;, + 9;3; 1.000000, 1.000000, 1.000000;;, + 10;3; 1.000000, 1.000000, 1.000000;;, + 11;3; 1.000000, 1.000000, 1.000000;;, + 12;3; 1.000000, 1.000000, 1.000000;;, + 13;3; 1.000000, 1.000000, 1.000000;;, + 14;3; 1.000000, 1.000000, 1.000000;;, + 15;3; 1.000000, 1.000000, 1.000000;;, + 16;3; 1.000000, 1.000000, 1.000000;;, + 17;3; 1.000000, 1.000000, 1.000000;;, + 18;3; 1.000000, 1.000000, 1.000000;;, + 19;3; 1.000000, 1.000000, 1.000000;;, + 20;3; 1.000000, 1.000000, 1.000000;;, + 21;3; 1.000000, 1.000000, 1.000000;;, + 22;3; 1.000000, 1.000000, 1.000000;;, + 23;3; 1.000000, 1.000000, 1.000000;;, + 24;3; 1.000000, 1.000000, 1.000000;;, + 25;3; 1.000000, 1.000000, 1.000000;;, + 26;3; 1.000000, 1.000000, 1.000000;;, + 27;3; 1.000000, 1.000000, 1.000000;;, + 28;3; 1.000000, 1.000000, 1.000000;;, + 29;3; 1.000000, 1.000000, 1.000000;;, + 30;3; 1.000000, 1.000000, 1.000000;;; + } + AnimationKey { // Position + 2; + 31; + 0;3; 0.000000, 0.000000, 1.000000;;, + 1;3; 0.000000, 0.000000, 1.000000;;, + 2;3; 0.000000, 0.000000, 1.000000;;, + 3;3; 0.000000, 0.000000, 1.000000;;, + 4;3; 0.000000, 0.000000, 1.000000;;, + 5;3; 0.000000, 0.000000, 1.000000;;, + 6;3; 0.000000, 0.000000, 1.000000;;, + 7;3; 0.000000, 0.000000, 1.000000;;, + 8;3; 0.000000, 0.000000, 1.000000;;, + 9;3; 0.000000, 0.000000, 1.000000;;, + 10;3; 0.000000, 0.000000, 1.000000;;, + 11;3; 0.000000, 0.000000, 1.000000;;, + 12;3; 0.000000, 0.000000, 1.000000;;, + 13;3; 0.000000, 0.000000, 1.000000;;, + 14;3; 0.000000, 0.000000, 1.000000;;, + 15;3; 0.000000, 0.000000, 1.000000;;, + 16;3; 0.000000, 0.000000, 1.000000;;, + 17;3; 0.000000, 0.000000, 1.000000;;, + 18;3; 0.000000, 0.000000, 1.000000;;, + 19;3; 0.000000, 0.000000, 1.000000;;, + 20;3; 0.000000, 0.000000, 1.000000;;, + 21;3; 0.000000, 0.000000, 1.000000;;, + 22;3; 0.000000, 0.000000, 1.000000;;, + 23;3; 0.000000, 0.000000, 1.000000;;, + 24;3; 0.000000, 0.000000, 1.000000;;, + 25;3; 0.000000, 0.000000, 1.000000;;, + 26;3; 0.000000, 0.000000, 1.000000;;, + 27;3; 0.000000, 0.000000, 1.000000;;, + 28;3; 0.000000, 0.000000, 1.000000;;, + 29;3; 0.000000, 0.000000, 1.000000;;, + 30;3; 0.000000, 0.000000, 1.000000;;; + } + } +} // End of AnimationSet Global diff --git a/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua index b61b5e819..6bb1b8282 100644 --- a/games/devtest/mods/testentities/visuals.lua +++ b/games/devtest/mods/testentities/visuals.lua @@ -79,6 +79,20 @@ core.register_entity("testentities:sam", { end, }) +core.register_entity("testentities:lava_flan", { + initial_properties = { + infotext = "Lava Flan (smoke test .x)", + visual = "mesh", + mesh = "testentities_lava_flan.x", + textures = { + "testentities_lava_flan.png" + }, + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 28}, 15, 0, true) + end, +}) + -- Advanced visual tests -- An entity for testing animated and yaw-modulated sprites diff --git a/games/devtest/mods/testitems/init.lua b/games/devtest/mods/testitems/init.lua index 12da2ad1c..71a85ef6d 100644 --- a/games/devtest/mods/testitems/init.lua +++ b/games/devtest/mods/testitems/init.lua @@ -105,3 +105,68 @@ core.register_craftitem("testitems:telescope_stick", { return itemstack end, }) + + +-- Tree spawners + +local tree_def={ + axiom="Af", + rules_a="TT[&GB][&+GB][&++GB][&+++GB]A", + rules_b="[+GB]fB", + trunk="basenodes:tree", + leaves="basenodes:leaves", + angle=90, + iterations=4, + trunk_type="single", + thin_branches=true, +} + +core.register_craftitem("testitems:tree_spawner", { + description = S("Tree Spawner"), + inventory_image = "testitems_tree_spawner.png", + on_place = function(itemstack, placer, pointed_thing) + if (not pointed_thing or pointed_thing.type ~= "node") then + return + end + core.spawn_tree(pointed_thing.above, tree_def) + end, +}) + +local vmanip_for_trees = {} -- per player +core.register_craftitem("testitems:tree_spawner_vmanip", { + description = S("Tree Spawner using VoxelManip"), + inventory_image = "testitems_tree_spawner_vmanip.png", + on_place = function(itemstack, placer, pointed_thing) + if (not pointed_thing or pointed_thing.type ~= "node" or + not core.is_player(placer)) then + return + end + local name = placer:get_player_name() + local vm = vmanip_for_trees[name] + if not vm then + vm = VoxelManip(vector.add(pointed_thing.above, 20), + vector.subtract(pointed_thing.above, 20)) + vmanip_for_trees[name] = vm + core.chat_send_player(name, + "Tree in new VoxelManip spawned, left click to apply to map, ".. + "or right click to add more trees.") + end + core.spawn_tree_on_vmanip(vm, pointed_thing.above, tree_def) + end, + on_use = function(itemstack, user, pointed_thing) + if not core.is_player(user) then + return + end + local name = user:get_player_name() + local vm = vmanip_for_trees[name] + if vm then + vm:write_to_map() + vmanip_for_trees[name] = nil + core.chat_send_player(name, "VoxelManip written to map.") + end + end, +}) + +core.register_on_leaveplayer(function(player, timed_out) + vmanip_for_trees[player:get_player_name()] = nil +end) diff --git a/games/devtest/mods/testitems/textures/testitems_tree_spawner.png b/games/devtest/mods/testitems/textures/testitems_tree_spawner.png new file mode 100644 index 000000000..aa7430677 Binary files /dev/null and b/games/devtest/mods/testitems/textures/testitems_tree_spawner.png differ diff --git a/games/devtest/mods/testitems/textures/testitems_tree_spawner_vmanip.png b/games/devtest/mods/testitems/textures/testitems_tree_spawner_vmanip.png new file mode 100644 index 000000000..2d8e0311f Binary files /dev/null and b/games/devtest/mods/testitems/textures/testitems_tree_spawner_vmanip.png differ diff --git a/games/devtest/mods/testtools/particles.lua b/games/devtest/mods/testtools/particles.lua index 17f4f5c80..da6b6cab9 100644 --- a/games/devtest/mods/testtools/particles.lua +++ b/games/devtest/mods/testtools/particles.lua @@ -1,14 +1,27 @@ +local function spawn_clip_test_particle(pos) + core.add_particle({ + pos = pos, + size = 5, + expirationtime = 10, + texture = { + name = "testtools_particle_clip.png", + blend = "clip", + }, + }) +end + core.register_tool("testtools:particle_spawner", { - description = "Particle Spawner".."\n".. + description = table.concat({ + "Particle Spawner", "Punch: Spawn random test particle", + "Place: Spawn clip test particle", + }, "\n"), inventory_image = "testtools_particle_spawner.png", groups = { testtool = 1, disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) local pos = core.get_pointed_thing_position(pointed_thing, true) if pos == nil then - if user then - pos = user:get_pos() - end + pos = assert(user):get_pos() end pos = vector.add(pos, {x=0, y=0.5, z=0}) local tex, anim @@ -32,5 +45,12 @@ core.register_tool("testtools:particle_spawner", { glow = math.random(0, 5), }) end, + on_place = function(itemstack, user, pointed_thing) + local pos = assert(core.get_pointed_thing_position(pointed_thing, true)) + spawn_clip_test_particle(pos) + end, + on_secondary_use = function(_, user) + spawn_clip_test_particle(assert(user):get_pos()) + end, }) diff --git a/games/devtest/mods/testtools/textures/testtools_particle_clip.png b/games/devtest/mods/testtools/textures/testtools_particle_clip.png new file mode 100644 index 000000000..5fb9ad09a Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_particle_clip.png differ diff --git a/irr/include/CIndexBuffer.h b/irr/include/CIndexBuffer.h index 904b0ab9a..ba85d49e6 100644 --- a/irr/include/CIndexBuffer.h +++ b/irr/include/CIndexBuffer.h @@ -24,12 +24,7 @@ class CIndexBuffer final : public IIndexBuffer { public: //! Default constructor for empty buffer - CIndexBuffer() - { -#ifdef _DEBUG - setDebugName("CIndexBuffer"); -#endif - } + CIndexBuffer() {} video::E_INDEX_TYPE getType() const override { diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index 9a6d4426f..d0c41ccfa 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -22,9 +22,6 @@ public: CMeshBuffer() : PrimitiveType(EPT_TRIANGLES) { -#ifdef _DEBUG - setDebugName("CMeshBuffer"); -#endif Vertices = new CVertexBuffer(); Indices = new SIndexBuffer(); } diff --git a/irr/include/CVertexBuffer.h b/irr/include/CVertexBuffer.h index 4b3f33688..1bface16c 100644 --- a/irr/include/CVertexBuffer.h +++ b/irr/include/CVertexBuffer.h @@ -24,12 +24,7 @@ class CVertexBuffer final : public IVertexBuffer { public: //! Default constructor for empty buffer - CVertexBuffer() - { -#ifdef _DEBUG - setDebugName("CVertexBuffer"); -#endif - } + CVertexBuffer() {} const void *getData() const override { diff --git a/irr/include/EDriverFeatures.h b/irr/include/EDriverFeatures.h index bde2c7698..0a35161cf 100644 --- a/irr/include/EDriverFeatures.h +++ b/irr/include/EDriverFeatures.h @@ -129,6 +129,9 @@ enum E_VIDEO_DRIVER_FEATURE //! Support for clamping vertices beyond far-plane to depth instead of capping them. EVDF_DEPTH_CLAMP, + //! Support for multisample textures. + EVDF_TEXTURE_MULTISAMPLE, + //! Only used for counting the elements of this enum EVDF_COUNT }; diff --git a/irr/include/IBoneSceneNode.h b/irr/include/IBoneSceneNode.h index 668289271..eef55f6e0 100644 --- a/irr/include/IBoneSceneNode.h +++ b/irr/include/IBoneSceneNode.h @@ -48,7 +48,7 @@ const c8 *const BoneAnimationModeNames[] = { }; //! Interface for bones used for skeletal animation. -/** Used with ISkinnedMesh and IAnimatedMeshSceneNode. */ +/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */ class IBoneSceneNode : public ISceneNode { public: diff --git a/irr/include/IGUIElement.h b/irr/include/IGUIElement.h index cffac10d1..429bc06b3 100644 --- a/irr/include/IGUIElement.h +++ b/irr/include/IGUIElement.h @@ -34,10 +34,6 @@ public: AlignLeft(EGUIA_UPPERLEFT), AlignRight(EGUIA_UPPERLEFT), AlignTop(EGUIA_UPPERLEFT), AlignBottom(EGUIA_UPPERLEFT), Environment(environment), Type(type) { -#ifdef _DEBUG - setDebugName("IGUIElement"); -#endif - // if we were given a parent to attach to if (parent) { parent->addChildToEnd(this); diff --git a/irr/include/IImage.h b/irr/include/IImage.h index 47349ed1a..a303201a9 100644 --- a/irr/include/IImage.h +++ b/irr/include/IImage.h @@ -342,6 +342,8 @@ public: return 16; case ECF_R16G16: return 32; + case ECF_A2R10G10B10: + return 32; case ECF_R16F: return 16; case ECF_G16R16F: diff --git a/irr/include/IReferenceCounted.h b/irr/include/IReferenceCounted.h index 68aa20fb6..65c991db2 100644 --- a/irr/include/IReferenceCounted.h +++ b/irr/include/IReferenceCounted.h @@ -136,31 +136,6 @@ public: return ReferenceCounter; } -#ifdef _DEBUG - //! Returns the debug name of the object. - /** The Debugname may only be set and changed by the object - itself. This method should only be used in Debug mode. - \return Returns a string, previously set by setDebugName(); */ - const c8 *getDebugName() const - { - return DebugName; - } - -protected: - //! Sets the debug name of the object. - /** The Debugname may only be set and changed by the object - itself. This method should only be used in Debug mode. - \param newName: New debug name to set. */ - void setDebugName(const c8 *newName) - { - DebugName = newName; - } - -private: - //! The debug name. - const c8 *DebugName = nullptr; -#endif - private: //! The reference counter. Mutable to do reference counting on const objects. diff --git a/irr/include/IRenderTarget.h b/irr/include/IRenderTarget.h index 1e70c1e06..d4e5960a2 100644 --- a/irr/include/IRenderTarget.h +++ b/irr/include/IRenderTarget.h @@ -26,6 +26,7 @@ enum E_CUBE_SURFACE }; //! Interface of a Render Target. +/** This is a framebuffer object (FBO) in OpenGL. */ class IRenderTarget : public virtual IReferenceCounted { public: @@ -108,11 +109,6 @@ protected: //! Driver type of render target. E_DRIVER_TYPE DriverType; - -private: - // no copying (IReferenceCounted still allows that for reasons which take some time to work around) - IRenderTarget(const IRenderTarget &); - IRenderTarget &operator=(const IRenderTarget &); }; } diff --git a/irr/include/ISceneManager.h b/irr/include/ISceneManager.h index 18521dbe9..25e1e5fe4 100644 --- a/irr/include/ISceneManager.h +++ b/irr/include/ISceneManager.h @@ -11,7 +11,6 @@ #include "SColor.h" #include "ESceneNodeTypes.h" #include "SceneParameters.h" // IWYU pragma: export -#include "ISkinnedMesh.h" namespace irr { @@ -55,9 +54,6 @@ enum E_SCENE_NODE_RENDER_PASS //! Camera pass. The active view is set up here. The very first pass. ESNRP_CAMERA = 1, - //! In this pass, lights are transformed into camera space and added to the driver - ESNRP_LIGHT = 2, - //! This is used for sky boxes. ESNRP_SKY_BOX = 4, @@ -85,9 +81,6 @@ enum E_SCENE_NODE_RENDER_PASS //! Transparent effect scene nodes, drawn after Transparent nodes. They are sorted from back to front and drawn in that order. ESNRP_TRANSPARENT_EFFECT = 32, - //! Drawn after the solid nodes, before the transparent nodes, the time for drawing shadow volumes - ESNRP_SHADOW = 64, - //! Drawn after transparent effect nodes. For custom gui's. Unsorted (in order nodes registered themselves). ESNRP_GUI = 128 @@ -99,6 +92,7 @@ class IBillboardSceneNode; class ICameraSceneNode; class IDummyTransformationSceneNode; class IMesh; +class SkinnedMesh; class IMeshBuffer; class IMeshCache; class ISceneCollisionManager; @@ -127,189 +121,6 @@ public: //! Get pointer to an animatable mesh. Loads the file if not loaded already. /** * If you want to remove a loaded mesh from the cache again, use removeMesh(). - * Currently there are the following mesh formats supported: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
FormatDescription
3D Studio (.3ds)Loader for 3D-Studio files which lots of 3D packages - * are able to export. Only static meshes are currently - * supported by this importer.
3D World Studio (.smf)Loader for Leadwerks SMF mesh files, a simple mesh format - * containing static geometry for games. The proprietary .STF texture format - * is not supported yet. This loader was originally written by Joseph Ellis.
Bliz Basic B3D (.b3d)Loader for blitz basic files, developed by Mark - * Sibly. This is the ideal animated mesh format for game - * characters as it is both rigidly defined and widely - * supported by modeling and animation software. - * As this format supports skeletal animations, an - * ISkinnedMesh will be returned by this importer.
Cartography shop 4 (.csm)Cartography Shop is a modeling program for creating - * architecture and calculating lighting. Irrlicht can - * directly import .csm files thanks to the IrrCSM library - * created by Saurav Mohapatra which is now integrated - * directly in Irrlicht. - *
Delgine DeleD (.dmf)DeleD (delgine.com) is a 3D editor and level-editor - * combined into one and is specifically designed for 3D - * game-development. With this loader, it is possible to - * directly load all geometry is as well as textures and - * lightmaps from .dmf files. To set texture and - * material paths, see scene::DMF_USE_MATERIALS_DIRS. - * It is also possible to flip the alpha texture by setting - * scene::DMF_FLIP_ALPHA_TEXTURES to true and to set the - * material transparent reference value by setting - * scene::DMF_ALPHA_CHANNEL_REF to a float between 0 and - * 1. The loader is based on Salvatore Russo's .dmf - * loader, I just changed some parts of it. Thanks to - * Salvatore for his work and for allowing me to use his - * code in Irrlicht and put it under Irrlicht's license. - * For newer and more enhanced versions of the loader, - * take a look at delgine.com. - *
DirectX (.x)Platform independent importer (so not D3D-only) for - * .x files. Most 3D packages can export these natively - * and there are several tools for them available, e.g. - * the Maya exporter included in the DX SDK. - * .x files can include skeletal animations and Irrlicht - * is able to play and display them, users can manipulate - * the joints via the ISkinnedMesh interface. Currently, - * Irrlicht only supports uncompressed .x files.
Half-Life model (.mdl)This loader opens Half-life 1 models, it was contributed - * by Fabio Concas and adapted by Thomas Alten.
LightWave (.lwo)Native to NewTek's LightWave 3D, the LWO format is well - * known and supported by many exporters. This loader will - * import LWO2 models including lightmaps, bumpmaps and - * reflection textures.
Maya (.obj)Most 3D software can create .obj files which contain - * static geometry without material data. The material - * files .mtl are also supported. This importer for - * Irrlicht can load them directly.
Milkshape (.ms3d).MS3D files contain models and sometimes skeletal - * animations from the Milkshape 3D modeling and animation - * software. Like the other skeletal mesh loaders, joints - * are exposed via the ISkinnedMesh animated mesh type.
My3D (.my3d).my3D is a flexible 3D file format. The My3DTools - * contains plug-ins to export .my3D files from several - * 3D packages. With this built-in importer, Irrlicht - * can read and display those files directly. This - * loader was written by Zhuck Dimitry who also created - * the whole My3DTools package. - *
OCT (.oct)The oct file format contains 3D geometry and - * lightmaps and can be loaded directly by Irrlicht. OCT - * files
can be created by FSRad, Paul Nette's - * radiosity processor or exported from Blender using - * OCTTools which can be found in the exporters/OCTTools - * directory of the SDK. Thanks to Murphy McCauley for - * creating all this.
OGRE Meshes (.mesh)Ogre .mesh files contain 3D data for the OGRE 3D - * engine. Irrlicht can read and display them directly - * with this importer. To define materials for the mesh, - * copy a .material file named like the corresponding - * .mesh file where the .mesh file is. (For example - * ogrehead.material for ogrehead.mesh). Thanks to - * Christian Stehno who wrote and contributed this - * loader.
Pulsar LMTools (.lmts)LMTools is a set of tools (Windows & Linux) for - * creating lightmaps. Irrlicht can directly read .lmts - * files thanks to
the importer created by Jonas - * Petersen. - * Notes for
this version of the loader:
- * - It does not recognize/support user data in the - * *.lmts files.
- * - The TGAs generated by LMTools don't work in - * Irrlicht for some reason (the textures are upside - * down). Opening and resaving them in a graphics app - * will solve the problem.
Quake 3 levels (.bsp)Quake 3 is a popular game by IDSoftware, and .pk3 - * files contain .bsp files and textures/lightmaps - * describing huge prelighted levels. Irrlicht can read - * .pk3 and .bsp files directly and thus render Quake 3 - * levels directly. Written by Nikolaus Gebhardt - * enhanced by Dean P. Macri with the curved surfaces - * feature.
Quake 2 models (.md2)Quake 2 models are characters with morph target - * animation. Irrlicht can read, display and animate - * them directly with this importer.
Quake 3 models (.md3)Quake 3 models are characters with morph target - * animation, they contain mount points for weapons and body - * parts and are typically made of several sections which are - * manually joined together.
Stanford Triangle (.ply)Invented by Stanford University and known as the native - * format of the infamous "Stanford Bunny" model, this is a - * popular static mesh format used by 3D scanning hardware - * and software. This loader supports extremely large models - * in both ASCII and binary format, but only has rudimentary - * material support in the form of vertex colors and texture - * coordinates.
Stereolithography (.stl)The STL format is used for rapid prototyping and - * computer-aided manufacturing, thus has no support for - * materials.
- * - * To load and display a mesh quickly, just do this: - * \code - * SceneManager->addAnimatedMeshSceneNode( - * SceneManager->getMesh("yourmesh.3ds")); - * \endcode * If you would like to implement and add your own file format loader to Irrlicht, * see addExternalMeshLoader(). * \param file File handle of the mesh to load. @@ -600,13 +411,7 @@ public: //! Get a skinned mesh, which is not available as header-only code /** Note: You need to drop() the pointer after use again, see IReferenceCounted::drop() for details. */ - virtual ISkinnedMesh *createSkinnedMesh() = 0; - - //! Sets ambient color of the scene - virtual void setAmbientLight(const video::SColorf &ambientColor) = 0; - - //! Get ambient color of the scene - virtual const video::SColorf &getAmbientLight() const = 0; + virtual SkinnedMesh *createSkinnedMesh() = 0; //! Get current render pass. virtual E_SCENE_NODE_RENDER_PASS getCurrentRenderPass() const = 0; diff --git a/irr/include/ISkinnedMesh.h b/irr/include/ISkinnedMesh.h deleted file mode 100644 index af241f55d..000000000 --- a/irr/include/ISkinnedMesh.h +++ /dev/null @@ -1,226 +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 "irrArray.h" -#include "IAnimatedMesh.h" -#include "SSkinMeshBuffer.h" -#include "quaternion.h" - -#include -#include - -namespace irr -{ -namespace scene -{ - -enum E_INTERPOLATION_MODE -{ - // constant does use the current key-values without interpolation - EIM_CONSTANT = 0, - - // linear interpolation - EIM_LINEAR, - - //! count of all available interpolation modes - EIM_COUNT -}; - -//! Interface for using some special functions of Skinned meshes -class ISkinnedMesh : public IAnimatedMesh -{ -public: - //! Gets joint count. - /** \return Amount of joints in the skeletal animated mesh. */ - virtual u32 getJointCount() const = 0; - - //! Gets the name of a joint. - /** \param number: Zero based index of joint. The last joint - has the number getJointCount()-1; - \return Name of joint and null if an error happened. */ - virtual const std::optional &getJointName(u32 number) const = 0; - - //! Gets a joint number from its name - /** \param name: Name of the joint. - \return Number of the joint or std::nullopt if not found. */ - virtual std::optional getJointNumber(const std::string &name) const = 0; - - //! Use animation from another mesh - /** The animation is linked (not copied) based on joint names - so make sure they are unique. - \return True if all joints in this mesh were - matched up (empty names will not be matched, and it's case - sensitive). Unmatched joints will not be animated. */ - virtual bool useAnimationFrom(const ISkinnedMesh *mesh) = 0; - - //! Update Normals when Animating - /** \param on If false don't animate, which is faster. - Else update normals, which allows for proper lighting of - animated meshes. */ - virtual void updateNormalsWhenAnimating(bool on) = 0; - - //! Sets Interpolation Mode - virtual void setInterpolationMode(E_INTERPOLATION_MODE mode) = 0; - - //! Animates this mesh's joints based on frame input - virtual void animateMesh(f32 frame, f32 blend) = 0; - - //! Preforms a software skin on this mesh based of joint positions - virtual void skinMesh() = 0; - - //! converts the vertex type of all meshbuffers to tangents. - /** E.g. used for bump mapping. */ - virtual void convertMeshToTangents() = 0; - - //! Allows to enable hardware skinning. - /* This feature is not implemented in Irrlicht yet */ - virtual bool setHardwareSkinning(bool on) = 0; - - //! Refreshes vertex data cached in joints such as positions and normals - virtual void refreshJointCache() = 0; - - //! Moves the mesh into static position. - virtual void resetAnimation() = 0; - - //! A vertex weight - struct SWeight - { - //! Index of the mesh buffer - u16 buffer_id; // I doubt 32bits is needed - - //! Index of the vertex - u32 vertex_id; // Store global ID here - - //! Weight Strength/Percentage (0-1) - f32 strength; - - private: - //! Internal members used by CSkinnedMesh - friend class CSkinnedMesh; - char *Moved; - core::vector3df StaticPos; - core::vector3df StaticNormal; - }; - - //! Animation keyframe which describes a new position - struct SPositionKey - { - f32 frame; - core::vector3df position; - }; - - //! Animation keyframe which describes a new scale - struct SScaleKey - { - f32 frame; - core::vector3df scale; - }; - - //! Animation keyframe which describes a new rotation - struct SRotationKey - { - f32 frame; - core::quaternion rotation; - }; - - //! Joints - struct SJoint - { - SJoint() : - UseAnimationFrom(0), GlobalSkinningSpace(false), - positionHint(-1), scaleHint(-1), rotationHint(-1) - { - } - - //! The name of this joint - std::optional Name; - - //! Local matrix of this joint - core::matrix4 LocalMatrix; - - //! List of child joints - core::array Children; - - //! List of attached meshes - core::array AttachedMeshes; - - //! Animation keys causing translation change - core::array PositionKeys; - - //! Animation keys causing scale change - core::array ScaleKeys; - - //! Animation keys causing rotation change - core::array RotationKeys; - - //! Skin weights - core::array Weights; - - //! 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 CSkinnedMesh - friend class CSkinnedMesh; - - SJoint *UseAnimationFrom; - bool GlobalSkinningSpace; - - s32 positionHint; - s32 scaleHint; - s32 rotationHint; - }; - - // Interface for the mesh loaders (finalize should lock these functions, and they should have some prefix like loader_ - - // these functions will use the needed arrays, set values, etc to help the loaders - - //! exposed for loaders: to add mesh buffers - virtual core::array &getMeshBuffers() = 0; - - //! exposed for loaders: joints list - virtual core::array &getAllJoints() = 0; - - //! exposed for loaders: joints list - virtual const core::array &getAllJoints() const = 0; - - //! loaders should call this after populating the mesh - virtual void finalize() = 0; - - //! Adds a new meshbuffer to the mesh, access it as last one - virtual SSkinMeshBuffer *addMeshBuffer() = 0; - - //! Adds a new meshbuffer to the mesh, access it as last one - virtual void addMeshBuffer(SSkinMeshBuffer *meshbuf) = 0; - - //! Adds a new joint to the mesh, access it as last one - virtual SJoint *addJoint(SJoint *parent = 0) = 0; - - //! Adds a new weight to the mesh, access it as last one - virtual SWeight *addWeight(SJoint *joint) = 0; - - //! Adds a new position key to the mesh, access it as last one - virtual SPositionKey *addPositionKey(SJoint *joint) = 0; - //! Adds a new scale key to the mesh, access it as last one - virtual SScaleKey *addScaleKey(SJoint *joint) = 0; - //! Adds a new rotation key to the mesh, access it as last one - virtual SRotationKey *addRotationKey(SJoint *joint) = 0; - - //! Check if the mesh is non-animated - virtual bool isStatic() = 0; -}; - -} // end namespace scene -} // end namespace irr diff --git a/irr/include/ITexture.h b/irr/include/ITexture.h index 8760c9f2a..869a325e0 100644 --- a/irr/include/ITexture.h +++ b/irr/include/ITexture.h @@ -156,6 +156,9 @@ enum E_TEXTURE_TYPE //! 2D texture. ETT_2D, + //! 2D texture with multisampling. + ETT_2D_MS, + //! Cubemap texture. ETT_CUBEMAP }; diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 035f5ad77..ebb39dfd9 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -131,7 +131,6 @@ public: /** The following names can be queried for the given types: MaxTextures (int) The maximum number of simultaneous textures supported by the driver. This can be less than the supported number of textures of the driver. Use _IRR_MATERIAL_MAX_TEXTURES_ to adapt the number. MaxSupportedTextures (int) The maximum number of simultaneous textures supported by the fixed function pipeline of the (hw) driver. The actual supported number of textures supported by the engine can be lower. - MaxLights (int) Number of hardware lights supported in the fixed function pipeline of the driver, typically 6-8. Use light manager or deferred shading for more. MaxAnisotropy (int) Number of anisotropy levels supported for filtering. At least 1, max is typically at 16 or 32. MaxAuxBuffers (int) Special render buffers, which are currently not really usable inside Irrlicht. Only supported by OpenGL MaxMultipleRenderTargets (int) Number of render targets which can be bound simultaneously. Rendering to MRTs is done via shaders. @@ -273,6 +272,14 @@ public: virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name = "rt", const ECOLOR_FORMAT format = ECF_UNKNOWN) = 0; + //! Adds a multisampled render target texture to the texture cache. + /** \param msaa The number of samples to use, values that make sense are > 1. + Only works if the driver supports the EVDF_TEXTURE_MULTISAMPLE feature, + check via queryFeature. + \see addRenderTargetTexture */ + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name = "rt", const ECOLOR_FORMAT format = ECF_UNKNOWN) = 0; + //! Adds a new render target texture with 6 sides for a cubemap map to the texture cache. /** \param sideLen Length of one cubemap side. \param name A name for the texture. Later calls of getTexture() with this name will return this texture. @@ -358,6 +365,10 @@ public: //! Remove all render targets. virtual void removeAllRenderTargets() = 0; + //! Blit contents of one render target to another one. + /** This is glBlitFramebuffer in OpenGL. */ + virtual void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) = 0; + //! Sets a boolean alpha channel on the texture based on a color key. /** This makes the texture fully transparent at the texels where this color key can be found when using for example draw2DImage @@ -1098,15 +1109,6 @@ public: //! Get the graphics card vendor name. virtual core::stringc getVendorInfo() = 0; - //! Only used by the engine internally. - /** The ambient color is set in the scene manager, see - scene::ISceneManager::setAmbientLight(). - \param color New color of the ambient light. */ - virtual void setAmbientLight(const SColorf &color) = 0; - - //! Get the global ambient light currently used by the driver - virtual const SColorf &getAmbientLight() const = 0; - //! Only used by the engine internally. /** Passes the global material flag AllowZWriteOnTransparent. Use the SceneManager attribute to set this value from your app. @@ -1116,19 +1118,6 @@ public: //! Get the maximum texture size supported. virtual core::dimension2du getMaxTextureSize() const = 0; - //! Color conversion convenience function - /** Convert an image (as array of pixels) from source to destination - array, thereby converting the color format. The pixel size is - determined by the color formats. - \param sP Pointer to source - \param sF Color format of source - \param sN Number of pixels to convert, both array must be large enough - \param dP Pointer to destination - \param dF Color format of destination - */ - virtual void convertColor(const void *sP, ECOLOR_FORMAT sF, s32 sN, - void *dP, ECOLOR_FORMAT dF) const = 0; - //! Check if the driver supports creating textures with the given color format /** \return True if the format is available, false if not. */ virtual bool queryTextureFormat(ECOLOR_FORMAT format) const = 0; diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h index dd7306633..8f7156236 100644 --- a/irr/include/SAnimatedMesh.h +++ b/irr/include/SAnimatedMesh.h @@ -21,9 +21,6 @@ struct SAnimatedMesh final : public IAnimatedMesh SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) : IAnimatedMesh(), FramesPerSecond(25.f), Type(type) { -#ifdef _DEBUG - setDebugName("SAnimatedMesh"); -#endif addMesh(mesh); recalculateBoundingBox(); } diff --git a/irr/include/SColor.h b/irr/include/SColor.h index a2dd52603..0845b31f7 100644 --- a/irr/include/SColor.h +++ b/irr/include/SColor.h @@ -72,6 +72,9 @@ enum ECOLOR_FORMAT //! 32 bit format using 16 bits for the red and green channels. ECF_R16G16, + //! 32 bit format using 10 bits for R, G, B and 2 for alpha. + ECF_A2R10G10B10, + /** Depth and stencil formats. */ //! 16 bit format using 16 bits for depth. @@ -91,7 +94,7 @@ enum ECOLOR_FORMAT }; //! Names for ECOLOR_FORMAT types -const c8 *const ColorFormatNames[ECF_UNKNOWN + 2] = { +const c8 *const ColorFormatNames[] = { "A1R5G5B5", "R5G6B5", "R8G8B8", @@ -106,6 +109,7 @@ const c8 *const ColorFormatNames[ECF_UNKNOWN + 2] = { "R8G8", "R16", "R16G16", + "A2R10G10B10", "D16", "D24", "D32", @@ -114,6 +118,9 @@ const c8 *const ColorFormatNames[ECF_UNKNOWN + 2] = { 0, }; +static_assert(sizeof(ColorFormatNames) / sizeof(ColorFormatNames[0]) + == ECF_UNKNOWN + 2, "name table size mismatch"); + //! Creates a 16 bit A1R5G5B5 color inline u16 RGBA16(u32 r, u32 g, u32 b, u32 a = 0xFF) { @@ -224,12 +231,6 @@ inline u32 getBlue(u16 color) return (color & 0x1F); } -//! Returns the average from a 16 bit A1R5G5B5 color -inline s32 getAverage(s16 color) -{ - return ((getRed(color) << 3) + (getGreen(color) << 3) + (getBlue(color) << 3)) / 3; -} - //! Class representing a 32 bit ARGB color. /** The color values for alpha, red, green, and blue are stored in a single u32. So all four values may be between 0 and 255. @@ -275,24 +276,12 @@ public: 0 means no blue, 255 means full blue. */ u32 getBlue() const { return color & 0xff; } - //! Get lightness of the color in the range [0,255] - f32 getLightness() const - { - return 0.5f * (core::max_(core::max_(getRed(), getGreen()), getBlue()) + core::min_(core::min_(getRed(), getGreen()), getBlue())); - } - - //! Get luminance of the color in the range [0,255]. - f32 getLuminance() const + //! Get an approximate brightness value of the color in the range [0,255] + f32 getBrightness() const { return 0.3f * getRed() + 0.59f * getGreen() + 0.11f * getBlue(); } - //! Get average intensity of the color in the range [0,255]. - u32 getAverage() const - { - return (getRed() + getGreen() + getBlue()) / 3; - } - //! Sets the alpha component of the Color. /** The alpha component defines how transparent a color should be. \param a The alpha value of the color. 0 is fully transparent, 255 is fully opaque. */ @@ -362,9 +351,9 @@ public: /** \return True if this color is smaller than the other one */ bool operator<(const SColor &other) const { return (color < other.color); } - //! Adds two colors, result is clamped to 0..255 values + //! Adds two colors in a gamma-incorrect way /** \param other Color to add to this color - \return Addition of the two colors, clamped to 0..255 values */ + \return Sum of the two non-linear colors, clamped to 0..255 values */ SColor operator+(const SColor &other) const { return SColor(core::min_(getAlpha() + other.getAlpha(), 255u), @@ -374,7 +363,9 @@ public: } //! Interpolates the color with a f32 value to another color - /** \param other: Other color + /** Note that the interpolation is neither physically nor perceptually + linear since it happens directly in the sRGB color space. + \param other: Other color \param d: value between 0.0f and 1.0f. d=0 returns other, d=1 returns this, values between interpolate. \return Interpolated color. */ SColor getInterpolated(const SColor &other, f32 d) const @@ -387,34 +378,6 @@ public: (u32)core::round32(other.getBlue() * inv + getBlue() * d)); } - //! Returns interpolated color. ( quadratic ) - /** \param c1: first color to interpolate with - \param c2: second color to interpolate with - \param d: value between 0.0f and 1.0f. */ - SColor getInterpolated_quadratic(const SColor &c1, const SColor &c2, f32 d) const - { - // this*(1-d)*(1-d) + 2 * c1 * (1-d) + c2 * d * d; - d = core::clamp(d, 0.f, 1.f); - const f32 inv = 1.f - d; - const f32 mul0 = inv * inv; - const f32 mul1 = 2.f * d * inv; - const f32 mul2 = d * d; - - return SColor( - core::clamp(core::floor32( - getAlpha() * mul0 + c1.getAlpha() * mul1 + c2.getAlpha() * mul2), - 0, 255), - core::clamp(core::floor32( - getRed() * mul0 + c1.getRed() * mul1 + c2.getRed() * mul2), - 0, 255), - core::clamp(core::floor32( - getGreen() * mul0 + c1.getGreen() * mul1 + c2.getGreen() * mul2), - 0, 255), - core::clamp(core::floor32( - getBlue() * mul0 + c1.getBlue() * mul1 + c2.getBlue() * mul2), - 0, 255)); - } - //! set the color by expecting data in the given format /** \param data: must point to valid memory containing color information in the given format \param format: tells the format in which data is available @@ -508,7 +471,7 @@ public: SColorf(f32 r, f32 g, f32 b, f32 a = 1.0f) : r(r), g(g), b(b), a(a) {} - //! Constructs a color from 32 bit Color. + //! Constructs a color from 32 bit Color without gamma correction /** \param c: 32 bit color from which this SColorf class is constructed from. */ SColorf(SColor c) @@ -520,7 +483,7 @@ public: a = c.getAlpha() * inv; } - //! Converts this color to a SColor without floats. + //! Converts this color to a SColor without gamma correction SColor toSColor() const { return SColor((u32)core::round32(a * 255.0f), (u32)core::round32(r * 255.0f), (u32)core::round32(g * 255.0f), (u32)core::round32(b * 255.0f)); @@ -558,7 +521,9 @@ public: } //! Interpolates the color with a f32 value to another color - /** \param other: Other color + /** Note that the interpolation is neither physically nor perceptually + linear if it happens directly in the sRGB color space. + \param other: Other color \param d: value between 0.0f and 1.0f \return Interpolated color. */ SColorf getInterpolated(const SColorf &other, f32 d) const @@ -569,45 +534,6 @@ public: other.g * inv + g * d, other.b * inv + b * d, other.a * inv + a * d); } - //! Returns interpolated color. ( quadratic ) - /** \param c1: first color to interpolate with - \param c2: second color to interpolate with - \param d: value between 0.0f and 1.0f. */ - inline SColorf getInterpolated_quadratic(const SColorf &c1, const SColorf &c2, - f32 d) const - { - d = core::clamp(d, 0.f, 1.f); - // this*(1-d)*(1-d) + 2 * c1 * (1-d) + c2 * d * d; - const f32 inv = 1.f - d; - const f32 mul0 = inv * inv; - const f32 mul1 = 2.f * d * inv; - const f32 mul2 = d * d; - - return SColorf(r * mul0 + c1.r * mul1 + c2.r * mul2, - g * mul0 + c1.g * mul1 + c2.g * mul2, - b * mul0 + c1.b * mul1 + c2.b * mul2, - a * mul0 + c1.a * mul1 + c2.a * mul2); - } - - //! Sets a color component by index. R=0, G=1, B=2, A=3 - void setColorComponentValue(s32 index, f32 value) - { - switch (index) { - case 0: - r = value; - break; - case 1: - g = value; - break; - case 2: - b = value; - break; - case 3: - a = value; - break; - } - } - //! Returns the alpha component of the color in the range 0.0 (transparent) to 1.0 (opaque) f32 getAlpha() const { return a; } diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h index 7a939317c..867557154 100644 --- a/irr/include/SMaterial.h +++ b/irr/include/SMaterial.h @@ -230,14 +230,14 @@ const c8 *const ZWriteNames[] = { /** SMaterial might ignore some textures in most function, like assignment and comparison, when SIrrlichtCreationParameters::MaxTextureUnits is set to a lower number. */ -const u32 MATERIAL_MAX_TEXTURES = 4; +constexpr static u32 MATERIAL_MAX_TEXTURES = 4; //! Struct for holding parameters for a material renderer // Note for implementors: Serialization is in CNullDriver class SMaterial { public: - //! Default constructor. Creates a solid, lit material with white colors + //! Default constructor. Creates a solid material SMaterial() : MaterialType(EMT_SOLID), ColorParam(0, 0, 0, 0), MaterialTypeParam(0.0f), Thickness(1.0f), ZBuffer(ECFN_LESSEQUAL), @@ -257,7 +257,7 @@ public: E_MATERIAL_TYPE MaterialType; //! Custom color parameter, can be used by custom shader materials. - // See MainShaderConstantSetter in Minetest. + // See MainShaderConstantSetter in Luanti. SColor ColorParam; //! Free parameter, dependent on the material type. @@ -427,10 +427,13 @@ public: PolygonOffsetDepthBias != b.PolygonOffsetDepthBias || PolygonOffsetSlopeScale != b.PolygonOffsetSlopeScale || UseMipMaps != b.UseMipMaps; - for (u32 i = 0; (i < MATERIAL_MAX_TEXTURES) && !different; ++i) { - different |= (TextureLayers[i] != b.TextureLayers[i]); + if (different) + return true; + for (u32 i = 0; i < MATERIAL_MAX_TEXTURES; ++i) { + if (TextureLayers[i] != b.TextureLayers[i]) + return true; } - return different; + return false; } //! Equality operator @@ -477,5 +480,19 @@ public: //! global const identity Material IRRLICHT_API extern SMaterial IdentityMaterial; + } // end namespace video } // end namespace irr + +template<> +struct std::hash +{ + /// @brief std::hash specialization for video::SMaterial + std::size_t operator()(const irr::video::SMaterial &m) const noexcept + { + // basic implementation that hashes the two things most likely to differ + auto h1 = std::hash{}(m.getTexture(0)); + auto h2 = std::hash{}(m.MaterialType); + return (h1 << 1) ^ h2; + } +}; diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index 15fa65115..c9a76b051 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -17,12 +17,7 @@ namespace scene struct SMesh final : public IMesh { //! constructor - SMesh() - { -#ifdef _DEBUG - setDebugName("SMesh"); -#endif - } + SMesh() {} //! destructor virtual ~SMesh() diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index b0b6658b3..82c0d9f37 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -22,9 +22,6 @@ struct SSkinMeshBuffer final : public IMeshBuffer VertexType(vt), PrimitiveType(EPT_TRIANGLES), BoundingBoxNeedsRecalculated(true) { -#ifdef _DEBUG - setDebugName("SSkinMeshBuffer"); -#endif Vertices_Tangents = new SVertexBufferTangents(); Vertices_2TCoords = new SVertexBufferLightMap(); Vertices_Standard = new SVertexBuffer(); diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h new file mode 100644 index 000000000..4cf5905c9 --- /dev/null +++ b/irr/include/SkinnedMesh.h @@ -0,0 +1,419 @@ +// 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 "IAnimatedMesh.h" +#include "ISceneManager.h" +#include "SMeshBuffer.h" +#include "SSkinMeshBuffer.h" +#include "quaternion.h" +#include "vector3d.h" + +#include +#include + +namespace irr +{ +namespace scene +{ + +class IAnimatedMeshSceneNode; +class IBoneSceneNode; +class ISceneManager; + +class SkinnedMesh : public IAnimatedMesh +{ +public: + //! constructor + SkinnedMesh() : + EndFrame(0.f), FramesPerSecond(25.f), + LastAnimatedFrame(-1), SkinnedLastFrame(false), + HasAnimation(false), PreparedForSkinning(false), + AnimateNormals(true), HardwareSkinning(false) + { + SkinningBuffers = &LocalBuffers; + } + + //! destructor + virtual ~SkinnedMesh(); + + //! If the duration is 0, it is a static (=non animated) mesh. + f32 getMaxFrameNumber() const override; + + //! 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; + + //! 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; + + //! returns the animated mesh for the given frame + IMesh *getMesh(f32) override; + + //! Animates joints based on frame input + void animateMesh(f32 frame); + + //! Performs a software skin on this mesh based of joint positions + void skinMesh(); + + //! returns amount of mesh buffers. + u32 getMeshBufferCount() const override; + + //! returns pointer to a mesh buffer + IMeshBuffer *getMeshBuffer(u32 nr) const override; + + //! 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; + + u32 getTextureSlot(u32 meshbufNr) const override; + + void setTextureSlot(u32 meshbufNr, u32 textureSlot); + + //! returns an axis aligned bounding box + const core::aabbox3d &getBoundingBox() const override { + return BoundingBox; + } + + //! set user axis aligned bounding box + void setBoundingBox(const core::aabbox3df &box) override { + BoundingBox = box; + } + + //! set the hardware mapping hint, for driver + void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override; + + //! flags the meshbuffer as changed, reloads hardware buffers + void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override; + + //! Returns the type of the animated mesh. + E_ANIMATED_MESH_TYPE getMeshType() const override { + return EAMT_SKINNED; + } + + //! Gets joint count. + u32 getJointCount() const; + + //! Gets the name of a joint. + /** \param number: Zero based index of joint. + \return Name of joint and null if an error happened. */ + const std::optional &getJointName(u32 number) const; + + //! Gets a joint number from its name + /** \param name: Name of the joint. + \return Number of the joint or std::nullopt if not found. */ + std::optional getJointNumber(const std::string &name) const; + + //! Update Normals when Animating + /** \param on If false don't animate, which is faster. + Else update normals, which allows for proper lighting of + animated meshes (default). */ + void updateNormalsWhenAnimating(bool on) { + AnimateNormals = on; + } + + //! converts the vertex type of all meshbuffers to tangents. + /** E.g. used for bump mapping. */ + void convertMeshToTangents(); + + //! Does the mesh have no animation + bool isStatic() const { + 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(); + + virtual 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); + + //! A vertex weight + struct SWeight + { + //! Index of the mesh buffer + u16 buffer_id; // I doubt 32bits is needed + + //! Index of the vertex + u32 vertex_id; // Store global ID here + + //! Weight Strength/Percentage (0-1) + f32 strength; + + private: + //! Internal members used by SkinnedMesh + friend class SkinnedMesh; + char *Moved; + core::vector3df StaticPos; + core::vector3df StaticNormal; + }; + + template + struct Channel { + struct Frame { + f32 time; + T value; + }; + std::vector frames; + bool interpolate = true; + + bool empty() const { + return frames.empty(); + } + + f32 getEndFrame() const { + return frames.empty() ? 0 : frames.back().time; + } + + void pushBack(f32 time, const T &value) { + frames.push_back({time, value}); + } + + void append(const Channel &other) { + frames.insert(frames.end(), other.frames.begin(), other.frames.end()); + } + + void cleanup() { + if (frames.empty()) + return; + + std::vector ordered; + ordered.push_back(frames.front()); + // Drop out-of-order frames + for (auto it = frames.begin() + 1; it != frames.end(); ++it) { + if (it->time > ordered.back().time) { + ordered.push_back(*it); + } + } + frames.clear(); + // Drop redundant middle keys + frames.push_back(ordered.front()); + for (u32 i = 1; i < ordered.size() - 1; ++i) { + if (ordered[i - 1].value != ordered[i].value + || ordered[i + 1].value != ordered[i].value) { + frames.push_back(ordered[i]); + } + } + if (ordered.size() > 1) + frames.push_back(ordered.back()); + frames.shrink_to_fit(); + } + + static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) { + core::quaternion result; + result.slerp(from, to, time, 0.001f); + return result; + } + + static core::vector3df interpolateValue(core::vector3df from, core::vector3df to, f32 time) { + // Note: `from` and `to` are swapped here compared to quaternion slerp + return to.getInterpolated(from, time); + } + + std::optional get(f32 time) const { + if (frames.empty()) + return std::nullopt; + + const auto next = std::lower_bound(frames.begin(), frames.end(), time, [](const auto& frame, f32 time) { + return frame.time < time; + }); + if (next == frames.begin()) + return next->value; + if (next == frames.end()) + return frames.back().value; + + const auto prev = next - 1; + if (!interpolate) + return prev->value; + + return interpolateValue(prev->value, next->value, (time - prev->time) / (next->time - prev->time)); + } + }; + + struct Keys { + Channel position; + Channel rotation; + Channel scale; + + bool empty() const { + return position.empty() && rotation.empty() && scale.empty(); + } + + void append(const Keys &other) { + position.append(other.position); + rotation.append(other.rotation); + scale.append(other.scale); + } + + f32 getEndFrame() const { + return std::max({ + position.getEndFrame(), + rotation.getEndFrame(), + scale.getEndFrame() + }); + } + + void updateTransform(f32 frame, + core::vector3df &t, core::quaternion &r, core::vector3df &s) const + { + if (auto pos = position.get(frame)) + t = *pos; + if (auto rot = rotation.get(frame)) + r = *rot; + if (auto scl = scale.get(frame)) + s = *scl; + } + + void cleanup() { + position.cleanup(); + rotation.cleanup(); + scale.cleanup(); + } + }; + + //! Joints + struct SJoint + { + SJoint() : GlobalSkinningSpace(false) {} + + //! The name of this joint + std::optional Name; + + //! Local matrix of this joint + core::matrix4 LocalMatrix; + + //! List of child joints + std::vector Children; + + //! List of attached meshes + std::vector AttachedMeshes; + + // Animation keyframes for translation, rotation, scale + Keys keys; + + //! Skin weights + std::vector Weights; + + //! 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; + }; + + const std::vector &getAllJoints() const { + return AllJoints; + } + +protected: + void checkForAnimation(); + + 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, + const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3); + + std::vector *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers + + std::vector LocalBuffers; + //! Mapping from meshbuffer number to bindable texture slot + std::vector TextureSlots; + + 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; + + f32 EndFrame; + f32 FramesPerSecond; + + f32 LastAnimatedFrame; + bool SkinnedLastFrame; + + bool HasAnimation; + bool PreparedForSkinning; + bool AnimateNormals; + bool HardwareSkinning; +}; + +// Interface for mesh loaders +class SkinnedMeshBuilder : public SkinnedMesh { +public: + SkinnedMeshBuilder() : SkinnedMesh() {} + + //! loaders should call this after populating the mesh + // returns *this, so do not try to drop the mesh builder instance + SkinnedMesh *finalize(); + + //! alternative method for adding joints + std::vector &getAllJoints() { + return AllJoints; + } + + //! Adds a new meshbuffer to the mesh, access it as last one + SSkinMeshBuffer *addMeshBuffer(); + + //! Adds a new meshbuffer to the mesh, access it as last one + void addMeshBuffer(SSkinMeshBuffer *meshbuf); + + //! Adds a new joint to the mesh, access it as last one + SJoint *addJoint(SJoint *parent = nullptr); + + void addPositionKey(SJoint *joint, f32 frame, core::vector3df pos); + void addRotationKey(SJoint *joint, f32 frame, core::quaternion rotation); + void addScaleKey(SJoint *joint, f32 frame, core::vector3df scale); + + //! Adds a new weight to the mesh, access it as last one + SWeight *addWeight(SJoint *joint); +}; + +} // end namespace scene +} // end namespace irr diff --git a/irr/include/irrArray.h b/irr/include/irrArray.h index 4a87382bc..834dc825c 100644 --- a/irr/include/irrArray.h +++ b/irr/include/irrArray.h @@ -59,8 +59,12 @@ public: { size_t allocated = m_data.capacity(); if (new_size < allocated) { - if (canShrink) - m_data.resize(new_size); + if (canShrink) { + // since capacity != size don't accidentally make it bigger + if (m_data.size() > new_size) + m_data.resize(new_size); + m_data.shrink_to_fit(); + } } else { m_data.reserve(new_size); } diff --git a/irr/include/matrix4.h b/irr/include/matrix4.h index 8fce0157a..ed74dc9d2 100644 --- a/irr/include/matrix4.h +++ b/irr/include/matrix4.h @@ -13,11 +13,6 @@ #include "rect.h" #include "IrrCompileConfig.h" // for IRRLICHT_API -// enable this to keep track of changes to the matrix -// and make simpler identity check for seldom changing matrices -// otherwise identity check will always compare the elements -// #define USE_MATRIX_TEST - namespace irr { namespace core @@ -81,9 +76,6 @@ public: //! Simple operator for directly accessing every element of the matrix. T &operator()(const s32 row, const s32 col) { -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return M[row * 4 + col]; } @@ -93,9 +85,6 @@ public: //! Simple operator for linearly accessing every element of the matrix. T &operator[](u32 index) { -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return M[index]; } @@ -112,19 +101,12 @@ public: const T *pointer() const { return M; } T *pointer() { -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return M; } //! Returns true if other matrix is equal to this matrix. constexpr bool operator==(const CMatrix4 &other) const { -#if defined(USE_MATRIX_TEST) - if (definitelyIdentityMatrix && other.definitelyIdentityMatrix) - return true; -#endif for (s32 i = 0; i < 16; ++i) if (M[i] != other.M[i]) return false; @@ -196,9 +178,13 @@ public: CMatrix4 &setInverseTranslation(const vector3d &translation); //! Make a rotation matrix from Euler angles. The 4th row and column are unmodified. + //! NOTE: Rotation order is ZYX. This means that vectors are + //! first rotated around the X, then the Y, and finally the Z axis. + //! NOTE: The rotation is done as per the right-hand rule. + //! See test_irr_matrix4.cpp if you're still unsure about the conventions used here. inline CMatrix4 &setRotationRadians(const vector3d &rotation); - //! Make a rotation matrix from Euler angles. The 4th row and column are unmodified. + //! Same as `setRotationRadians`, but uses degrees. CMatrix4 &setRotationDegrees(const vector3d &rotation); //! Get the rotation, as set by setRotation() when you already know the scale used to create the matrix @@ -254,12 +240,21 @@ public: [[nodiscard]] vector3d rotateAndScaleVect(const vector3d &vect) const; //! Transforms the vector by this matrix - /** This operation is performed as if the vector was 4d with the 4th component =1 */ - void transformVect(vector3df &vect) const; + /** This operation is performed as if the vector was 4d with the 4th component = 1 */ + [[nodiscard]] vector3d transformVect(const vector3d &v) const; + + //! Transforms the vector by this matrix + /** This operation is performed as if the vector was 4d with the 4th component = 1 */ + void transformVect(vector3d &vect) const { + const vector3d &v = vect; + vect = transformVect(v); + } //! Transforms input vector by this matrix and stores result in output vector - /** This operation is performed as if the vector was 4d with the 4th component =1 */ - void transformVect(vector3df &out, const vector3df &in) const; + /** This operation is performed as if the vector was 4d with the 4th component = 1 */ + void transformVect(vector3d &out, const vector3d &in) const { + out = transformVect(in); + } //! An alternate transform vector method, writing into an array of 4 floats /** This operation is performed as if the vector was 4d with the 4th component =1. @@ -443,31 +438,17 @@ public: //! Sets all matrix data members at once CMatrix4 &setM(const T *data); - //! Sets if the matrix is definitely identity matrix - void setDefinitelyIdentityMatrix(bool isDefinitelyIdentityMatrix); - - //! Gets if the matrix is definitely identity matrix - bool getDefinitelyIdentityMatrix() const; - //! Compare two matrices using the equal method bool equals(const core::CMatrix4 &other, const T tolerance = (T)ROUNDING_ERROR_f64) const; private: //! Matrix data, stored in row-major order T M[16]; -#if defined(USE_MATRIX_TEST) - //! Flag is this matrix is identity matrix - mutable u32 definitelyIdentityMatrix; -#endif }; // Default constructor template inline CMatrix4::CMatrix4(eConstructor constructor) -#if defined(USE_MATRIX_TEST) - : - definitelyIdentityMatrix(BIT_UNTESTED) -#endif { switch (constructor) { case EM4CONST_NOTHING: @@ -484,10 +465,6 @@ inline CMatrix4::CMatrix4(eConstructor constructor) // Copy constructor template inline CMatrix4::CMatrix4(const CMatrix4 &other, eConstructor constructor) -#if defined(USE_MATRIX_TEST) - : - definitelyIdentityMatrix(BIT_UNTESTED) -#endif { switch (constructor) { case EM4CONST_IDENTITY: @@ -713,9 +690,6 @@ inline CMatrix4 &CMatrix4::setbyproduct_nocheck(const CMatrix4 &other_a M[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15]; M[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15]; M[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15]; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -787,9 +761,6 @@ inline CMatrix4 &CMatrix4::setTranslation(const vector3d &translation) M[12] = translation.X; M[13] = translation.Y; M[14] = translation.Z; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -799,9 +770,6 @@ inline CMatrix4 &CMatrix4::setInverseTranslation(const vector3d &transl M[12] = -translation.X; M[13] = -translation.Y; M[14] = -translation.Z; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -811,9 +779,6 @@ inline CMatrix4 &CMatrix4::setScale(const vector3d &scale) M[0] = scale.X; M[5] = scale.Y; M[10] = scale.Z; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -856,30 +821,27 @@ inline CMatrix4 &CMatrix4::setInverseRotationDegrees(const vector3d &ro template inline CMatrix4 &CMatrix4::setRotationRadians(const vector3d &rotation) { - const f64 cr = cos(rotation.X); - const f64 sr = sin(rotation.X); - const f64 cp = cos(rotation.Y); - const f64 sp = sin(rotation.Y); - const f64 cy = cos(rotation.Z); - const f64 sy = sin(rotation.Z); + const f64 cPitch = cos(rotation.X); + const f64 sPitch = sin(rotation.X); + const f64 cYaw = cos(rotation.Y); + const f64 sYaw = sin(rotation.Y); + const f64 cRoll = cos(rotation.Z); + const f64 sRoll = sin(rotation.Z); - M[0] = (T)(cp * cy); - M[1] = (T)(cp * sy); - M[2] = (T)(-sp); + M[0] = (T)(cYaw * cRoll); + M[1] = (T)(cYaw * sRoll); + M[2] = (T)(-sYaw); - const f64 srsp = sr * sp; - const f64 crsp = cr * sp; + const f64 sPitch_sYaw = sPitch * sYaw; + const f64 cPitch_sYaw = cPitch * sYaw; - M[4] = (T)(srsp * cy - cr * sy); - M[5] = (T)(srsp * sy + cr * cy); - M[6] = (T)(sr * cp); + M[4] = (T)(sPitch_sYaw * cRoll - cPitch * sRoll); + M[5] = (T)(sPitch_sYaw * sRoll + cPitch * cRoll); + M[6] = (T)(sPitch * cYaw); - M[8] = (T)(crsp * cy + sr * sy); - M[9] = (T)(crsp * sy - sr * cy); - M[10] = (T)(cr * cp); -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif + M[8] = (T)(cPitch_sYaw * cRoll + sPitch * sRoll); + M[9] = (T)(cPitch_sYaw * sRoll - sPitch * cRoll); + M[10] = (T)(cPitch * cYaw); return *this; } @@ -960,30 +922,27 @@ inline core::vector3d CMatrix4::getRotationDegrees() const template inline CMatrix4 &CMatrix4::setInverseRotationRadians(const vector3d &rotation) { - f64 cr = cos(rotation.X); - f64 sr = sin(rotation.X); - f64 cp = cos(rotation.Y); - f64 sp = sin(rotation.Y); - f64 cy = cos(rotation.Z); - f64 sy = sin(rotation.Z); + f64 cPitch = cos(rotation.X); + f64 sPitch = sin(rotation.X); + f64 cYaw = cos(rotation.Y); + f64 sYaw = sin(rotation.Y); + f64 cRoll = cos(rotation.Z); + f64 sRoll = sin(rotation.Z); - M[0] = (T)(cp * cy); - M[4] = (T)(cp * sy); - M[8] = (T)(-sp); + M[0] = (T)(cYaw * cRoll); + M[4] = (T)(cYaw * sRoll); + M[8] = (T)(-sYaw); - f64 srsp = sr * sp; - f64 crsp = cr * sp; + f64 sPitch_sYaw = sPitch * sYaw; + f64 cPitch_sYaw = cPitch * sYaw; - M[1] = (T)(srsp * cy - cr * sy); - M[5] = (T)(srsp * sy + cr * cy); - M[9] = (T)(sr * cp); + M[1] = (T)(sPitch_sYaw * cRoll - cPitch * sRoll); + M[5] = (T)(sPitch_sYaw * sRoll + cPitch * cRoll); + M[9] = (T)(sPitch * cYaw); - M[2] = (T)(crsp * cy + sr * sy); - M[6] = (T)(crsp * sy - sr * cy); - M[10] = (T)(cr * cp); -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif + M[2] = (T)(cPitch_sYaw * cRoll + sPitch * sRoll); + M[6] = (T)(cPitch_sYaw * sRoll - sPitch * cRoll); + M[10] = (T)(cPitch * cYaw); return *this; } @@ -1015,9 +974,6 @@ inline CMatrix4 &CMatrix4::setRotationAxisRadians(const T &angle, const ve M[9] = (T)(tz * axis.Y - sx); M[10] = (T)(tz * axis.Z + c); -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1028,9 +984,6 @@ inline CMatrix4 &CMatrix4::makeIdentity() { memset(M, 0, 16 * sizeof(T)); M[0] = M[5] = M[10] = M[15] = (T)1; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = true; -#endif return *this; } @@ -1041,10 +994,6 @@ inline CMatrix4 &CMatrix4::makeIdentity() template inline bool CMatrix4::isIdentity() const { -#if defined(USE_MATRIX_TEST) - if (definitelyIdentityMatrix) - return true; -#endif if (!core::equals(M[12], (T)0) || !core::equals(M[13], (T)0) || !core::equals(M[14], (T)0) || !core::equals(M[15], (T)1)) return false; @@ -1068,9 +1017,6 @@ inline bool CMatrix4::isIdentity() const if ((j != i) && (!iszero((*this)(i,j)))) return false; */ -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = true; -#endif return true; } @@ -1106,10 +1052,6 @@ inline bool CMatrix4::isOrthogonal() const template inline bool CMatrix4::isIdentity_integer_base() const { -#if defined(USE_MATRIX_TEST) - if (definitelyIdentityMatrix) - return true; -#endif if (IR(M[0]) != F32_VALUE_1) return false; if (IR(M[1]) != 0) @@ -1146,9 +1088,6 @@ inline bool CMatrix4::isIdentity_integer_base() const if (IR(M[15]) != F32_VALUE_1) return false; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = true; -#endif return true; } @@ -1173,25 +1112,13 @@ inline vector3d CMatrix4::scaleThenInvRotVect(const vector3d &v) const } template -inline void CMatrix4::transformVect(vector3df &vect) const +inline vector3d CMatrix4::transformVect(const vector3d &v) const { - T vector[3]; - - vector[0] = vect.X * M[0] + vect.Y * M[4] + vect.Z * M[8] + M[12]; - vector[1] = vect.X * M[1] + vect.Y * M[5] + vect.Z * M[9] + M[13]; - vector[2] = vect.X * M[2] + vect.Y * M[6] + vect.Z * M[10] + M[14]; - - vect.X = static_cast(vector[0]); - vect.Y = static_cast(vector[1]); - vect.Z = static_cast(vector[2]); -} - -template -inline void CMatrix4::transformVect(vector3df &out, const vector3df &in) const -{ - out.X = in.X * M[0] + in.Y * M[4] + in.Z * M[8] + M[12]; - out.Y = in.X * M[1] + in.Y * M[5] + in.Z * M[9] + M[13]; - out.Z = in.X * M[2] + in.Y * M[6] + in.Z * M[10] + M[14]; + return { + v.X * M[0] + v.Y * M[4] + v.Z * M[8] + M[12], + v.X * M[1] + v.Y * M[5] + v.Z * M[9] + M[13], + v.X * M[2] + v.Y * M[6] + v.Z * M[10] + M[14], + }; } template @@ -1402,9 +1329,6 @@ inline bool CMatrix4::getInverse(CMatrix4 &out) const m[1] * (m[6] * m[8] - m[4] * m[10]) + m[2] * (m[4] * m[9] - m[5] * m[8])); -#if defined(USE_MATRIX_TEST) - out.definitelyIdentityMatrix = definitelyIdentityMatrix; -#endif return true; } @@ -1433,9 +1357,6 @@ inline bool CMatrix4::getInversePrimitive(CMatrix4 &out) const out.M[14] = (T) - (M[12] * M[8] + M[13] * M[9] + M[14] * M[10]); out.M[15] = 1; -#if defined(USE_MATRIX_TEST) - out.definitelyIdentityMatrix = definitelyIdentityMatrix; -#endif return true; } @@ -1444,10 +1365,6 @@ inline bool CMatrix4::getInversePrimitive(CMatrix4 &out) const template inline bool CMatrix4::makeInverse() { -#if defined(USE_MATRIX_TEST) - if (definitelyIdentityMatrix) - return true; -#endif CMatrix4 temp(EM4CONST_NOTHING); if (getInverse(temp)) { @@ -1464,9 +1381,6 @@ inline CMatrix4 &CMatrix4::operator=(const T &scalar) for (s32 i = 0; i < 16; ++i) M[i] = scalar; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1509,9 +1423,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveFovRH( M[14] = (T)(2.0f * zNear * zFar / (zNear - zFar)); } -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1554,9 +1465,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveFovLH( M[14] = (T)(2.0f * zNear * zFar / (zNear - zFar)); } -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1589,9 +1497,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveFovInfinityLH( M[14] = (T)(zNear * (epsilon - 1.f)); M[15] = 0; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1631,9 +1536,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixOrthoLH( M[14] = (T) - (zFar + zNear) / (zFar - zNear); } -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1673,9 +1575,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixOrthoRH( M[14] = (T) - (zFar + zNear) / (zFar - zNear); } -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1716,9 +1615,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveRH( M[14] = (T)(2.0f * zNear * zFar / (zNear - zFar)); } -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1759,9 +1655,6 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveLH( M[14] = (T)(2.0f * zNear * zFar / (zNear - zFar)); } -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } @@ -1791,9 +1684,7 @@ inline CMatrix4 &CMatrix4::buildShadowMatrix(const core::vector3df &light, M[13] = (T)(-plane.D * light.Y); M[14] = (T)(-plane.D * light.Z); M[15] = (T)(-plane.D * point + d); -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif + return *this; } @@ -1831,9 +1722,7 @@ inline CMatrix4 &CMatrix4::buildCameraLookAtMatrixLH( M[13] = (T)-yaxis.dotProduct(position); M[14] = (T)-zaxis.dotProduct(position); M[15] = 1; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif + return *this; } @@ -1871,9 +1760,7 @@ inline CMatrix4 &CMatrix4::buildCameraLookAtMatrixRH( M[13] = (T)-yaxis.dotProduct(position); M[14] = (T)-zaxis.dotProduct(position); M[15] = 1; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif + return *this; } @@ -1924,9 +1811,6 @@ inline void CMatrix4::getTransposed(CMatrix4 &o) const o[13] = M[7]; o[14] = M[11]; o[15] = M[15]; -#if defined(USE_MATRIX_TEST) - o.definitelyIdentityMatrix = definitelyIdentityMatrix; -#endif } // used to scale <-1,-1><1,1> to viewport @@ -2064,9 +1948,6 @@ inline void CMatrix4::setRotationCenter(const core::vector3df ¢er, const M[13] = -M[1] * center.X - M[5] * center.Y - M[9] * center.Z + (center.Y - translation.Y); M[14] = -M[2] * center.X - M[6] * center.Y - M[10] * center.Z + (center.Z - translation.Z); M[15] = (T)1.0; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif } /*! @@ -2108,9 +1989,7 @@ inline CMatrix4 &CMatrix4::buildTextureTransform(f32 rotateRad, M[13] = 0; M[14] = 0; M[15] = 1; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif + return *this; } @@ -2129,9 +2008,6 @@ inline CMatrix4 &CMatrix4::setTextureRotationCenter(f32 rotateRad) M[8] = (T)(0.5f * (s - c) + 0.5f); M[9] = (T)(-0.5f * (s + c) + 0.5f); -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = definitelyIdentityMatrix && (rotateRad == 0.0f); -#endif return *this; } @@ -2141,9 +2017,6 @@ inline CMatrix4 &CMatrix4::setTextureTranslate(f32 x, f32 y) M[8] = (T)x; M[9] = (T)y; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = definitelyIdentityMatrix && (x == 0.0f) && (y == 0.0f); -#endif return *this; } @@ -2159,10 +2032,6 @@ inline CMatrix4 &CMatrix4::setTextureTranslateTransposed(f32 x, f32 y) { M[2] = (T)x; M[6] = (T)y; - -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = definitelyIdentityMatrix && (x == 0.0f) && (y == 0.0f); -#endif return *this; } @@ -2171,9 +2040,6 @@ inline CMatrix4 &CMatrix4::setTextureScale(f32 sx, f32 sy) { M[0] = (T)sx; M[5] = (T)sy; -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = definitelyIdentityMatrix && (sx == 1.0f) && (sy == 1.0f); -#endif return *this; } @@ -2191,10 +2057,6 @@ inline CMatrix4 &CMatrix4::setTextureScaleCenter(f32 sx, f32 sy) M[5] = (T)sy; M[8] = (T)(0.5f - 0.5f * sx); M[9] = (T)(0.5f - 0.5f * sy); - -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = definitelyIdentityMatrix && (sx == 1.0f) && (sy == 1.0f); -#endif return *this; } @@ -2203,43 +2065,13 @@ template inline CMatrix4 &CMatrix4::setM(const T *data) { memcpy(M, data, 16 * sizeof(T)); - -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = false; -#endif return *this; } -// sets if the matrix is definitely identity matrix -template -inline void CMatrix4::setDefinitelyIdentityMatrix(bool isDefinitelyIdentityMatrix) -{ -#if defined(USE_MATRIX_TEST) - definitelyIdentityMatrix = isDefinitelyIdentityMatrix; -#else - (void)isDefinitelyIdentityMatrix; // prevent compiler warning -#endif -} - -// gets if the matrix is definitely identity matrix -template -inline bool CMatrix4::getDefinitelyIdentityMatrix() const -{ -#if defined(USE_MATRIX_TEST) - return definitelyIdentityMatrix; -#else - return false; -#endif -} - //! Compare two matrices using the equal method template inline bool CMatrix4::equals(const core::CMatrix4 &other, const T tolerance) const { -#if defined(USE_MATRIX_TEST) - if (definitelyIdentityMatrix && other.definitelyIdentityMatrix) - return true; -#endif for (s32 i = 0; i < 16; ++i) if (!core::equals(M[i], other.M[i], tolerance)) return false; diff --git a/irr/include/mt_opengl.h b/irr/include/mt_opengl.h index f69ba8c69..1a92cde56 100755 --- a/irr/include/mt_opengl.h +++ b/irr/include/mt_opengl.h @@ -15,11 +15,18 @@ #ifndef APIENTRYP #define APIENTRYP APIENTRY * #endif -#ifndef GLAPI - #define GLAPI extern +// undefine a few names that can easily clash with system headers +#ifdef NO_ERROR +#undef NO_ERROR +#endif +#ifdef ZERO +#undef ZERO +#endif +#ifdef ONE +#undef ONE #endif -class OpenGLProcedures { +class OpenGLProcedures final { private: // ./glcorearb.h typedef void GLvoid; @@ -49,8 +56,6 @@ private: typedef khronos_int64_t GLint64EXT; typedef void *GLeglClientBufferEXT; - // The script will miss this particular typedef thinking it's a PFN, - // so we have to paste it in manually. It's the only such type in OpenGL. typedef void (APIENTRY *GLDEBUGPROC) (GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); @@ -775,10 +780,12 @@ private: typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTPROC_MT) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); std::unordered_set extensions; + public: // Call this once after creating the context. void LoadAllProcedures(irr::video::IContextManager *cmgr); - // Check if an extension is supported. + /// Check if an extension is supported. + /// @param ext full extension name e.g. "GL_KHR_no_error" inline bool IsExtensionPresent(const std::string &ext) const { return extensions.count(ext) > 0; @@ -3185,6 +3192,7 @@ public: static constexpr const GLenum STATE_RESTORE = 0x8BDC; static constexpr const GLenum SHADER_BINARY_VIV = 0x8FC4; + static constexpr const GLenum NO_ERROR = 0; static constexpr const GLenum ZERO = 0; static constexpr const GLenum ONE = 1; static constexpr const GLenum NONE = 0; diff --git a/irr/include/quaternion.h b/irr/include/quaternion.h index 4e90fa067..e23b1317d 100644 --- a/irr/include/quaternion.h +++ b/irr/include/quaternion.h @@ -358,8 +358,6 @@ inline void quaternion::getMatrixFast(matrix4 &dest) const dest[13] = 0.f; dest[14] = 0.f; dest[15] = 1.f; - - dest.setDefinitelyIdentityMatrix(false); } /*! @@ -397,8 +395,6 @@ inline void quaternion::getMatrix(matrix4 &dest, dest[13] = center.Y; dest[14] = center.Z; dest[15] = 1.f; - - dest.setDefinitelyIdentityMatrix(false); } /*! @@ -471,8 +467,6 @@ inline void quaternion::getMatrix_transposed(matrix4 &dest) const dest[7] = 0.f; dest[11] = 0.f; dest[15] = 1.f; - - dest.setDefinitelyIdentityMatrix(false); } // Inverts this quaternion diff --git a/irr/scripts/BindingGenerator.lua b/irr/scripts/BindingGenerator.lua index d0be04293..1ca5b41d9 100755 --- a/irr/scripts/BindingGenerator.lua +++ b/irr/scripts/BindingGenerator.lua @@ -219,11 +219,11 @@ local function ParseHeader( path, into, apiRegex, defs, consts, nameSet, noNewNa }; end elseif ( line:find( "#" ) and not line:find( "#include" ) ) then - local rawName, value = line:match( "#define%s+GL_([_%w]+)%s+0x(%w+)" ); + local rawName, value = line:match( "#define%s+GL_([_%w]+)%s+(0x%w+)" ); if rawName and value then local name, vendor = StripVendorSuffix( rawName, true ); if not constBanned[vendor] then - consts:Add{ name = name, vendor = vendor, value = "0x"..value }; + consts:Add{ name = name, vendor = vendor, value = value }; end end ::skip:: @@ -359,21 +359,28 @@ f:write[[ #ifndef APIENTRYP #define APIENTRYP APIENTRY * #endif -#ifndef GLAPI - #define GLAPI extern +// undefine a few names that can easily clash with system headers +#ifdef NO_ERROR +#undef NO_ERROR +#endif +#ifdef ZERO +#undef ZERO +#endif +#ifdef ONE +#undef ONE #endif ]]; f:write[[ -class OpenGLProcedures { +class OpenGLProcedures final { private: ]]; f:write( definitions:Concat( "\n" ) ); f:write( "\n" ); +-- The script will miss this particular typedef thinking it's a PFN, +-- so we have to paste it in manually. It's the only such type in OpenGL. f:write[[ - // The script will miss this particular typedef thinking it's a PFN, - // so we have to paste it in manually. It's the only such type in OpenGL. typedef void (APIENTRY *GLDEBUGPROC) (GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); @@ -382,10 +389,12 @@ f:write( typedefs:Concat( "\n" ) ); f:write( "\n\n" ); f:write [[ std::unordered_set extensions; + public: // Call this once after creating the context. void LoadAllProcedures(irr::video::IContextManager *cmgr); - // Check if an extension is supported. + /// Check if an extension is supported. + /// @param ext full extension name e.g. "GL_KHR_no_error" inline bool IsExtensionPresent(const std::string &ext) const { return extensions.count(ext) > 0; @@ -396,7 +405,10 @@ f:write( pointers:Concat( "\n" ) ); f:write( "\n\n" ); f:write( cppConsts:Concat( "\n" ) ); f:write( "\n\n" ); +-- We filter constants not in hex format to avoid the VERSION_X_X and extension +-- defines, but that means we miss these. f:write[[ + static constexpr const GLenum NO_ERROR = 0; static constexpr const GLenum ZERO = 0; static constexpr const GLenum ONE = 1; static constexpr const GLenum NONE = 0; @@ -416,7 +428,7 @@ f:write[[ #include #include -OpenGLProcedures GL = OpenGLProcedures(); +OpenGLProcedures GL; void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr) { @@ -425,9 +437,11 @@ void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr) f:write( loader:Concat() ); f:write[[ - // OpenGL 3 way to enumerate extensions + /* OpenGL 3 & ES 3 way to enumerate extensions */ GLint ext_count = 0; GetIntegerv(NUM_EXTENSIONS, &ext_count); + // clear error which is raised if unsupported + while (GetError() != GL.NO_ERROR) {} extensions.reserve(ext_count); for (GLint k = 0; k < ext_count; k++) { auto tmp = GetStringi(EXTENSIONS, k); @@ -437,16 +451,15 @@ f:write[[ if (!extensions.empty()) return; - // OpenGL 2 / ES 2 way to enumerate extensions + /* OpenGL 2 / ES 2 way to enumerate extensions */ auto ext_str = GetString(EXTENSIONS); if (!ext_str) return; // get the extension string, chop it up - std::stringstream ext_ss((char*)ext_str); + std::istringstream ext_ss((char*)ext_str); std::string tmp; while (std::getline(ext_ss, tmp, ' ')) extensions.emplace(tmp); - } ]]; f:close(); diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index ba8bc3b78..3871d52a3 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -7,7 +7,7 @@ #include "ISceneManager.h" #include "S3DVertex.h" #include "os.h" -#include "CSkinnedMesh.h" +#include "SkinnedMesh.h" #include "IDummyTransformationSceneNode.h" #include "IBoneSceneNode.h" #include "IMaterialRenderer.h" @@ -38,10 +38,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh, Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false), LoopCallBack(0), PassCount(0) { -#ifdef _DEBUG - setDebugName("CAnimatedMeshSceneNode"); -#endif - setMesh(mesh); } @@ -165,12 +161,12 @@ IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame() // 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. - CSkinnedMesh *skinnedMesh = static_cast(Mesh); + SkinnedMesh *skinnedMesh = static_cast(Mesh); if (JointMode == EJUOR_CONTROL) // write to mesh skinnedMesh->transferJointsToMesh(JointChildSceneNodes); else - skinnedMesh->animateMesh(getFrameNr(), 1.0f); + skinnedMesh->animateMesh(getFrameNr()); // Update the skinned mesh for the current joint transforms. skinnedMesh->skinMesh(); @@ -227,7 +223,7 @@ void CAnimatedMeshSceneNode::render() Box = m->getBoundingBox(); } else { #ifdef _DEBUG - os::Printer::log("Animated Mesh returned no mesh to render.", Mesh->getDebugName(), ELL_WARNING); + os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING); #endif return; } @@ -299,12 +295,10 @@ void CAnimatedMeshSceneNode::render() if (Mesh->getMeshType() == EAMT_SKINNED) { // draw skeleton - for (u32 g = 0; g < ((ISkinnedMesh *)Mesh)->getAllJoints().size(); ++g) { - ISkinnedMesh::SJoint *joint = ((ISkinnedMesh *)Mesh)->getAllJoints()[g]; - - for (u32 n = 0; n < joint->Children.size(); ++n) { + for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) { + for (const auto *childJoint : joint->Children) { driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(), - joint->Children[n]->GlobalAnimatedMatrix.getTranslation(), + childJoint->GlobalAnimatedMatrix.getTranslation(), video::SColor(255, 51, 66, 255)); } } @@ -404,7 +398,7 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName) checkJoints(); - ISkinnedMesh *skinnedMesh = (ISkinnedMesh *)Mesh; + auto *skinnedMesh = (SkinnedMesh *)Mesh; const std::optional number = skinnedMesh->getJointNumber(jointName); @@ -446,7 +440,7 @@ u32 CAnimatedMeshSceneNode::getJointCount() const if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED) return 0; - ISkinnedMesh *skinnedMesh = (ISkinnedMesh *)Mesh; + auto *skinnedMesh = (SkinnedMesh *)Mesh; return skinnedMesh->getJointCount(); } @@ -596,10 +590,9 @@ void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions) checkJoints(); const f32 frame = getFrameNr(); // old? - CSkinnedMesh *skinnedMesh = static_cast(Mesh); + SkinnedMesh *skinnedMesh = static_cast(Mesh); - skinnedMesh->transferOnlyJointsHintsToMesh(JointChildSceneNodes); - skinnedMesh->animateMesh(frame, 1.0f); + skinnedMesh->animateMesh(frame); skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes); //----------------------------------------- @@ -671,8 +664,8 @@ void CAnimatedMeshSceneNode::checkJoints() JointChildSceneNodes.clear(); // Create joints for SkinnedMesh - ((CSkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager); - ((CSkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes); + ((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager); + ((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes); JointsUsed = true; JointMode = EJUOR_READ; diff --git a/irr/src/CAnimatedMeshSceneNode.h b/irr/src/CAnimatedMeshSceneNode.h index e45edca86..047a4030f 100644 --- a/irr/src/CAnimatedMeshSceneNode.h +++ b/irr/src/CAnimatedMeshSceneNode.h @@ -169,7 +169,7 @@ private: IAnimationEndCallBack *LoopCallBack; s32 PassCount; - core::array JointChildSceneNodes; + std::vector JointChildSceneNodes; core::array PretransitingSave; }; diff --git a/irr/src/CAttributes.cpp b/irr/src/CAttributes.cpp index b1509e455..924bd1a45 100644 --- a/irr/src/CAttributes.cpp +++ b/irr/src/CAttributes.cpp @@ -12,12 +12,7 @@ namespace irr namespace io { -CAttributes::CAttributes() -{ -#ifdef _DEBUG - setDebugName("CAttributes"); -#endif -} +CAttributes::CAttributes() {} CAttributes::~CAttributes() { diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 6cb40cb95..e99bd2eed 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -10,6 +10,7 @@ #include "IVideoDriver.h" #include "IFileSystem.h" +#include "SkinnedMesh.h" #include "coreutil.h" #include "os.h" @@ -28,11 +29,7 @@ namespace scene CB3DMeshFileLoader::CB3DMeshFileLoader(scene::ISceneManager *smgr) : AnimatedMesh(0), B3DFile(0), VerticesStart(0), NormalsInFile(false), HasVertexColors(false), ShowWarning(true) -{ -#ifdef _DEBUG - setDebugName("CB3DMeshFileLoader"); -#endif -} +{} //! returns true if the file maybe is able to be loaded by this class //! based on the file extension (e.g. ".bsp") @@ -51,12 +48,12 @@ IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file) return 0; B3DFile = file; - AnimatedMesh = new scene::CSkinnedMesh(); + AnimatedMesh = new scene::SkinnedMeshBuilder(); ShowWarning = true; // If true a warning is issued if too many textures are used VerticesStart = 0; if (load()) { - AnimatedMesh->finalize(); + return AnimatedMesh->finalize(); } else { AnimatedMesh->drop(); AnimatedMesh = 0; @@ -111,7 +108,7 @@ bool CB3DMeshFileLoader::load() if (!readChunkBRUS()) return false; } else if (strncmp(B3dStack.getLast().name, "NODE", 4) == 0) { - if (!readChunkNODE((CSkinnedMesh::SJoint *)0)) + if (!readChunkNODE((SkinnedMesh::SJoint *)0)) return false; } else { os::Printer::log("Unknown chunk found in mesh base - skipping"); @@ -133,9 +130,9 @@ bool CB3DMeshFileLoader::load() return true; } -bool CB3DMeshFileLoader::readChunkNODE(CSkinnedMesh::SJoint *inJoint) +bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint) { - CSkinnedMesh::SJoint *joint = AnimatedMesh->addJoint(inJoint); + SkinnedMesh::SJoint *joint = AnimatedMesh->addJoint(inJoint); joint->Name = readString(); #ifdef _B3D_READER_DEBUG @@ -211,7 +208,7 @@ bool CB3DMeshFileLoader::readChunkNODE(CSkinnedMesh::SJoint *inJoint) return true; } -bool CB3DMeshFileLoader::readChunkMESH(CSkinnedMesh::SJoint *inJoint) +bool CB3DMeshFileLoader::readChunkMESH(SkinnedMesh::SJoint *inJoint) { #ifdef _B3D_READER_DEBUG core::stringc logStr; @@ -254,7 +251,7 @@ bool CB3DMeshFileLoader::readChunkMESH(CSkinnedMesh::SJoint *inJoint) meshBuffer->Material = Materials[brushID].Material; } - if (readChunkTRIS(meshBuffer, AnimatedMesh->getMeshBuffers().size() - 1, VerticesStart) == false) + if (readChunkTRIS(meshBuffer, AnimatedMesh->getMeshBufferCount() - 1, VerticesStart) == false) return false; if (!NormalsInFile) { @@ -302,7 +299,7 @@ VRTS: float tex_coords[tex_coord_sets][tex_coord_set_size] ;tex coords } */ -bool CB3DMeshFileLoader::readChunkVRTS(CSkinnedMesh::SJoint *inJoint) +bool CB3DMeshFileLoader::readChunkVRTS(SkinnedMesh::SJoint *inJoint) { #ifdef _B3D_READER_DEBUG core::stringc logStr; @@ -521,7 +518,7 @@ bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 m return true; } -bool CB3DMeshFileLoader::readChunkBONE(CSkinnedMesh::SJoint *inJoint) +bool CB3DMeshFileLoader::readChunkBONE(SkinnedMesh::SJoint *inJoint) { #ifdef _B3D_READER_DEBUG core::stringc logStr; @@ -552,7 +549,7 @@ bool CB3DMeshFileLoader::readChunkBONE(CSkinnedMesh::SJoint *inJoint) if (AnimatedVertices_VertexID[globalVertexID] == -1) { os::Printer::log("B3dMeshLoader: Weight has bad vertex id (no link to meshbuffer index found)"); } else if (strength > 0) { - CSkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(inJoint); + SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(inJoint); weight->strength = strength; // Find the meshbuffer and Vertex index from the Global Vertex ID: weight->vertex_id = AnimatedVertices_VertexID[globalVertexID]; @@ -565,11 +562,11 @@ bool CB3DMeshFileLoader::readChunkBONE(CSkinnedMesh::SJoint *inJoint) return true; } -bool CB3DMeshFileLoader::readChunkKEYS(CSkinnedMesh::SJoint *inJoint) +bool CB3DMeshFileLoader::readChunkKEYS(SkinnedMesh::SJoint *inJoint) { #ifdef _B3D_READER_DEBUG // Only print first, that's just too much output otherwise - if (!inJoint || (inJoint->PositionKeys.empty() && inJoint->ScaleKeys.empty() && inJoint->RotationKeys.empty())) { + if (!inJoint || inJoint->keys.empty()) { core::stringc logStr; for (u32 i = 1; i < B3dStack.size(); ++i) logStr += "-"; @@ -584,13 +581,6 @@ bool CB3DMeshFileLoader::readChunkKEYS(CSkinnedMesh::SJoint *inJoint) flags = os::Byteswap::byteswap(flags); #endif - CSkinnedMesh::SPositionKey *oldPosKey = 0; - core::vector3df oldPos[2]; - CSkinnedMesh::SScaleKey *oldScaleKey = 0; - core::vector3df oldScale[2]; - CSkinnedMesh::SRotationKey *oldRotKey = 0; - core::quaternion oldRot[2]; - bool isFirst[3] = {true, true, true}; while ((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) // this chunk repeats { s32 frame; @@ -600,91 +590,24 @@ bool CB3DMeshFileLoader::readChunkKEYS(CSkinnedMesh::SJoint *inJoint) frame = os::Byteswap::byteswap(frame); #endif + if (frame < 1) { + os::Printer::log("Illegal frame number found", B3DFile->getFileName(), ELL_ERROR); + frame = 1; + } + // Add key frames, frames in Irrlicht are zero-based f32 data[4]; if (flags & 1) { readFloats(data, 3); - if ((oldPosKey != 0) && (oldPos[0] == oldPos[1])) { - const core::vector3df pos(data[0], data[1], data[2]); - if (oldPos[1] == pos) - oldPosKey->frame = (f32)frame - 1; - else { - oldPos[0] = oldPos[1]; - oldPosKey = AnimatedMesh->addPositionKey(inJoint); - oldPosKey->frame = (f32)frame - 1; - oldPos[1].set(oldPosKey->position.set(pos)); - } - } else if (oldPosKey == 0 && isFirst[0]) { - oldPosKey = AnimatedMesh->addPositionKey(inJoint); - oldPosKey->frame = (f32)frame - 1; - oldPos[0].set(oldPosKey->position.set(data[0], data[1], data[2])); - oldPosKey = 0; - isFirst[0] = false; - } else { - if (oldPosKey != 0) - oldPos[0] = oldPos[1]; - oldPosKey = AnimatedMesh->addPositionKey(inJoint); - oldPosKey->frame = (f32)frame - 1; - oldPos[1].set(oldPosKey->position.set(data[0], data[1], data[2])); - } + AnimatedMesh->addPositionKey(inJoint, frame - 1, {data[0], data[1], data[2]}); } if (flags & 2) { readFloats(data, 3); - if ((oldScaleKey != 0) && (oldScale[0] == oldScale[1])) { - const core::vector3df scale(data[0], data[1], data[2]); - if (oldScale[1] == scale) - oldScaleKey->frame = (f32)frame - 1; - else { - oldScale[0] = oldScale[1]; - oldScaleKey = AnimatedMesh->addScaleKey(inJoint); - oldScaleKey->frame = (f32)frame - 1; - oldScale[1].set(oldScaleKey->scale.set(scale)); - } - } else if (oldScaleKey == 0 && isFirst[1]) { - oldScaleKey = AnimatedMesh->addScaleKey(inJoint); - oldScaleKey->frame = (f32)frame - 1; - oldScale[0].set(oldScaleKey->scale.set(data[0], data[1], data[2])); - oldScaleKey = 0; - isFirst[1] = false; - } else { - if (oldScaleKey != 0) - oldScale[0] = oldScale[1]; - oldScaleKey = AnimatedMesh->addScaleKey(inJoint); - oldScaleKey->frame = (f32)frame - 1; - oldScale[1].set(oldScaleKey->scale.set(data[0], data[1], data[2])); - } + AnimatedMesh->addScaleKey(inJoint, frame - 1, {data[0], data[1], data[2]}); } if (flags & 4) { readFloats(data, 4); - if ((oldRotKey != 0) && (oldRot[0] == oldRot[1])) { - // meant to be in this order since b3d stores W first - const core::quaternion rot(data[1], data[2], data[3], data[0]); - if (oldRot[1] == rot) - oldRotKey->frame = (f32)frame - 1; - else { - oldRot[0] = oldRot[1]; - oldRotKey = AnimatedMesh->addRotationKey(inJoint); - oldRotKey->frame = (f32)frame - 1; - oldRot[1].set(oldRotKey->rotation.set(data[1], data[2], data[3], data[0])); - oldRot[1].normalize(); - } - } else if (oldRotKey == 0 && isFirst[2]) { - oldRotKey = AnimatedMesh->addRotationKey(inJoint); - oldRotKey->frame = (f32)frame - 1; - // meant to be in this order since b3d stores W first - oldRot[0].set(oldRotKey->rotation.set(data[1], data[2], data[3], data[0])); - oldRot[0].normalize(); - oldRotKey = 0; - isFirst[2] = false; - } else { - if (oldRotKey != 0) - oldRot[0] = oldRot[1]; - oldRotKey = AnimatedMesh->addRotationKey(inJoint); - oldRotKey->frame = (f32)frame - 1; - // meant to be in this order since b3d stores W first - oldRot[1].set(oldRotKey->rotation.set(data[1], data[2], data[3], data[0])); - oldRot[1].normalize(); - } + AnimatedMesh->addRotationKey(inJoint, frame - 1, core::quaternion(data[1], data[2], data[3], data[0])); } } diff --git a/irr/src/CB3DMeshFileLoader.h b/irr/src/CB3DMeshFileLoader.h index 808b68170..55978d743 100644 --- a/irr/src/CB3DMeshFileLoader.h +++ b/irr/src/CB3DMeshFileLoader.h @@ -10,7 +10,7 @@ #include "IMeshLoader.h" #include "ISceneManager.h" -#include "CSkinnedMesh.h" +#include "SkinnedMesh.h" #include "SB3DStructs.h" #include "IReadFile.h" @@ -39,12 +39,12 @@ public: private: bool load(); - bool readChunkNODE(CSkinnedMesh::SJoint *InJoint); - bool readChunkMESH(CSkinnedMesh::SJoint *InJoint); - bool readChunkVRTS(CSkinnedMesh::SJoint *InJoint); + bool readChunkNODE(SkinnedMesh::SJoint *InJoint); + bool readChunkMESH(SkinnedMesh::SJoint *InJoint); + bool readChunkVRTS(SkinnedMesh::SJoint *InJoint); bool readChunkTRIS(scene::SSkinMeshBuffer *MeshBuffer, u32 MeshBufferID, s32 Vertices_Start); - bool readChunkBONE(CSkinnedMesh::SJoint *InJoint); - bool readChunkKEYS(CSkinnedMesh::SJoint *InJoint); + bool readChunkBONE(SkinnedMesh::SJoint *InJoint); + bool readChunkKEYS(SkinnedMesh::SJoint *InJoint); bool readChunkANIM(); bool readChunkTEXS(); bool readChunkBRUS(); @@ -63,7 +63,7 @@ private: core::array BaseVertices; - CSkinnedMesh *AnimatedMesh; + SkinnedMeshBuilder *AnimatedMesh; io::IReadFile *B3DFile; // B3Ds have Vertex ID's local within the mesh I don't want this diff --git a/irr/src/CBillboardSceneNode.cpp b/irr/src/CBillboardSceneNode.cpp index 1c6d88dae..6769e8f04 100644 --- a/irr/src/CBillboardSceneNode.cpp +++ b/irr/src/CBillboardSceneNode.cpp @@ -20,10 +20,6 @@ CBillboardSceneNode::CBillboardSceneNode(ISceneNode *parent, ISceneManager *mgr, IBillboardSceneNode(parent, mgr, id, position), Buffer(new SMeshBuffer()) { -#ifdef _DEBUG - setDebugName("CBillboardSceneNode"); -#endif - setSize(size); auto &Vertices = Buffer->Vertices->Data; diff --git a/irr/src/CBoneSceneNode.cpp b/irr/src/CBoneSceneNode.cpp index b9eb8892e..7aa637094 100644 --- a/irr/src/CBoneSceneNode.cpp +++ b/irr/src/CBoneSceneNode.cpp @@ -18,9 +18,6 @@ CBoneSceneNode::CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id, BoneIndex(boneIndex), AnimationMode(EBAM_AUTOMATIC), SkinningSpace(EBSS_LOCAL) { -#ifdef _DEBUG - setDebugName("CBoneSceneNode"); -#endif setName(boneName); } diff --git a/irr/src/CCameraSceneNode.cpp b/irr/src/CCameraSceneNode.cpp index f8899e5ff..e1c3c96f6 100644 --- a/irr/src/CCameraSceneNode.cpp +++ b/irr/src/CCameraSceneNode.cpp @@ -20,10 +20,6 @@ CCameraSceneNode::CCameraSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 i Target(lookat), UpVector(0.0f, 1.0f, 0.0f), ZNear(1.0f), ZFar(3000.0f), InputReceiverEnabled(true), TargetAndRotationAreBound(false) { -#ifdef _DEBUG - setDebugName("CCameraSceneNode"); -#endif - // set default projection Fovy = core::PI / 2.5f; // Field of view, in radians. Aspect = 4.0f / 3.0f; // Aspect ratio. diff --git a/irr/src/CColorConverter.cpp b/irr/src/CColorConverter.cpp index 7749ececf..d1bd302c7 100644 --- a/irr/src/CColorConverter.cpp +++ b/irr/src/CColorConverter.cpp @@ -7,6 +7,15 @@ #include "os.h" #include "irrString.h" +// Warning: The naming of Irrlicht color formats +// is not consistent regarding actual component order in memory. +// E.g. in CImage, ECF_R8G8B8 is handled per-byte and stored as [R][G][B] in memory +// while ECF_A8R8G8B8 is handled as an u32 0xAARRGGBB so [B][G][R][A] (little endian) in memory. +// The conversions suffer from the same inconsistencies, e.g. +// convert_R8G8B8toA8R8G8B8 converts [R][G][B] into 0xFFRRGGBB = [B][G][R][FF] (little endian); +// convert_A1R5G5B5toR8G8B8 converts 0bARRRRRGGGGGBBBBB into [R][G][B]. +// This also means many conversions may be broken on big endian. + namespace irr { namespace video @@ -393,19 +402,6 @@ void CColorConverter::convert_R8G8B8toA1R5G5B5(const void *sP, s32 sN, void *dP) } } -void CColorConverter::convert_B8G8R8toA8R8G8B8(const void *sP, s32 sN, void *dP) -{ - u8 *sB = (u8 *)sP; - u32 *dB = (u32 *)dP; - - for (s32 x = 0; x < sN; ++x) { - *dB = 0xff000000 | (sB[2] << 16) | (sB[1] << 8) | sB[0]; - - sB += 3; - ++dB; - } -} - void CColorConverter::convert_A8R8G8B8toR8G8B8A8(const void *sP, s32 sN, void *dP) { const u32 *sB = (const u32 *)sP; @@ -428,22 +424,6 @@ void CColorConverter::convert_A8R8G8B8toA8B8G8R8(const void *sP, s32 sN, void *d } } -void CColorConverter::convert_B8G8R8A8toA8R8G8B8(const void *sP, s32 sN, void *dP) -{ - u8 *sB = (u8 *)sP; - u8 *dB = (u8 *)dP; - - for (s32 x = 0; x < sN; ++x) { - dB[0] = sB[3]; - dB[1] = sB[2]; - dB[2] = sB[1]; - dB[3] = sB[0]; - - sB += 4; - dB += 4; - } -} - void CColorConverter::convert_R8G8B8toB8G8R8(const void *sP, s32 sN, void *dP) { u8 *sB = (u8 *)sP; diff --git a/irr/src/CColorConverter.h b/irr/src/CColorConverter.h index af7b13726..8db7af097 100644 --- a/irr/src/CColorConverter.h +++ b/irr/src/CColorConverter.h @@ -66,8 +66,6 @@ public: static void convert_R8G8B8toA1R5G5B5(const void *sP, s32 sN, void *dP); static void convert_R8G8B8toB8G8R8(const void *sP, s32 sN, void *dP); static void convert_R8G8B8toR5G6B5(const void *sP, s32 sN, void *dP); - static void convert_B8G8R8toA8R8G8B8(const void *sP, s32 sN, void *dP); - static void convert_B8G8R8A8toA8R8G8B8(const void *sP, s32 sN, void *dP); static void convert_A8R8G8B8toR8G8B8A8(const void *sP, s32 sN, void *dP); static void convert_A8R8G8B8toA8B8G8R8(const void *sP, s32 sN, void *dP); diff --git a/irr/src/CDummyTransformationSceneNode.cpp b/irr/src/CDummyTransformationSceneNode.cpp index 73953dd38..e354038df 100644 --- a/irr/src/CDummyTransformationSceneNode.cpp +++ b/irr/src/CDummyTransformationSceneNode.cpp @@ -15,10 +15,6 @@ CDummyTransformationSceneNode::CDummyTransformationSceneNode( ISceneNode *parent, ISceneManager *mgr, s32 id) : IDummyTransformationSceneNode(parent, mgr, id) { -#ifdef _DEBUG - setDebugName("CDummyTransformationSceneNode"); -#endif - setAutomaticCulling(scene::EAC_OFF); } diff --git a/irr/src/CEGLManager.cpp b/irr/src/CEGLManager.cpp index 6c39c5c74..b70a0c091 100644 --- a/irr/src/CEGLManager.cpp +++ b/irr/src/CEGLManager.cpp @@ -18,11 +18,7 @@ namespace video CEGLManager::CEGLManager() : IContextManager(), EglWindow(0), EglDisplay(EGL_NO_DISPLAY), EglSurface(EGL_NO_SURFACE), EglContext(EGL_NO_CONTEXT), EglConfig(0), MajorVersion(0), MinorVersion(0) -{ -#ifdef _DEBUG - setDebugName("CEGLManager"); -#endif -} +{} CEGLManager::~CEGLManager() { diff --git a/irr/src/CEmptySceneNode.cpp b/irr/src/CEmptySceneNode.cpp index 2b965a198..70a2ca21d 100644 --- a/irr/src/CEmptySceneNode.cpp +++ b/irr/src/CEmptySceneNode.cpp @@ -14,10 +14,6 @@ namespace scene CEmptySceneNode::CEmptySceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id) : ISceneNode(parent, mgr, id) { -#ifdef _DEBUG - setDebugName("CEmptySceneNode"); -#endif - setAutomaticCulling(scene::EAC_OFF); } diff --git a/irr/src/CFileList.cpp b/irr/src/CFileList.cpp index cd6c85df4..9294f3f46 100644 --- a/irr/src/CFileList.cpp +++ b/irr/src/CFileList.cpp @@ -18,10 +18,6 @@ static const io::path emptyFileListEntry; CFileList::CFileList(const io::path &path, bool ignoreCase, bool ignorePaths) : IgnorePaths(ignorePaths), IgnoreCase(ignoreCase), Path(path) { -#ifdef _DEBUG - setDebugName("CFileList"); -#endif - Path.replace('\\', '/'); } diff --git a/irr/src/CFileSystem.cpp b/irr/src/CFileSystem.cpp index 4b938c4c5..d8f04eb1d 100644 --- a/irr/src/CFileSystem.cpp +++ b/irr/src/CFileSystem.cpp @@ -43,10 +43,6 @@ namespace io //! constructor CFileSystem::CFileSystem() { -#ifdef _DEBUG - setDebugName("CFileSystem"); -#endif - setFileListSystem(FILESYSTEM_NATIVE); //! reset current working directory getWorkingDirectory(); diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index fe67228a7..79c68355b 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -5,7 +5,7 @@ #include "SMaterialLayer.h" #include "coreutil.h" -#include "CSkinnedMesh.h" +#include "SkinnedMesh.h" #include "IAnimatedMesh.h" #include "IReadFile.h" #include "irrTypes.h" @@ -305,7 +305,7 @@ SelfType::createNormalizedValuesAccessor( } } -template +template std::array SelfType::getNormalizedValues( const NormalizedValuesAccessor &accessor, const std::size_t i) @@ -313,17 +313,19 @@ std::array SelfType::getNormalizedValues( std::array values; if (std::holds_alternative>>(accessor)) { const auto u8s = std::get>>(accessor).get(i); - for (u8 i = 0; i < N; ++i) - values[i] = static_cast(u8s[i]) / std::numeric_limits::max(); + for (std::size_t j = 0; j < N; ++j) + values[j] = static_cast(u8s[j]) / std::numeric_limits::max(); } else if (std::holds_alternative>>(accessor)) { const auto u16s = std::get>>(accessor).get(i); - for (u8 i = 0; i < N; ++i) - values[i] = static_cast(u16s[i]) / std::numeric_limits::max(); + for (std::size_t j = 0; j < N; ++j) + values[j] = static_cast(u16s[j]) / std::numeric_limits::max(); } else { values = std::get>>(accessor).get(i); - for (u8 i = 0; i < N; ++i) { - if (values[i] < 0 || values[i] > 1) - throw std::runtime_error("invalid normalized value"); + if constexpr (validate) { + for (std::size_t j = 0; j < N; ++j) { + if (values[j] < 0 || values[j] > 1) + throw std::runtime_error("invalid normalized value"); + } } } return values; @@ -344,14 +346,14 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file) const char *filename = file->getFileName().c_str(); try { tiniergltf::GlTF model = parseGLTF(file); - irr_ptr mesh(new CSkinnedMesh()); + irr_ptr mesh(new SkinnedMeshBuilder()); MeshExtractor extractor(std::move(model), mesh.get()); try { extractor.load(); for (const auto &warning : extractor.getWarnings()) { os::Printer::log(filename, warning.c_str(), ELL_WARNING); } - return mesh.release(); + return mesh.release()->finalize(); } catch (const std::runtime_error &e) { os::Printer::log("error converting gltf to irrlicht mesh", e.what(), ELL_ERROR); } @@ -410,7 +412,7 @@ static video::E_TEXTURE_CLAMP convertTextureWrap(const Wrap wrap) { void SelfType::MeshExtractor::addPrimitive( const tiniergltf::MeshPrimitive &primitive, const std::optional skinIdx, - CSkinnedMesh::SJoint *parent) + SkinnedMesh::SJoint *parent) { auto vertices = getVertices(primitive); if (!vertices.has_value()) @@ -493,6 +495,7 @@ void SelfType::MeshExtractor::addPrimitive( const auto weightAccessor = createNormalizedValuesAccessor<4>(m_gltf_model, weights->at(set)); + bool negative_weights = false; for (std::size_t v = 0; v < n_vertices; ++v) { std::array jointIdxs; if (std::holds_alternative>>(jointAccessor)) { @@ -501,21 +504,27 @@ void SelfType::MeshExtractor::addPrimitive( } else if (std::holds_alternative>>(jointAccessor)) { jointIdxs = std::get>>(jointAccessor).get(v); } - std::array strengths = getNormalizedValues(weightAccessor, v); + + // Be lax: We can allow weights that aren't normalized. Irrlicht already normalizes them. + // The glTF spec only requires that these be "as close to 1 as reasonably possible". + auto strengths = getNormalizedValues<4, false>(weightAccessor, v); // 4 joints per set for (std::size_t in_set = 0; in_set < 4; ++in_set) { u16 jointIdx = jointIdxs[in_set]; f32 strength = strengths[in_set]; - if (strength == 0) - continue; + negative_weights = negative_weights || (strength < 0); + if (strength <= 0) + continue; // note: also ignores negative weights - CSkinnedMesh::SWeight *weight = m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx))); + SkinnedMesh::SWeight *weight = m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx))); weight->buffer_id = meshbufNr; weight->vertex_id = v; weight->strength = strength; } } + if (negative_weights) + warn("negative weights"); } } @@ -527,7 +536,7 @@ void SelfType::MeshExtractor::addPrimitive( void SelfType::MeshExtractor::deferAddMesh( const std::size_t meshIdx, const std::optional skinIdx, - CSkinnedMesh::SJoint *parent) + SkinnedMesh::SJoint *parent) { m_mesh_loaders.emplace_back([=] { for (std::size_t pi = 0; pi < getPrimitiveCount(meshIdx); ++pi) { @@ -547,7 +556,7 @@ static const core::matrix4 leftToRight = core::matrix4( ); static const core::matrix4 rightToLeft = leftToRight; -static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, CSkinnedMesh::SJoint *joint) +static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMesh::SJoint *joint) { // Note: Under the hood, this casts these doubles to floats. core::matrix4 mat = convertHandedness(core::matrix4( @@ -576,7 +585,7 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, CSkinnedMe return mat; } -static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, CSkinnedMesh::SJoint *joint) +static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint) { const auto &trans = trs.translation; const auto &rot = trs.rotation; @@ -594,7 +603,7 @@ static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, CSkinnedMes } static core::matrix4 loadTransform(std::optional> transform, - CSkinnedMesh::SJoint *joint) { + SkinnedMesh::SJoint *joint) { if (!transform.has_value()) { return core::matrix4(); } @@ -603,7 +612,7 @@ static core::matrix4 loadTransform(std::optionalat(nodeIdx); auto *joint = m_irr_model->addJoint(parent); @@ -626,7 +635,7 @@ void SelfType::MeshExtractor::loadNode( void SelfType::MeshExtractor::loadNodes() { - m_loaded_nodes = std::vector(m_gltf_model.nodes->size()); + m_loaded_nodes = std::vector(m_gltf_model.nodes->size()); std::vector isChild(m_gltf_model.nodes->size()); for (const auto &node : *m_gltf_model.nodes) { @@ -682,27 +691,27 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); for (std::size_t i = 0; i < n_frames; ++i) { - auto *key = m_irr_model->addPositionKey(joint); - key->frame = inputAccessor.get(i); - key->position = convertHandedness(outputAccessor.get(i)); + f32 frame = inputAccessor.get(i); + core::vector3df position = outputAccessor.get(i); + m_irr_model->addPositionKey(joint, frame, convertHandedness(position)); } break; } case tiniergltf::AnimationChannelTarget::Path::ROTATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); for (std::size_t i = 0; i < n_frames; ++i) { - auto *key = m_irr_model->addRotationKey(joint); - key->frame = inputAccessor.get(i); - key->rotation = convertHandedness(outputAccessor.get(i)); + f32 frame = inputAccessor.get(i); + core::quaternion rotation = outputAccessor.get(i); + m_irr_model->addRotationKey(joint, frame, convertHandedness(rotation)); } break; } case tiniergltf::AnimationChannelTarget::Path::SCALE: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); for (std::size_t i = 0; i < n_frames; ++i) { - auto *key = m_irr_model->addScaleKey(joint); - key->frame = inputAccessor.get(i); - key->scale = outputAccessor.get(i); + f32 frame = inputAccessor.get(i); + core::vector3df scale = outputAccessor.get(i); + m_irr_model->addScaleKey(joint, frame, scale); } break; } @@ -747,8 +756,6 @@ void SelfType::MeshExtractor::load() } catch (const std::bad_optional_access &e) { throw std::runtime_error(e.what()); } - - m_irr_model->finalize(); } /** @@ -791,7 +798,8 @@ std::optional> SelfType::MeshExtractor::getIndices( index = std::get>(accessor).get(elemIdx); if (index == std::numeric_limits::max()) throw std::runtime_error("invalid index"); - } else if (std::holds_alternative>(accessor)) { + } else { + _IRR_DEBUG_BREAK_IF(!std::holds_alternative>(accessor)); u32 indexWide = std::get>(accessor).get(elemIdx); // Use >= here for consistency. if (indexWide >= std::numeric_limits::max()) diff --git a/irr/src/CGLTFMeshFileLoader.h b/irr/src/CGLTFMeshFileLoader.h index ae178565d..a4eac8baa 100644 --- a/irr/src/CGLTFMeshFileLoader.h +++ b/irr/src/CGLTFMeshFileLoader.h @@ -3,7 +3,7 @@ #pragma once -#include "CSkinnedMesh.h" +#include "SkinnedMesh.h" #include "IMeshLoader.h" #include "IReadFile.h" #include "irrTypes.h" @@ -91,7 +91,7 @@ private: const tiniergltf::GlTF &model, const std::size_t accessorIdx); - template + template static std::array getNormalizedValues( const NormalizedValuesAccessor &accessor, const std::size_t i); @@ -100,7 +100,7 @@ private: { public: MeshExtractor(tiniergltf::GlTF &&model, - CSkinnedMesh *mesh) noexcept + SkinnedMeshBuilder *mesh) noexcept : m_gltf_model(std::move(model)), m_irr_model(mesh) {}; /* Gets indices for the given mesh/primitive. @@ -118,20 +118,20 @@ private: std::size_t getPrimitiveCount(const std::size_t meshIdx) const; void load(); - const std::vector &getWarnings() { + const std::unordered_set &getWarnings() { return warnings; } private: const tiniergltf::GlTF m_gltf_model; - CSkinnedMesh *m_irr_model; + SkinnedMeshBuilder *m_irr_model; std::vector> m_mesh_loaders; - std::vector m_loaded_nodes; + std::vector m_loaded_nodes; - std::vector warnings; + std::unordered_set warnings; void warn(const std::string &warning) { - warnings.push_back(warning); + warnings.insert(warning); } void copyPositions(const std::size_t accessorIdx, @@ -145,13 +145,13 @@ private: void addPrimitive(const tiniergltf::MeshPrimitive &primitive, const std::optional skinIdx, - CSkinnedMesh::SJoint *parent); + SkinnedMesh::SJoint *parent); void deferAddMesh(const std::size_t meshIdx, const std::optional skinIdx, - CSkinnedMesh::SJoint *parentJoint); + SkinnedMesh::SJoint *parentJoint); - void loadNode(const std::size_t nodeIdx, CSkinnedMesh::SJoint *parentJoint); + void loadNode(const std::size_t nodeIdx, SkinnedMesh::SJoint *parentJoint); void loadNodes(); diff --git a/irr/src/CGLXManager.cpp b/irr/src/CGLXManager.cpp index 8593621b7..89a5cac20 100644 --- a/irr/src/CGLXManager.cpp +++ b/irr/src/CGLXManager.cpp @@ -23,10 +23,6 @@ namespace video CGLXManager::CGLXManager(const SIrrlichtCreationParameters ¶ms, const SExposedVideoData &videodata, int screennr) : Params(params), PrimaryContext(videodata), VisualInfo(0), glxFBConfig(0), GlxWin(0) { -#ifdef _DEBUG - setDebugName("CGLXManager"); -#endif - CurrentContext.OpenGLLinux.X11Display = PrimaryContext.OpenGLLinux.X11Display; int major, minor; diff --git a/irr/src/CGUIButton.cpp b/irr/src/CGUIButton.cpp index 60bab5f83..ea685be94 100644 --- a/irr/src/CGUIButton.cpp +++ b/irr/src/CGUIButton.cpp @@ -26,9 +26,6 @@ CGUIButton::CGUIButton(IGUIEnvironment *environment, IGUIElement *parent, IsPushButton(false), Pressed(false), UseAlphaChannel(false), DrawBorder(true), ScaleImage(false) { -#ifdef _DEBUG - setDebugName("CGUIButton"); -#endif setNotClipped(noclip); // This element can be tabbed. diff --git a/irr/src/CGUICheckBox.cpp b/irr/src/CGUICheckBox.cpp index 18dd6856a..4d407e676 100644 --- a/irr/src/CGUICheckBox.cpp +++ b/irr/src/CGUICheckBox.cpp @@ -19,10 +19,6 @@ namespace gui CGUICheckBox::CGUICheckBox(bool checked, IGUIEnvironment *environment, IGUIElement *parent, s32 id, core::rect rectangle) : IGUICheckBox(environment, parent, id, rectangle), CheckTime(0), Pressed(false), Checked(checked), Border(false), Background(false) { -#ifdef _DEBUG - setDebugName("CGUICheckBox"); -#endif - // this element can be tabbed into setTabStop(true); setTabOrder(-1); diff --git a/irr/src/CGUIComboBox.cpp b/irr/src/CGUIComboBox.cpp index 0088641b5..b55602b0c 100644 --- a/irr/src/CGUIComboBox.cpp +++ b/irr/src/CGUIComboBox.cpp @@ -26,10 +26,6 @@ CGUIComboBox::CGUIComboBox(IGUIEnvironment *environment, IGUIElement *parent, Selected(-1), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), MaxSelectionRows(5), HasFocus(false), ActiveFont(nullptr) { -#ifdef _DEBUG - setDebugName("CGUIComboBox"); -#endif - IGUISkin *skin = Environment->getSkin(); ListButton = Environment->addButton(core::recti(0, 0, 1, 1), this, -1, L""); diff --git a/irr/src/CGUIEditBox.cpp b/irr/src/CGUIEditBox.cpp index 8d03caf4a..7d1571606 100644 --- a/irr/src/CGUIEditBox.cpp +++ b/irr/src/CGUIEditBox.cpp @@ -39,10 +39,6 @@ CGUIEditBox::CGUIEditBox(const wchar_t *text, bool border, PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), CurrentTextRect(0, 0, 1, 1), FrameRect(rectangle) { -#ifdef _DEBUG - setDebugName("CGUIEditBox"); -#endif - Text = text; if (Environment) diff --git a/irr/src/CGUIEnvironment.cpp b/irr/src/CGUIEnvironment.cpp index 80115ab11..b40896327 100644 --- a/irr/src/CGUIEnvironment.cpp +++ b/irr/src/CGUIEnvironment.cpp @@ -50,10 +50,6 @@ CGUIEnvironment::CGUIEnvironment(io::IFileSystem *fs, video::IVideoDriver *drive if (Operator) Operator->grab(); -#ifdef _DEBUG - IGUIEnvironment::setDebugName("CGUIEnvironment"); -#endif - loadBuiltInFont(); IGUISkin *skin = createSkin(gui::EGST_WINDOWS_METALLIC); diff --git a/irr/src/CGUIFileOpenDialog.cpp b/irr/src/CGUIFileOpenDialog.cpp index 1e669b255..7add9d8e1 100644 --- a/irr/src/CGUIFileOpenDialog.cpp +++ b/irr/src/CGUIFileOpenDialog.cpp @@ -33,10 +33,6 @@ CGUIFileOpenDialog::CGUIFileOpenDialog(const wchar_t *title, (parent->getAbsolutePosition().getHeight() - FOD_HEIGHT) / 2 + FOD_HEIGHT)), FileNameText(0), FileList(0), Dragging(false) { -#ifdef _DEBUG - IGUIElement::setDebugName("CGUIFileOpenDialog"); -#endif - Text = title; FileSystem = Environment ? Environment->getFileSystem() : 0; diff --git a/irr/src/CGUIFont.cpp b/irr/src/CGUIFont.cpp index 4d85702f5..c00b40395 100644 --- a/irr/src/CGUIFont.cpp +++ b/irr/src/CGUIFont.cpp @@ -21,10 +21,6 @@ CGUIFont::CGUIFont(IGUIEnvironment *env, const io::path &filename) : Driver(0), SpriteBank(0), Environment(env), WrongCharacter(0), MaxHeight(0), GlobalKerningWidth(0), GlobalKerningHeight(0) { -#ifdef _DEBUG - setDebugName("CGUIFont"); -#endif - if (Environment) { // don't grab environment, to avoid circular references Driver = Environment->getVideoDriver(); diff --git a/irr/src/CGUIImage.cpp b/irr/src/CGUIImage.cpp index 5f7bb8e26..a17e8f2d0 100644 --- a/irr/src/CGUIImage.cpp +++ b/irr/src/CGUIImage.cpp @@ -17,11 +17,7 @@ namespace gui CGUIImage::CGUIImage(IGUIEnvironment *environment, IGUIElement *parent, s32 id, core::rect rectangle) : IGUIImage(environment, parent, id, rectangle), Texture(0), Color(255, 255, 255, 255), UseAlphaChannel(false), ScaleImage(false), DrawBounds(0.f, 0.f, 1.f, 1.f), DrawBackground(true) -{ -#ifdef _DEBUG - setDebugName("CGUIImage"); -#endif -} +{} //! destructor CGUIImage::~CGUIImage() diff --git a/irr/src/CGUIImageList.cpp b/irr/src/CGUIImageList.cpp index 3bd30d913..b11f10b0c 100644 --- a/irr/src/CGUIImageList.cpp +++ b/irr/src/CGUIImageList.cpp @@ -18,10 +18,6 @@ CGUIImageList::CGUIImageList(video::IVideoDriver *driver) : ImagesPerRow(0), UseAlphaChannel(false) { -#ifdef _DEBUG - setDebugName("CGUIImageList"); -#endif - if (Driver) { Driver->grab(); } diff --git a/irr/src/CGUIListBox.cpp b/irr/src/CGUIListBox.cpp index 78cb96ecf..f8db2cf80 100644 --- a/irr/src/CGUIListBox.cpp +++ b/irr/src/CGUIListBox.cpp @@ -29,10 +29,6 @@ CGUIListBox::CGUIListBox(IGUIEnvironment *environment, IGUIElement *parent, ScrollBar(0), selectTime(0), LastKeyTime(0), Selecting(false), DrawBack(drawBack), MoveOverSelect(moveOverSelect), AutoScroll(true), HighlightWhenNotFocused(true) { -#ifdef _DEBUG - setDebugName("CGUIListBox"); -#endif - IGUISkin *skin = Environment->getSkin(); ScrollBar = new CGUIScrollBar(false, Environment, this, -1, diff --git a/irr/src/CGUIScrollBar.cpp b/irr/src/CGUIScrollBar.cpp index f9ebad09e..5e7737bc1 100644 --- a/irr/src/CGUIScrollBar.cpp +++ b/irr/src/CGUIScrollBar.cpp @@ -28,10 +28,6 @@ CGUIScrollBar::CGUIScrollBar(bool horizontal, IGUIEnvironment *environment, DrawHeight(0), Min(0), Max(100), SmallStep(10), LargeStep(50), DesiredPos(0), LastChange(0) { -#ifdef _DEBUG - setDebugName("CGUIScrollBar"); -#endif - refreshControls(); setNotClipped(noclip); diff --git a/irr/src/CGUISkin.cpp b/irr/src/CGUISkin.cpp index e9721a5fa..3c130f7a1 100644 --- a/irr/src/CGUISkin.cpp +++ b/irr/src/CGUISkin.cpp @@ -20,10 +20,6 @@ namespace gui CGUISkin::CGUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) : SpriteBank(0), Driver(driver), Type(type) { - #ifdef _DEBUG - setDebugName("CGUISkin"); - #endif - if ((Type == EGST_WINDOWS_CLASSIC) || (Type == EGST_WINDOWS_METALLIC)) { Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101,50,50,50); diff --git a/irr/src/CGUISpriteBank.cpp b/irr/src/CGUISpriteBank.cpp index fe0c087b8..ea3fe8788 100644 --- a/irr/src/CGUISpriteBank.cpp +++ b/irr/src/CGUISpriteBank.cpp @@ -16,10 +16,6 @@ namespace gui CGUISpriteBank::CGUISpriteBank(IGUIEnvironment *env) : Environment(env), Driver(0) { -#ifdef _DEBUG - setDebugName("CGUISpriteBank"); -#endif - if (Environment) { Driver = Environment->getVideoDriver(); if (Driver) diff --git a/irr/src/CGUIStaticText.cpp b/irr/src/CGUIStaticText.cpp index 035847583..871589447 100644 --- a/irr/src/CGUIStaticText.cpp +++ b/irr/src/CGUIStaticText.cpp @@ -27,10 +27,6 @@ CGUIStaticText::CGUIStaticText(const wchar_t *text, bool border, OverrideColor(video::SColor(101, 255, 255, 255)), BGColor(video::SColor(101, 210, 210, 210)), OverrideFont(0), LastBreakFont(0) { -#ifdef _DEBUG - setDebugName("CGUIStaticText"); -#endif - Text = text; if (environment && environment->getSkin()) { BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE); diff --git a/irr/src/CGUITabControl.cpp b/irr/src/CGUITabControl.cpp index 3e02773db..bca7f0f21 100644 --- a/irr/src/CGUITabControl.cpp +++ b/irr/src/CGUITabControl.cpp @@ -29,10 +29,6 @@ CGUITab::CGUITab(IGUIEnvironment *environment, BackColor(0, 0, 0, 0), OverrideTextColorEnabled(false), TextColor(255, 0, 0, 0), DrawBackground(false) { -#ifdef _DEBUG - setDebugName("CGUITab"); -#endif - const IGUISkin *const skin = environment->getSkin(); if (skin) TextColor = skin->getColor(EGDC_BUTTON_TEXT); @@ -104,10 +100,6 @@ CGUITabControl::CGUITabControl(IGUIEnvironment *environment, Border(border), FillBackground(fillbackground), ScrollControl(false), TabHeight(0), VerticalAlignment(EGUIA_UPPERLEFT), UpButton(0), DownButton(0), TabMaxWidth(0), CurrentScrollTabIndex(0), TabExtraWidth(20) { -#ifdef _DEBUG - setDebugName("CGUITabControl"); -#endif - IGUISkin *skin = Environment->getSkin(); IGUISpriteBank *sprites = 0; diff --git a/irr/src/CImage.cpp b/irr/src/CImage.cpp index 209590174..0c6d705f3 100644 --- a/irr/src/CImage.cpp +++ b/irr/src/CImage.cpp @@ -95,6 +95,8 @@ SColor CImage::getPixel(u32 x, u32 y) const case ECF_A8R8G8B8: return ((u32 *)Data)[y * Size.Width + x]; case ECF_R8G8B8: { + // FIXME this interprets the memory as [R][G][B], whereas SColor is stored as + // 0xAARRGGBB, meaning it is lies in memory as [B][G][R][A] on a little endian machine. u8 *p = Data + (y * 3) * Size.Width + (x * 3); return SColor(255, p[0], p[1], p[2]); } diff --git a/irr/src/CImageLoaderJPG.cpp b/irr/src/CImageLoaderJPG.cpp index 5b7978a4b..ec1a998b9 100644 --- a/irr/src/CImageLoaderJPG.cpp +++ b/irr/src/CImageLoaderJPG.cpp @@ -19,9 +19,6 @@ namespace video //! constructor CImageLoaderJPG::CImageLoaderJPG() { -#ifdef _DEBUG - setDebugName("CImageLoaderJPG"); -#endif } //! destructor diff --git a/irr/src/CImageLoaderTGA.cpp b/irr/src/CImageLoaderTGA.cpp index 274b15543..75b0b5679 100644 --- a/irr/src/CImageLoaderTGA.cpp +++ b/irr/src/CImageLoaderTGA.cpp @@ -93,6 +93,25 @@ bool CImageLoaderTGA::isALoadableFileFormat(io::IReadFile *file) const return (!strcmp(footer.Signature, "TRUEVISION-XFILE.")); // very old tgas are refused. } +/// Converts *byte order* BGR to *endianness order* ARGB (SColor "=" u32) +static void convert_BGR8_to_SColor(const u8 *src, u32 n, u32 *dst) +{ + for (u32 i = 0; i < n; ++i) { + const u8 *bgr = &src[3 * i]; + dst[i] = 0xff000000 | (bgr[2] << 16) | (bgr[1] << 8) | bgr[0]; + } +} + +/// Converts *byte order* BGRA to *endianness order* ARGB (SColor "=" u32) +/// Note: This just copies from src to dst on little endian. +static void convert_BGRA8_to_SColor(const u8 *src, u32 n, u32 *dst) +{ + for (u32 i = 0; i < n; ++i) { + const u8 *bgra = &src[4 * i]; + dst[i] = (bgra[3] << 24) | (bgra[2] << 16) | (bgra[1] << 8) | bgra[0]; + } +} + //! creates a surface from the file IImage *CImageLoaderTGA::loadImage(io::IReadFile *file) const { @@ -139,10 +158,10 @@ IImage *CImageLoaderTGA::loadImage(io::IReadFile *file) const CColorConverter::convert_A1R5G5B5toA8R8G8B8(colorMap, header.ColorMapLength, palette); break; case 24: - CColorConverter::convert_B8G8R8toA8R8G8B8(colorMap, header.ColorMapLength, palette); + convert_BGR8_to_SColor(colorMap, header.ColorMapLength, palette); break; case 32: - CColorConverter::convert_B8G8R8A8toA8R8G8B8(colorMap, header.ColorMapLength, palette); + convert_BGRA8_to_SColor(colorMap, header.ColorMapLength, palette); break; } delete[] colorMap; diff --git a/irr/src/CImageWriterJPG.cpp b/irr/src/CImageWriterJPG.cpp index fa8ad64bc..778d1fb74 100644 --- a/irr/src/CImageWriterJPG.cpp +++ b/irr/src/CImageWriterJPG.cpp @@ -10,6 +10,7 @@ #include "os.h" #include // IWYU pragma: keep (required for jpeglib.h) +#include #include #include @@ -130,32 +131,28 @@ static bool writeJPEGFile(io::IWriteFile *file, IImage *image, u32 quality) jpeg_set_quality(&cinfo, quality, TRUE); jpeg_start_compress(&cinfo, TRUE); - u8 *dest = new u8[dim.Width * 3]; + std::unique_ptr dest{new u8[dim.Width * 3]}; - if (dest) { - const u32 pitch = image->getPitch(); - JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ - row_pointer[0] = dest; + const u32 pitch = image->getPitch(); + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + row_pointer[0] = dest.get(); - u8 *src = (u8 *)image->getData(); + u8 *src = (u8 *)image->getData(); - while (cinfo.next_scanline < cinfo.image_height) { - // convert next line - format(src, dim.Width, dest); - src += pitch; - jpeg_write_scanlines(&cinfo, row_pointer, 1); - } - - delete[] dest; - - /* Step 6: Finish compression */ - jpeg_finish_compress(&cinfo); + while (cinfo.next_scanline < cinfo.image_height) { + // convert next line + format(src, dim.Width, dest.get()); + src += pitch; + jpeg_write_scanlines(&cinfo, row_pointer, 1); } + /* Step 6: Finish compression */ + jpeg_finish_compress(&cinfo); + /* Step 7: Destroy */ jpeg_destroy_compress(&cinfo); - return (dest != 0); + return true; } } // namespace video @@ -172,11 +169,7 @@ IImageWriter *createImageWriterJPG() } CImageWriterJPG::CImageWriterJPG() -{ -#ifdef _DEBUG - setDebugName("CImageWriterJPG"); -#endif -} +{} bool CImageWriterJPG::isAWriteableFileExtension(const io::path &filename) const { diff --git a/irr/src/CImageWriterPNG.cpp b/irr/src/CImageWriterPNG.cpp index 14c9f2d9c..51332d285 100644 --- a/irr/src/CImageWriterPNG.cpp +++ b/irr/src/CImageWriterPNG.cpp @@ -10,6 +10,7 @@ #include "os.h" // for logging #include // use system lib png +#include namespace irr { @@ -53,11 +54,7 @@ void PNGAPI user_write_data_fcn(png_structp png_ptr, png_bytep data, png_size_t } CImageWriterPNG::CImageWriterPNG() -{ -#ifdef _DEBUG - setDebugName("CImageWriterPNG"); -#endif -} +{} bool CImageWriterPNG::isAWriteableFileExtension(const io::path &filename) const { @@ -94,22 +91,23 @@ bool CImageWriterPNG::writeImage(io::IWriteFile *file, IImage *image, u32 param) png_set_write_fn(png_ptr, file, user_write_data_fcn, NULL); // Set info + core::dimension2d img_dim = image->getDimension(); switch (image->getColorFormat()) { case ECF_A8R8G8B8: case ECF_A1R5G5B5: png_set_IHDR(png_ptr, info_ptr, - image->getDimension().Width, image->getDimension().Height, + img_dim.Width, img_dim.Height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); break; default: png_set_IHDR(png_ptr, info_ptr, - image->getDimension().Width, image->getDimension().Height, + img_dim.Width, img_dim.Height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); } - s32 lineWidth = image->getDimension().Width; + s32 lineWidth = img_dim.Width; switch (image->getColorFormat()) { case ECF_R8G8B8: case ECF_R5G6B5: @@ -123,61 +121,52 @@ bool CImageWriterPNG::writeImage(io::IWriteFile *file, IImage *image, u32 param) default: break; } - u8 *tmpImage = new u8[image->getDimension().Height * lineWidth]; - if (!tmpImage) { - os::Printer::log("PNGWriter: Internal PNG create image failure", file->getFileName(), ELL_ERROR); - png_destroy_write_struct(&png_ptr, &info_ptr); - return false; - } + std::unique_ptr tmpImage{new u8[img_dim.Height * lineWidth]}; + auto num_pixels = img_dim.Height * img_dim.Width; u8 *data = (u8 *)image->getData(); switch (image->getColorFormat()) { case ECF_R8G8B8: - CColorConverter::convert_R8G8B8toR8G8B8(data, image->getDimension().Height * image->getDimension().Width, tmpImage); + CColorConverter::convert_R8G8B8toR8G8B8(data, num_pixels, + tmpImage.get()); break; case ECF_A8R8G8B8: - CColorConverter::convert_A8R8G8B8toA8R8G8B8(data, image->getDimension().Height * image->getDimension().Width, tmpImage); + CColorConverter::convert_A8R8G8B8toA8R8G8B8(data, num_pixels, + tmpImage.get()); break; case ECF_R5G6B5: - CColorConverter::convert_R5G6B5toR8G8B8(data, image->getDimension().Height * image->getDimension().Width, tmpImage); + CColorConverter::convert_R5G6B5toR8G8B8(data, num_pixels, + tmpImage.get()); break; case ECF_A1R5G5B5: - CColorConverter::convert_A1R5G5B5toA8R8G8B8(data, image->getDimension().Height * image->getDimension().Width, tmpImage); + CColorConverter::convert_A1R5G5B5toA8R8G8B8(data, num_pixels, + tmpImage.get()); break; // TODO: Error handling in case of unsupported color format default: os::Printer::log("CImageWriterPNG does not support image format", ColorFormatNames[image->getColorFormat()], ELL_WARNING); png_destroy_write_struct(&png_ptr, &info_ptr); - delete[] tmpImage; return false; } // Create array of pointers to rows in image data // Used to point to image rows - u8 **RowPointers = new png_bytep[image->getDimension().Height]; - if (!RowPointers) { - os::Printer::log("PNGWriter: Internal PNG create row pointers failure", file->getFileName(), ELL_ERROR); - png_destroy_write_struct(&png_ptr, &info_ptr); - delete[] tmpImage; - return false; - } + std::unique_ptr RowPointers{new u8*[img_dim.Height]}; - data = tmpImage; + data = tmpImage.get(); // Fill array of pointers to rows in image data - for (u32 i = 0; i < image->getDimension().Height; ++i) { + for (u32 i = 0; i < img_dim.Height; ++i) { RowPointers[i] = data; data += lineWidth; } // for proper error handling if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); - delete[] RowPointers; - delete[] tmpImage; return false; } - png_set_rows(png_ptr, info_ptr, RowPointers); + png_set_rows(png_ptr, info_ptr, RowPointers.get()); if (image->getColorFormat() == ECF_A8R8G8B8 || image->getColorFormat() == ECF_A1R5G5B5) png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL); @@ -185,8 +174,6 @@ bool CImageWriterPNG::writeImage(io::IWriteFile *file, IImage *image, u32 param) png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); } - delete[] RowPointers; - delete[] tmpImage; png_destroy_write_struct(&png_ptr, &info_ptr); return true; } diff --git a/irr/src/CIrrDeviceLinux.cpp b/irr/src/CIrrDeviceLinux.cpp index e88902936..7c5d9cf0b 100644 --- a/irr/src/CIrrDeviceLinux.cpp +++ b/irr/src/CIrrDeviceLinux.cpp @@ -105,10 +105,6 @@ CIrrDeviceLinux::CIrrDeviceLinux(const SIrrlichtCreationParameters ¶m) : WindowHasFocus(false), WindowMinimized(false), WindowMaximized(param.WindowMaximized), ExternalWindow(false), AutorepeatSupport(0) { -#ifdef _DEBUG - setDebugName("CIrrDeviceLinux"); -#endif - // print version, distribution etc. // thx to LynxLuna for pointing me to the uname function core::stringc linuxversion; @@ -1951,7 +1947,8 @@ Cursor CIrrDeviceLinux::TextureToMonochromeCursor(irr::video::ITexture *tex, con XPutPixel(sourceImage, x, y, 0); } else // color { - if (pixelCol.getAverage() >= 127) + if ((pixelCol.getRed() + pixelCol.getGreen() + + pixelCol.getBlue()) / 3 >= 127) XPutPixel(sourceImage, x, y, 1); else XPutPixel(sourceImage, x, y, 0); diff --git a/irr/src/CIrrDeviceOSX.mm b/irr/src/CIrrDeviceOSX.mm index 4b46e5e29..859d9b752 100644 --- a/irr/src/CIrrDeviceOSX.mm +++ b/irr/src/CIrrDeviceOSX.mm @@ -524,10 +524,6 @@ CIrrDeviceMacOSX::CIrrDeviceMacOSX(const SIrrlichtCreationParameters ¶m) : { struct utsname name; -#ifdef _DEBUG - setDebugName("CIrrDeviceMacOSX"); -#endif - if (firstLaunch) { firstLaunch = false; diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 543bea63e..f7974202f 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -253,10 +253,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) : Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0), IsInBackground(false) { -#ifdef _DEBUG - setDebugName("CIrrDeviceSDL"); -#endif - if (++SDLDeviceInstances == 1) { #ifdef __ANDROID__ // Blocking on pause causes problems with multiplayer. diff --git a/irr/src/CIrrDeviceWin32.cpp b/irr/src/CIrrDeviceWin32.cpp index 417694517..f0947163d 100644 --- a/irr/src/CIrrDeviceWin32.cpp +++ b/irr/src/CIrrDeviceWin32.cpp @@ -713,10 +713,6 @@ CIrrDeviceWin32::CIrrDeviceWin32(const SIrrlichtCreationParameters ¶ms) : ExternalWindow(false), Win32CursorControl(0), JoyControl(0), WindowMaximized(params.WindowMaximized) { -#ifdef _DEBUG - setDebugName("CIrrDeviceWin32"); -#endif - // get windows version and create OS operator core::stringc winversion; getWindowsVersion(winversion); diff --git a/irr/src/CLimitReadFile.cpp b/irr/src/CLimitReadFile.cpp index de9bf9ec3..b13120194 100644 --- a/irr/src/CLimitReadFile.cpp +++ b/irr/src/CLimitReadFile.cpp @@ -17,10 +17,6 @@ CLimitReadFile::CLimitReadFile(IReadFile *alreadyOpenedFile, long pos, AreaStart(0), AreaEnd(0), Pos(0), File(alreadyOpenedFile) { -#ifdef _DEBUG - setDebugName("CLimitReadFile"); -#endif - if (File) { File->grab(); AreaStart = pos; diff --git a/irr/src/CLogger.cpp b/irr/src/CLogger.cpp index 70d06b36a..2bb589a99 100644 --- a/irr/src/CLogger.cpp +++ b/irr/src/CLogger.cpp @@ -9,11 +9,7 @@ namespace irr CLogger::CLogger(IEventReceiver *r) : LogLevel(ELL_INFORMATION), Receiver(r) -{ -#ifdef _DEBUG - setDebugName("CLogger"); -#endif -} +{} //! Returns the current set log level. ELOG_LEVEL CLogger::getLogLevel() const diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 1fbfe5c9f..7e0a3a3fa 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -9,7 +9,7 @@ option(USE_SDL2 "Use the SDL2 backend" ${DEFAULT_SDL2}) # Compiler flags if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-D_DEBUG) + add_compile_definitions(_DEBUG) endif() set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) @@ -43,9 +43,7 @@ elseif(MSVC) endif() # Platform-independent configuration (hard-coded currently) -add_definitions( - -DIRR_ENABLE_BUILTIN_FONT -) +add_compile_definitions(IRR_ENABLE_BUILTIN_FONT) # Platform-specific configuration @@ -56,35 +54,35 @@ endif() # Device if(WIN32) - add_definitions(-D_IRR_WINDOWS_ -D_IRR_WINDOWS_API_) + add_compile_definitions(_IRR_WINDOWS_ _IRR_WINDOWS_API_) set(DEVICE "WINDOWS") elseif(APPLE) - add_definitions(-D_IRR_OSX_PLATFORM_) + add_compile_definitions(_IRR_OSX_PLATFORM_) set(DEVICE "OSX") elseif(ANDROID) - add_definitions(-D_IRR_ANDROID_PLATFORM_) + add_compile_definitions(_IRR_ANDROID_PLATFORM_) if(NOT USE_SDL2) message(FATAL_ERROR "The Android build requires SDL2") endif() elseif(EMSCRIPTEN) - add_definitions(-D_IRR_EMSCRIPTEN_PLATFORM_ -D_IRR_COMPILE_WITH_EGL_MANAGER_) + add_compile_definitions(_IRR_EMSCRIPTEN_PLATFORM_ _IRR_COMPILE_WITH_EGL_MANAGER_) set(LINUX_PLATFORM TRUE) set(DEVICE "SDL") elseif(SOLARIS) - add_definitions(-D_IRR_SOLARIS_PLATFORM_ -D_IRR_POSIX_API_) + add_compile_definitions(_IRR_SOLARIS_PLATFORM_ _IRR_POSIX_API_) set(DEVICE "X11") else() - add_definitions(-D_IRR_POSIX_API_) + add_compile_definitions(_IRR_POSIX_API_) set(LINUX_PLATFORM TRUE) set(DEVICE "X11") endif() if(LINUX_PLATFORM) - add_definitions(-D_IRR_LINUX_PLATFORM_) + add_compile_definitions(_IRR_LINUX_PLATFORM_) endif() if(MSVC) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) endif() if(USE_SDL2) @@ -93,7 +91,7 @@ elseif(DEVICE STREQUAL "SDL") message(FATAL_ERROR "SDL was used but not enabled?!") endif() -add_definitions("-D_IRR_COMPILE_WITH_${DEVICE}_DEVICE_") +add_compile_definitions("_IRR_COMPILE_WITH_${DEVICE}_DEVICE_") # X11 @@ -114,7 +112,7 @@ endif() # Joystick if(NOT (BSD OR SOLARIS OR EMSCRIPTEN)) - add_definitions(-D_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + add_compile_definitions(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) endif() # OpenGL @@ -154,15 +152,15 @@ endif() if(ENABLE_OPENGL OR (ENABLE_OPENGL3 AND NOT USE_SDL2)) if(ENABLE_OPENGL) - add_definitions(-D_IRR_COMPILE_WITH_OPENGL_) + add_compile_definitions(_IRR_COMPILE_WITH_OPENGL_) set(OPENGL_DIRECT_LINK TRUE) # driver relies on this endif() if(DEVICE STREQUAL "WINDOWS") - add_definitions(-D_IRR_COMPILE_WITH_WGL_MANAGER_) + add_compile_definitions(_IRR_COMPILE_WITH_WGL_MANAGER_) elseif(DEVICE STREQUAL "X11") - add_definitions(-D_IRR_COMPILE_WITH_GLX_MANAGER_) + add_compile_definitions(_IRR_COMPILE_WITH_GLX_MANAGER_) elseif(DEVICE STREQUAL "OSX") - add_definitions(-D_IRR_COMPILE_WITH_NSOGL_MANAGER_) + add_compile_definitions(_IRR_COMPILE_WITH_NSOGL_MANAGER_) endif() endif() @@ -177,14 +175,14 @@ if(ENABLE_OPENGL3) endif() if(ENABLE_GLES2) - add_definitions(-D_IRR_COMPILE_WITH_OGLES2_) + add_compile_definitions(_IRR_COMPILE_WITH_OGLES2_) if(DEVICE MATCHES "^(WINDOWS|X11)$" OR EMSCRIPTEN) - add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_) + add_compile_definitions(_IRR_COMPILE_WITH_EGL_MANAGER_) endif() endif() if(ENABLE_WEBGL1) - add_definitions(-D_IRR_COMPILE_WITH_WEBGL1_) + add_compile_definitions(_IRR_COMPILE_WITH_WEBGL1_) endif() # Misc @@ -192,7 +190,7 @@ endif() include(TestBigEndian) TEST_BIG_ENDIAN(BIG_ENDIAN) if(BIG_ENDIAN) - add_definitions(-D__BIG_ENDIAN__) + add_compile_definitions(__BIG_ENDIAN__) endif() # Configuration report @@ -263,7 +261,7 @@ if(ENABLE_OPENGL AND DEVICE STREQUAL "SDL") #endif\n\ int main() {}" CHECK_GL_VERSION_4_5) if(CHECK_GL_VERSION_4_5) - add_definitions(-DIRR_PREFER_SDL_GL_HEADER) + add_compile_definitions(IRR_PREFER_SDL_GL_HEADER) endif() endif() @@ -275,7 +273,7 @@ elseif(APPLE) find_library(COCOA_LIB Cocoa REQUIRED) find_library(IOKIT_LIB IOKit REQUIRED) - add_definitions(-DGL_SILENCE_DEPRECATION) + add_compile_definitions(GL_SILENCE_DEPRECATION) elseif(NOT USE_SDL2) # Unix probably find_package(X11 REQUIRED) @@ -310,7 +308,7 @@ set(IRRMESHLOADER ) add_library(IRRMESHOBJ OBJECT - CSkinnedMesh.cpp + SkinnedMesh.cpp CBoneSceneNode.cpp CMeshSceneNode.cpp CAnimatedMeshSceneNode.cpp diff --git a/irr/src/CMemoryFile.cpp b/irr/src/CMemoryFile.cpp index 6d7906b91..4e6baa99c 100644 --- a/irr/src/CMemoryFile.cpp +++ b/irr/src/CMemoryFile.cpp @@ -12,11 +12,7 @@ namespace io CMemoryReadFile::CMemoryReadFile(const void *memory, long len, const io::path &fileName, bool d) : Buffer(memory), Len(len), Pos(0), Filename(fileName), deleteMemoryWhenDropped(d) -{ -#ifdef _DEBUG - setDebugName("CMemoryReadFile"); -#endif -} +{} CMemoryReadFile::~CMemoryReadFile() { @@ -82,11 +78,7 @@ const io::path &CMemoryReadFile::getFileName() const CMemoryWriteFile::CMemoryWriteFile(void *memory, long len, const io::path &fileName, bool d) : Buffer(memory), Len(len), Pos(0), Filename(fileName), deleteMemoryWhenDropped(d) -{ -#ifdef _DEBUG - setDebugName("CMemoryWriteFile"); -#endif -} +{} CMemoryWriteFile::~CMemoryWriteFile() { diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 67b22a07e..63157403b 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -3,7 +3,7 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CMeshManipulator.h" -#include "ISkinnedMesh.h" +#include "SkinnedMesh.h" #include "SMesh.h" #include "CMeshBuffer.h" #include "SAnimatedMesh.h" @@ -101,7 +101,7 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool return; if (mesh->getMeshType() == EAMT_SKINNED) { - ISkinnedMesh *smesh = (ISkinnedMesh *)mesh; + auto *smesh = (SkinnedMesh *)mesh; smesh->resetAnimation(); } @@ -110,7 +110,7 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool recalculateNormals(mesh->getMeshBuffer(b), smooth, angleWeighted); if (mesh->getMeshType() == EAMT_SKINNED) { - ISkinnedMesh *smesh = (ISkinnedMesh *)mesh; + auto *smesh = (SkinnedMesh *)mesh; smesh->refreshJointCache(); } } diff --git a/irr/src/CMeshSceneNode.cpp b/irr/src/CMeshSceneNode.cpp index 030e1fd15..2d9e400e9 100644 --- a/irr/src/CMeshSceneNode.cpp +++ b/irr/src/CMeshSceneNode.cpp @@ -5,10 +5,8 @@ #include "CMeshSceneNode.h" #include "IVideoDriver.h" #include "ISceneManager.h" -#include "S3DVertex.h" -#include "ICameraSceneNode.h" #include "IMeshCache.h" -#include "IAnimatedMesh.h" +#include "IMeshBuffer.h" #include "IMaterialRenderer.h" #include "IFileSystem.h" @@ -25,10 +23,6 @@ CMeshSceneNode::CMeshSceneNode(IMesh *mesh, ISceneNode *parent, ISceneManager *m Mesh(0), PassCount(0), ReadOnlyMaterials(false) { -#ifdef _DEBUG - setDebugName("CMeshSceneNode"); -#endif - setMesh(mesh); } diff --git a/irr/src/CNSOGLManager.mm b/irr/src/CNSOGLManager.mm index c1e543e53..b550df20e 100644 --- a/irr/src/CNSOGLManager.mm +++ b/irr/src/CNSOGLManager.mm @@ -16,11 +16,7 @@ namespace video CNSOGLManager::CNSOGLManager() : PrimaryContext(SExposedVideoData(0)), PixelFormat(nil) -{ -#ifdef _DEBUG - setDebugName("CNSOGLManager"); -#endif -} +{} CNSOGLManager::~CNSOGLManager() { diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index acce8383b..80aacb042 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -53,10 +53,6 @@ CNullDriver::CNullDriver(io::IFileSystem *io, const core::dimension2d &scre ViewPort(0, 0, 0, 0), ScreenSize(screenSize), MinVertexCountForVBO(500), TextureCreationFlags(0), OverrideMaterial2DEnabled(false), AllowZWriteOnTransparent(false) { -#ifdef _DEBUG - setDebugName("CNullDriver"); -#endif - DriverAttributes = new io::CAttributes(); DriverAttributes->addInt("MaxTextures", MATERIAL_MAX_TEXTURES); DriverAttributes->addInt("MaxSupportedTextures", MATERIAL_MAX_TEXTURES); @@ -743,19 +739,6 @@ SFrameStats CNullDriver::getFrameStats() const return FrameStats; } -//! Sets the dynamic ambient light color. The default color is -//! (0,0,0,0) which means it is dark. -//! \param color: New color of the ambient light. -void CNullDriver::setAmbientLight(const SColorf &color) -{ - AmbientLight = color; -} - -const SColorf &CNullDriver::getAmbientLight() const -{ - return AmbientLight; -} - //! \return Returns the name of the video driver. Example: In case of the DIRECT3D8 //! driver, it would return "Direct3D8". @@ -1678,6 +1661,12 @@ ITexture *CNullDriver::addRenderTargetTexture(const core::dimension2d &size return 0; } +ITexture *CNullDriver::addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format) +{ + return 0; +} + ITexture *CNullDriver::addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) { @@ -1768,21 +1757,5 @@ bool CNullDriver::needsTransparentRenderPass(const irr::video::SMaterial &materi return false; } -//! Color conversion convenience function -/** Convert an image (as array of pixels) from source to destination -array, thereby converting the color format. The pixel size is -determined by the color formats. -\param sP Pointer to source -\param sF Color format of source -\param sN Number of pixels to convert, both array must be large enough -\param dP Pointer to destination -\param dF Color format of destination -*/ -void CNullDriver::convertColor(const void *sP, ECOLOR_FORMAT sF, s32 sN, - void *dP, ECOLOR_FORMAT dF) const -{ - video::CColorConverter::convert_viaFormat(sP, sF, sN, dP, dF); -} - } // end namespace } // end namespace diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index b8d45118f..e772dd8e8 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -201,14 +201,6 @@ public: //! driver, it would return "Direct3D8.1". const char *getName() const override; - //! Sets the dynamic ambient light color. The default color is - //! (0,0,0,0) which means it is dark. - //! \param color: New color of the ambient light. - void setAmbientLight(const SColorf &color) override; - - //! Get the global ambient light currently used by the driver - const SColorf &getAmbientLight() const override; - //! Adds an external image loader to the engine. void addExternalImageLoader(IImageLoader *loader) override; @@ -227,6 +219,10 @@ public: virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a multisampled render target texture. + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a render target texture for a cubemap ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) override; @@ -410,6 +406,8 @@ public: //! Create render target. IRenderTarget *addRenderTarget() override; + void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override {} + //! Remove render target. void removeRenderTarget(IRenderTarget *renderTarget) override; @@ -553,19 +551,6 @@ public: //! Used by some SceneNodes to check if a material should be rendered in the transparent render pass bool needsTransparentRenderPass(const irr::video::SMaterial &material) const override; - //! Color conversion convenience function - /** Convert an image (as array of pixels) from source to destination - array, thereby converting the color format. The pixel size is - determined by the color formats. - \param sP Pointer to source - \param sF Color format of source - \param sN Number of pixels to convert, both array must be large enough - \param dP Pointer to destination - \param dF Color format of destination - */ - virtual void convertColor(const void *sP, ECOLOR_FORMAT sF, s32 sN, - void *dP, ECOLOR_FORMAT dF) const override; - protected: //! deletes all textures void deleteAllTextures(); @@ -753,8 +738,6 @@ protected: bool AllowZWriteOnTransparent; bool FeatureEnabled[video::EVDF_COUNT]; - - SColorf AmbientLight; }; } // end namespace video diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index 97e90c322..fdbcbd1d0 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -26,11 +26,7 @@ namespace scene //! Constructor COBJMeshFileLoader::COBJMeshFileLoader(scene::ISceneManager *smgr) : SceneManager(smgr) -{ -#ifdef _DEBUG - setDebugName("COBJMeshFileLoader"); -#endif -} +{} //! destructor COBJMeshFileLoader::~COBJMeshFileLoader() diff --git a/irr/src/COSOperator.cpp b/irr/src/COSOperator.cpp index d53223916..518f26563 100644 --- a/irr/src/COSOperator.cpp +++ b/irr/src/COSOperator.cpp @@ -43,11 +43,7 @@ COSOperator::COSOperator(const core::stringc &osVersion, CIrrDeviceLinux *device // constructor COSOperator::COSOperator(const core::stringc &osVersion) : OperatingSystem(osVersion) -{ -#ifdef _DEBUG - setDebugName("COSOperator"); -#endif -} +{} COSOperator::~COSOperator() { diff --git a/irr/src/COpenGLCoreCacheHandler.h b/irr/src/COpenGLCoreCacheHandler.h index a1277bfed..744511629 100644 --- a/irr/src/COpenGLCoreCacheHandler.h +++ b/irr/src/COpenGLCoreCacheHandler.h @@ -85,13 +85,19 @@ class COpenGLCoreCacheHandler GL.BindTexture(prevTextureType, 0); #if defined(IRR_COMPILE_GL_COMMON) - GL.Disable(prevTextureType); - GL.Enable(curTextureType); + // The "enable/disable texture" stuff is so legacy that + // it's not even allowed for multisample textures. + // (IRR_COMPILE_GL_COMMON is for the legacy driver.) + if (prevTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Disable(prevTextureType); + if (curTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Enable(curTextureType); #endif } #if defined(IRR_COMPILE_GL_COMMON) else if (!prevTexture) - GL.Enable(curTextureType); + if (curTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Enable(curTextureType); #endif GL.BindTexture(curTextureType, static_cast(texture)->getOpenGLTextureName()); @@ -110,7 +116,8 @@ class COpenGLCoreCacheHandler GL.BindTexture(prevTextureType, 0); #if defined(IRR_COMPILE_GL_COMMON) - GL.Disable(prevTextureType); + if (prevTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Disable(prevTextureType); #endif } diff --git a/irr/src/COpenGLCoreRenderTarget.h b/irr/src/COpenGLCoreRenderTarget.h index 6bfc98cc0..50656ce1f 100644 --- a/irr/src/COpenGLCoreRenderTarget.h +++ b/irr/src/COpenGLCoreRenderTarget.h @@ -5,6 +5,7 @@ #pragma once #include "IRenderTarget.h" +#include #ifndef GL_FRAMEBUFFER_INCOMPLETE_FORMATS #define GL_FRAMEBUFFER_INCOMPLETE_FORMATS GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT @@ -27,10 +28,6 @@ public: AssignedDepth(false), AssignedStencil(false), RequestTextureUpdate(false), RequestDepthStencilUpdate(false), BufferID(0), ColorAttachment(0), MultipleRenderTarget(0), Driver(driver) { -#ifdef _DEBUG - setDebugName("COpenGLCoreRenderTarget"); -#endif - DriverType = Driver->getDriverType(); Size = Driver->getScreenSize(); @@ -122,7 +119,7 @@ public: TOpenGLTexture *currentTexture = (depthStencil && depthStencil->getDriverType() == DriverType) ? static_cast(depthStencil) : 0; if (currentTexture) { - if (currentTexture->getType() == ETT_2D) { + if (currentTexture->getType() == ETT_2D || currentTexture->getType() == ETT_2D_MS) { GLuint textureID = currentTexture->getOpenGLTextureName(); const ECOLOR_FORMAT textureFormat = (textureID != 0) ? depthStencil->getColorFormat() : ECF_UNKNOWN; @@ -172,7 +169,20 @@ public: if (textureID != 0) { AssignedTextures[i] = GL_COLOR_ATTACHMENT0 + i; - GLenum textarget = currentTexture->getType() == ETT_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int)CubeSurfaces[i]; + GLenum textarget; + switch (currentTexture->getType()) { + case ETT_2D: + textarget = GL_TEXTURE_2D; + break; + case ETT_2D_MS: + textarget = GL_TEXTURE_2D_MULTISAMPLE; + break; + case ETT_CUBEMAP: + textarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int)CubeSurfaces[i]; + break; + default: + throw std::logic_error("not reachable"); + } Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, AssignedTextures[i], textarget, textureID, 0); TEST_GL_ERROR(Driver); } else if (AssignedTextures[i] != GL_NONE) { @@ -198,36 +208,50 @@ public: // Set depth and stencil attachments. if (RequestDepthStencilUpdate) { - const ECOLOR_FORMAT textureFormat = (DepthStencil) ? DepthStencil->getColorFormat() : ECF_UNKNOWN; + const ECOLOR_FORMAT textureFormat = DepthStencil ? DepthStencil->getColorFormat() : ECF_UNKNOWN; if (IImage::isDepthFormat(textureFormat)) { + GLenum textarget; + switch (DepthStencil->getType()) { + case ETT_2D: + textarget = GL_TEXTURE_2D; + break; + case ETT_2D_MS: + textarget = GL_TEXTURE_2D_MULTISAMPLE; + break; + default: + // ETT_CUBEMAP is rejected for depth/stencil by setTextures + throw std::logic_error("not reachable"); + } + GLuint textureID = static_cast(DepthStencil)->getOpenGLTextureName(); #ifdef _IRR_EMSCRIPTEN_PLATFORM_ // The WEBGL_depth_texture extension does not allow attaching stencil+depth separate. if (textureFormat == ECF_D24S8) { GLenum attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, textureID, 0); AssignedStencil = true; } else { - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget, textureID, 0); AssignedStencil = false; } #else - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget, textureID, 0); if (textureFormat == ECF_D24S8) { - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget, textureID, 0); AssignedStencil = true; } else { if (AssignedStencil) - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget, 0, 0); AssignedStencil = false; } #endif AssignedDepth = true; } else { + // No (valid) depth/stencil texture. if (AssignedDepth) Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 35c8f472a..d8a813d5d 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -45,12 +45,13 @@ public: COpenGLCoreTexture(const io::path &name, const std::vector &srcImages, E_TEXTURE_TYPE type, TOpenGLDriver *driver) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), - TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), + TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(0), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false) { _IRR_DEBUG_BREAK_IF(srcImages.empty()) DriverType = Driver->getDriverType(); + _IRR_DEBUG_BREAK_IF(Type == ETT_2D_MS); // not supported by this constructor TextureType = TextureTypeIrrToGL(Type); HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY); @@ -141,10 +142,10 @@ public: TEST_GL_ERROR(Driver); } - COpenGLCoreTexture(const io::path &name, const core::dimension2d &size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver *driver) : + COpenGLCoreTexture(const io::path &name, const core::dimension2d &size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver *driver, u8 msaa = 0) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), - TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), + TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(msaa), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false) { DriverType = Driver->getDriverType(); @@ -169,6 +170,17 @@ public: return; } +#ifndef IRR_COMPILE_GL_COMMON + // On GLES 3.0 we must use sized internal formats for textures in certain + // cases (e.g. with ETT_2D_MS). However ECF_A8R8G8B8 is mapped to GL_BGRA + // (an unsized format). + // Since we don't upload to RTT we can safely pick a different combo that works. + if (InternalFormat == GL_BGRA && Driver->Version.Major >= 3) { + InternalFormat = GL_RGBA8; + PixelFormat = GL_RGBA; + } +#endif + #ifdef _DEBUG char lbuf[100]; snprintf_irr(lbuf, sizeof(lbuf), @@ -184,23 +196,47 @@ public: const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); - GL.TexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - GL.TexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // An INVALID_ENUM error is generated by TexParameter* if target is either + // TEXTURE_2D_MULTISAMPLE or TEXTURE_2D_MULTISAMPLE_ARRAY, and pname is any + // sampler state from table 23.18. + // ~ https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf + if (Type != ETT_2D_MS) { + GL.TexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + GL.TexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #if defined(GL_VERSION_1_2) - GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); #endif - StatesCache.WrapU = ETC_CLAMP_TO_EDGE; - StatesCache.WrapV = ETC_CLAMP_TO_EDGE; - StatesCache.WrapW = ETC_CLAMP_TO_EDGE; + StatesCache.WrapU = ETC_CLAMP_TO_EDGE; + StatesCache.WrapV = ETC_CLAMP_TO_EDGE; + StatesCache.WrapW = ETC_CLAMP_TO_EDGE; + } switch (Type) { case ETT_2D: GL.TexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); break; + case ETT_2D_MS: { + // glTexImage2DMultisample is supported by OpenGL 3.2+ + // glTexStorage2DMultisample is supported by OpenGL 4.3+ and OpenGL ES 3.1+ +#ifdef IRR_COMPILE_GL_COMMON // legacy driver + constexpr bool use_gl_impl = true; +#else + const bool use_gl_impl = Driver->Version.Spec != OpenGLSpec::ES; +#endif + GLint max_samples = 0; + GL.GetIntegerv(GL_MAX_SAMPLES, &max_samples); + MSAA = std::min(MSAA, (u8)max_samples); + + if (use_gl_impl) + GL.TexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); + else + GL.TexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); + break; + } case ETT_CUBEMAP: GL.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); GL.TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); @@ -595,6 +631,8 @@ protected: switch (type) { case ETT_2D: return GL_TEXTURE_2D; + case ETT_2D_MS: + return GL_TEXTURE_2D_MULTISAMPLE; case ETT_CUBEMAP: return GL_TEXTURE_CUBE_MAP; } @@ -610,6 +648,7 @@ protected: GLint InternalFormat; GLenum PixelFormat; GLenum PixelType; + u8 MSAA; void (*Converter)(const void *, s32, void *); bool LockReadOnly; diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 2cbf6d8e6..7d98482e8 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -32,11 +32,7 @@ COpenGLDriver::COpenGLDriver(const SIrrlichtCreationParameters ¶ms, io::IFil CNullDriver(io, params.WindowSize), COpenGLExtensionHandler(), CacheHandler(0), CurrentRenderMode(ERM_NONE), ResetRenderStates(true), Transformation3DChanged(true), AntiAlias(params.AntiAlias), ColorFormat(ECF_R8G8B8), FixedPipelineState(EOFPS_ENABLE), Params(params), ContextManager(contextManager) -{ -#ifdef _DEBUG - setDebugName("COpenGLDriver"); -#endif -} +{} bool COpenGLDriver::initDriver() { @@ -122,7 +118,6 @@ bool COpenGLDriver::genericDriverInit() os::Printer::log("GLSL not available.", ELL_INFORMATION); DriverAttributes->setAttribute("MaxTextures", (s32)Feature.MaxTextureUnits); DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Feature.MaxTextureUnits); - DriverAttributes->setAttribute("MaxLights", MaxLights); DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); DriverAttributes->setAttribute("MaxAuxBuffers", MaxAuxBuffers); DriverAttributes->setAttribute("MaxMultipleRenderTargets", (s32)Feature.MultipleRenderTarget); @@ -139,13 +134,6 @@ bool COpenGLDriver::genericDriverInit() for (i = 0; i < ETS_COUNT; ++i) setTransform(static_cast(i), core::IdentityMatrix); - setAmbientLight(SColorf(0.0f, 0.0f, 0.0f, 0.0f)); -#ifdef GL_EXT_separate_specular_color - if (FeatureAvailable[IRR_EXT_separate_specular_color]) - glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); -#endif - glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); - // This is a fast replacement for NORMALIZE_NORMALS // if ((Version>101) || FeatureAvailable[IRR_EXT_rescale_normal]) // glEnable(GL_RESCALE_NORMAL_EXT); @@ -651,6 +639,29 @@ IRenderTarget *COpenGLDriver::addRenderTarget() return renderTarget; } +void COpenGLDriver::blitRenderTarget(IRenderTarget *from, IRenderTarget *to) +{ + if (Version < 300) { + os::Printer::log("glBlitFramebuffer not supported by OpenGL < 3.0", ELL_ERROR); + return; + } + + GLuint prev_fbo_id; + CacheHandler->getFBO(prev_fbo_id); + + COpenGLRenderTarget *src = static_cast(from); + COpenGLRenderTarget *dst = static_cast(to); + GL.BindFramebuffer(GL.READ_FRAMEBUFFER, src->getBufferID()); + GL.BindFramebuffer(GL.DRAW_FRAMEBUFFER, dst->getBufferID()); + GL.BlitFramebuffer( + 0, 0, src->getSize().Width, src->getSize().Height, + 0, 0, dst->getSize().Width, dst->getSize().Height, + GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, GL.NEAREST); + + // This resets both read and draw framebuffer. Note that we bypass CacheHandler here. + GL.BindFramebuffer(GL.FRAMEBUFFER, prev_fbo_id); +} + // small helper function to create vertex buffer object address offsets static inline const GLvoid *buffer_offset(const size_t offset) { @@ -2091,7 +2102,9 @@ void COpenGLDriver::setBasicRenderStates(const SMaterial &material, const SMater else if (lastmaterial.AntiAliasing & EAAM_ALPHA_TO_COVERAGE) glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); - if ((AntiAlias >= 2) && (material.AntiAliasing & (EAAM_SIMPLE | EAAM_QUALITY))) { + // Enable MSAA even if it's not enabled in the OpenGL context, we might + // be rendering to an FBO with multisampling. + if (material.AntiAliasing & (EAAM_SIMPLE | EAAM_QUALITY)) { glEnable(GL_MULTISAMPLE_ARB); #ifdef GL_NV_multisample_filter_hint if (FeatureAvailable[IRR_NV_multisample_filter_hint]) { @@ -2395,16 +2408,6 @@ const char *COpenGLDriver::getName() const return Name.c_str(); } -//! Sets the dynamic ambient light color. The default color is -//! (0,0,0,0) which means it is dark. -//! \param color: New color of the ambient light. -void COpenGLDriver::setAmbientLight(const SColorf &color) -{ - CNullDriver::setAmbientLight(color); - GLfloat data[4] = {color.r, color.g, color.b, color.a}; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, data); -} - // this code was sent in by Oliver Klems, thank you! (I modified the glViewport // method just a bit. void COpenGLDriver::setViewPort(const core::rect &area) @@ -2694,6 +2697,12 @@ IVideoDriver *COpenGLDriver::getVideoDriver() ITexture *COpenGLDriver::addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format) +{ + return addRenderTargetTextureMs(size, 0, name, format); +} + +ITexture *COpenGLDriver::addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format) { if (IImage::isCompressedFormat(format)) return 0; @@ -2711,7 +2720,7 @@ ITexture *COpenGLDriver::addRenderTargetTexture(const core::dimension2d &si destSize = destSize.getOptimalSize((size == size.getOptimalSize()), false, false); } - COpenGLTexture *renderTargetTexture = new COpenGLTexture(name, destSize, ETT_2D, format, this); + COpenGLTexture *renderTargetTexture = new COpenGLTexture(name, destSize, msaa > 0 ? ETT_2D_MS : ETT_2D, format, this, msaa); addTexture(renderTargetTexture); renderTargetTexture->drop(); diff --git a/irr/src/COpenGLDriver.h b/irr/src/COpenGLDriver.h index 9c4ecd3b3..aa457b8ee 100644 --- a/irr/src/COpenGLDriver.h +++ b/irr/src/COpenGLDriver.h @@ -108,6 +108,8 @@ public: //! Create render target. IRenderTarget *addRenderTarget() override; + void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override; + //! draws a vertex primitive list virtual void drawVertexPrimitiveList(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, @@ -180,11 +182,6 @@ public: //! driver, it would return "Direct3D8.1". const char *getName() const override; - //! Sets the dynamic ambient light color. The default color is - //! (0,0,0,0) which means it is dark. - //! \param color: New color of the ambient light. - void setAmbientLight(const SColorf &color) override; - //! sets a viewport void setViewPort(const core::rect &area) override; @@ -270,6 +267,9 @@ public: virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name = "rt", const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a render target texture for a cubemap ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) override; diff --git a/irr/src/COpenGLExtensionHandler.cpp b/irr/src/COpenGLExtensionHandler.cpp index a20932c8f..7eb4252a6 100644 --- a/irr/src/COpenGLExtensionHandler.cpp +++ b/irr/src/COpenGLExtensionHandler.cpp @@ -19,7 +19,7 @@ namespace video bool COpenGLExtensionHandler::needsDSAFramebufferHack = true; COpenGLExtensionHandler::COpenGLExtensionHandler() : - StencilBuffer(false), TextureCompressionExtension(false), MaxLights(1), + StencilBuffer(false), TextureCompressionExtension(false), MaxAnisotropy(1), MaxAuxBuffers(0), MaxIndices(65535), MaxTextureSize(1), MaxGeometryVerticesOut(0), MaxTextureLODBias(0.f), Version(0), ShaderLanguageVersion(0), @@ -399,8 +399,6 @@ void COpenGLExtensionHandler::initExtensions(video::IContextManager *cmgr, bool Feature.MaxTextureUnits = core::max_(Feature.MaxTextureUnits, static_cast(num)); } #endif - glGetIntegerv(GL_MAX_LIGHTS, &num); - MaxLights = static_cast(num); #ifdef GL_EXT_texture_filter_anisotropic if (FeatureAvailable[IRR_EXT_texture_filter_anisotropic]) { glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &num); @@ -612,6 +610,8 @@ bool COpenGLExtensionHandler::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const return FeatureAvailable[IRR_ARB_seamless_cube_map]; case EVDF_DEPTH_CLAMP: return FeatureAvailable[IRR_NV_depth_clamp] || FeatureAvailable[IRR_ARB_depth_clamp]; + case EVDF_TEXTURE_MULTISAMPLE: + return (Version >= 302) || FeatureAvailable[IRR_ARB_texture_multisample]; default: return false; diff --git a/irr/src/COpenGLExtensionHandler.h b/irr/src/COpenGLExtensionHandler.h index cdff911b9..a1754a328 100644 --- a/irr/src/COpenGLExtensionHandler.h +++ b/irr/src/COpenGLExtensionHandler.h @@ -1015,8 +1015,6 @@ public: bool TextureCompressionExtension; // Some non-boolean properties - //! Maximum hardware lights supported - u8 MaxLights; //! Maximal Anisotropy u8 MaxAnisotropy; //! Number of auxiliary buffers diff --git a/irr/src/COpenGLSLMaterialRenderer.cpp b/irr/src/COpenGLSLMaterialRenderer.cpp index 27393adeb..de0f090c3 100644 --- a/irr/src/COpenGLSLMaterialRenderer.cpp +++ b/irr/src/COpenGLSLMaterialRenderer.cpp @@ -50,10 +50,6 @@ COpenGLSLMaterialRenderer::COpenGLSLMaterialRenderer(video::COpenGLDriver *drive Driver(driver), CallBack(callback), Alpha(false), Blending(false), AlphaTest(false), Program(0), Program2(0), UserData(userData) { -#ifdef _DEBUG - setDebugName("COpenGLSLMaterialRenderer"); -#endif - switch (baseMaterial) { case EMT_TRANSPARENT_VERTEX_ALPHA: case EMT_TRANSPARENT_ALPHA_CHANNEL: diff --git a/irr/src/CReadFile.cpp b/irr/src/CReadFile.cpp index 6c6e49d55..3ef90e715 100644 --- a/irr/src/CReadFile.cpp +++ b/irr/src/CReadFile.cpp @@ -12,10 +12,6 @@ namespace io CReadFile::CReadFile(const io::path &fileName) : File(0), FileSize(0), Filename(fileName) { -#ifdef _DEBUG - setDebugName("CReadFile"); -#endif - openFile(); } diff --git a/irr/src/CSDLManager.cpp b/irr/src/CSDLManager.cpp index 855c8c9e4..56fe9e0d7 100644 --- a/irr/src/CSDLManager.cpp +++ b/irr/src/CSDLManager.cpp @@ -15,11 +15,7 @@ namespace video CSDLManager::CSDLManager(CIrrDeviceSDL *device) : IContextManager(), SDLDevice(device) -{ -#ifdef _DEBUG - setDebugName("CSDLManager"); -#endif -} +{} bool CSDLManager::initialize(const SIrrlichtCreationParameters ¶ms, const SExposedVideoData &data) { diff --git a/irr/src/CSceneCollisionManager.cpp b/irr/src/CSceneCollisionManager.cpp index 77549a7dc..3d8f1091f 100644 --- a/irr/src/CSceneCollisionManager.cpp +++ b/irr/src/CSceneCollisionManager.cpp @@ -17,10 +17,6 @@ namespace scene CSceneCollisionManager::CSceneCollisionManager(ISceneManager *smanager, video::IVideoDriver *driver) : SceneManager(smanager), Driver(driver) { -#ifdef _DEBUG - setDebugName("CSceneCollisionManager"); -#endif - if (Driver) Driver->grab(); } diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index d75abc284..94b6c24a7 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -16,7 +16,7 @@ #include "os.h" -#include "CSkinnedMesh.h" +#include "SkinnedMesh.h" #include "CXMeshFileLoader.h" #include "COBJMeshFileLoader.h" #include "CB3DMeshFileLoader.h" @@ -41,14 +41,9 @@ CSceneManager::CSceneManager(video::IVideoDriver *driver, ISceneNode(0, 0), Driver(driver), CursorControl(cursorControl), - ActiveCamera(0), ShadowColor(150, 0, 0, 0), AmbientLight(0, 0, 0, 0), Parameters(0), + ActiveCamera(0), Parameters(0), MeshCache(cache), CurrentRenderPass(ESNRP_NONE) { -#ifdef _DEBUG - ISceneManager::setDebugName("CSceneManager ISceneManager"); - ISceneNode::setDebugName("CSceneManager ISceneNode"); -#endif - // root node's scene manager SceneManager = this; @@ -445,9 +440,6 @@ u32 CSceneManager::registerNodeForRendering(ISceneNode *node, E_SCENE_NODE_RENDE taken = 1; } - // as of yet unused - case ESNRP_LIGHT: - case ESNRP_SHADOW: case ESNRP_NONE: // ignore this one break; } @@ -472,14 +464,12 @@ void CSceneManager::drawAll() if (!Driver) return; - u32 i; // new ISO for scoping problem in some compilers - // reset all transforms Driver->setMaterial(video::SMaterial()); Driver->setTransform(video::ETS_PROJECTION, core::IdentityMatrix); Driver->setTransform(video::ETS_VIEW, core::IdentityMatrix); Driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); - for (i = video::ETS_COUNT - 1; i >= video::ETS_TEXTURE_0; --i) + for (u32 i = video::ETS_COUNT - 1; i >= video::ETS_TEXTURE_0; --i) Driver->setTransform((video::E_TRANSFORMATION_STATE)i, core::IdentityMatrix); // TODO: This should not use an attribute here but a real parameter when necessary (too slow!) Driver->setAllowZWriteOnTransparent(Parameters->getAttributeAsBool(ALLOW_ZWRITE_ON_TRANSPARENT)); @@ -775,22 +765,10 @@ ISceneManager *CSceneManager::createNewSceneManager(bool cloneContent) return manager; } -//! Sets ambient color of the scene -void CSceneManager::setAmbientLight(const video::SColorf &ambientColor) -{ - AmbientLight = ambientColor; -} - -//! Returns ambient color of the scene -const video::SColorf &CSceneManager::getAmbientLight() const -{ - return AmbientLight; -} - //! Get a skinned mesh, which is not available as header-only code -ISkinnedMesh *CSceneManager::createSkinnedMesh() +SkinnedMesh *CSceneManager::createSkinnedMesh() { - return new CSkinnedMesh(); + return new SkinnedMesh(); } // creates a scenemanager diff --git a/irr/src/CSceneManager.h b/irr/src/CSceneManager.h index 4ef6d64b0..2a4fb7f7b 100644 --- a/irr/src/CSceneManager.h +++ b/irr/src/CSceneManager.h @@ -4,6 +4,7 @@ #pragma once +#include "SkinnedMesh.h" #include "ISceneManager.h" #include "ISceneNode.h" #include "ICursorControl.h" @@ -22,6 +23,8 @@ namespace scene { class IMeshCache; +class SkinnedMesh; + /*! The Scene Manager manages scene nodes, mesh resources, cameras and all the other stuff. */ @@ -167,14 +170,8 @@ public: //! Returns type of the scene node ESCENE_NODE_TYPE getType() const override { return ESNT_SCENE_MANAGER; } - //! Get a skinned mesh, which is not available as header-only code - ISkinnedMesh *createSkinnedMesh() override; - - //! Sets ambient color of the scene - void setAmbientLight(const video::SColorf &ambientColor) override; - - //! Returns ambient color of the scene - const video::SColorf &getAmbientLight() const override; + //! Get a skinned mesh + SkinnedMesh *createSkinnedMesh() override; //! Get current render time. E_SCENE_NODE_RENDER_PASS getCurrentRenderPass() const override { return CurrentRenderPass; } @@ -199,21 +196,21 @@ private: } DefaultNodeEntry(ISceneNode *n) : - Node(n), TextureValue(0) + Node(n) { if (n->getMaterialCount()) - TextureValue = (n->getMaterial(0).getTexture(0)); + Hash = std::hash{}(n->getMaterial(0)); } - bool operator<(const DefaultNodeEntry &other) const + bool operator<(const DefaultNodeEntry &other) const noexcept { - return (TextureValue < other.TextureValue); + return Hash < other.Hash; } - ISceneNode *Node; + ISceneNode *Node = nullptr; private: - void *TextureValue; + size_t Hash = 0; }; //! sort on distance (center) to camera @@ -229,42 +226,15 @@ private: Distance = Node->getAbsoluteTransformation().getTranslation().getDistanceFromSQ(camera); } - bool operator<(const TransparentNodeEntry &other) const + bool operator<(const TransparentNodeEntry &other) const noexcept { return Distance > other.Distance; } - ISceneNode *Node; + ISceneNode *Node = nullptr; private: - f64 Distance; - }; - - //! sort on distance (sphere) to camera - struct DistanceNodeEntry - { - DistanceNodeEntry(ISceneNode *n, const core::vector3df &cameraPos) : - Node(n) - { - setNodeAndDistanceFromPosition(n, cameraPos); - } - - bool operator<(const DistanceNodeEntry &other) const - { - return Distance < other.Distance; - } - - void setNodeAndDistanceFromPosition(ISceneNode *n, const core::vector3df &fromPosition) - { - Node = n; - Distance = Node->getAbsoluteTransformation().getTranslation().getDistanceFromSQ(fromPosition); - Distance -= Node->getBoundingBox().getExtent().getLengthSQ() * 0.5; - } - - ISceneNode *Node; - - private: - f64 Distance; + f32 Distance = 0; }; //! video driver @@ -291,9 +261,6 @@ private: ICameraSceneNode *ActiveCamera; core::vector3df camWorldPos; // Position of camera for transparent nodes. - video::SColor ShadowColor; - video::SColorf AmbientLight; - //! String parameters // NOTE: Attributes are slow and should only be used for debug-info and not in release io::CAttributes *Parameters; diff --git a/irr/src/CSkinnedMesh.cpp b/irr/src/CSkinnedMesh.cpp deleted file mode 100644 index 875fd8e7e..000000000 --- a/irr/src/CSkinnedMesh.cpp +++ /dev/null @@ -1,1354 +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 "CSkinnedMesh.h" -#include -#include "CBoneSceneNode.h" -#include "IAnimatedMeshSceneNode.h" -#include "SSkinMeshBuffer.h" -#include "os.h" - -namespace -{ -// Frames must always be increasing, so we remove objects where this isn't the case -// return number of kicked keys -template // T = objects containing a "frame" variable -irr::u32 dropBadKeys(irr::core::array &array) -{ - if (array.size() < 2) - return 0; - - irr::u32 n = 1; // new index - for (irr::u32 j = 1; j < array.size(); ++j) { - if (array[j].frame < array[n - 1].frame) - continue; // bad frame, unneeded and may cause problems - if (n != j) - array[n] = array[j]; - ++n; - } - irr::u32 d = array.size() - n; // remove already copied keys - if (d > 0) { - array.erase(n, d); - } - return d; -} - -// drop identical middle keys - we only need the first and last -// return number of kicked keys -template // Cmp = comparison for keys of type T -irr::u32 dropMiddleKeys(irr::core::array &array, Cmp &cmp) -{ - if (array.size() < 3) - return 0; - - irr::u32 s = 0; // old index for current key - irr::u32 n = 1; // new index for next key - for (irr::u32 j = 1; j < array.size(); ++j) { - if (cmp(array[j], array[s])) - continue; // same key, handle later - - if (j > s + 1) // had there been identical keys? - array[n++] = array[j - 1]; // keep the last - array[n++] = array[j]; // keep the new one - s = j; - } - if (array.size() > s + 1) // identical keys at the array end? - array[n++] = array[array.size() - 1]; // keep the last - - irr::u32 d = array.size() - n; // remove already copied keys - if (d > 0) { - array.erase(n, d); - } - return d; -} - -bool identicalPos(const irr::scene::ISkinnedMesh::SPositionKey &a, const irr::scene::ISkinnedMesh::SPositionKey &b) -{ - return a.position == b.position; -} - -bool identicalScale(const irr::scene::ISkinnedMesh::SScaleKey &a, const irr::scene::ISkinnedMesh::SScaleKey &b) -{ - return a.scale == b.scale; -} - -bool identicalRotation(const irr::scene::ISkinnedMesh::SRotationKey &a, const irr::scene::ISkinnedMesh::SRotationKey &b) -{ - return a.rotation == b.rotation; -} -} - -namespace irr -{ -namespace scene -{ - -//! constructor -CSkinnedMesh::CSkinnedMesh() : - SkinningBuffers(0), EndFrame(0.f), FramesPerSecond(25.f), - LastAnimatedFrame(-1), SkinnedLastFrame(false), - InterpolationMode(EIM_LINEAR), - HasAnimation(false), PreparedForSkinning(false), - AnimateNormals(true), HardwareSkinning(false) -{ -#ifdef _DEBUG - setDebugName("CSkinnedMesh"); -#endif - - SkinningBuffers = &LocalBuffers; -} - -//! destructor -CSkinnedMesh::~CSkinnedMesh() -{ - for (u32 i = 0; i < AllJoints.size(); ++i) - delete AllJoints[i]; - - for (u32 j = 0; j < LocalBuffers.size(); ++j) { - if (LocalBuffers[j]) - LocalBuffers[j]->drop(); - } -} - -f32 CSkinnedMesh::getMaxFrameNumber() const -{ - return EndFrame; -} - -//! 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 CSkinnedMesh::getAnimationSpeed() const -{ - 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 CSkinnedMesh::setAnimationSpeed(f32 fps) -{ - FramesPerSecond = fps; -} - -//! returns the animated mesh based -IMesh *CSkinnedMesh::getMesh(f32 frame) -{ - // animate(frame,startFrameLoop, endFrameLoop); - if (frame == -1) - return this; - - animateMesh(frame, 1.0f); - skinMesh(); - return this; -} - -//-------------------------------------------------------------------------- -// Keyframe Animation -//-------------------------------------------------------------------------- - -//! Animates this mesh's joints based on frame input -//! blend: {0-old position, 1-New position} -void CSkinnedMesh::animateMesh(f32 frame, f32 blend) -{ - if (!HasAnimation || LastAnimatedFrame == frame) - return; - - LastAnimatedFrame = frame; - SkinnedLastFrame = false; - - if (blend <= 0.f) - return; // No need to animate - - for (u32 i = 0; i < AllJoints.size(); ++i) { - // The joints can be animated here with no input from their - // parents, but for setAnimationMode extra checks are needed - // to their parents - SJoint *joint = AllJoints[i]; - - const core::vector3df oldPosition = joint->Animatedposition; - const core::vector3df oldScale = joint->Animatedscale; - const core::quaternion oldRotation = joint->Animatedrotation; - - core::vector3df position = oldPosition; - core::vector3df scale = oldScale; - core::quaternion rotation = oldRotation; - - getFrameData(frame, joint, - position, joint->positionHint, - scale, joint->scaleHint, - rotation, joint->rotationHint); - - if (blend == 1.0f) { - // No blending needed - joint->Animatedposition = position; - joint->Animatedscale = scale; - joint->Animatedrotation = rotation; - } else { - // Blend animation - joint->Animatedposition = core::lerp(oldPosition, position, blend); - joint->Animatedscale = core::lerp(oldScale, scale, blend); - joint->Animatedrotation.slerp(oldRotation, rotation, blend); - } - } - - // 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 CSkinnedMesh::buildAllLocalAnimatedMatrices() -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - - // Could be faster: - - if (joint->UseAnimationFrom && - (joint->UseAnimationFrom->PositionKeys.size() || - joint->UseAnimationFrom->ScaleKeys.size() || - joint->UseAnimationFrom->RotationKeys.size())) { - 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->ScaleKeys.size()) { - /* - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - joint->LocalAnimatedMatrix *= scaleMatrix; - */ - - // -------- joint->LocalAnimatedMatrix *= scaleMatrix ----------------- - core::matrix4 &mat = joint->LocalAnimatedMatrix; - mat[0] *= joint->Animatedscale.X; - mat[1] *= joint->Animatedscale.X; - mat[2] *= joint->Animatedscale.X; - mat[3] *= joint->Animatedscale.X; - mat[4] *= joint->Animatedscale.Y; - mat[5] *= joint->Animatedscale.Y; - mat[6] *= joint->Animatedscale.Y; - mat[7] *= joint->Animatedscale.Y; - mat[8] *= joint->Animatedscale.Z; - mat[9] *= joint->Animatedscale.Z; - mat[10] *= joint->Animatedscale.Z; - mat[11] *= joint->Animatedscale.Z; - // ----------------------------------- - } - } else { - joint->LocalAnimatedMatrix = joint->LocalMatrix; - } - } - SkinnedLastFrame = false; -} - -void CSkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint) -{ - if (!joint) { - for (u32 i = 0; i < RootJoints.size(); ++i) - buildAllGlobalAnimatedMatrices(RootJoints[i], 0); - return; - } else { - // Find global matrix... - if (!parentJoint || joint->GlobalSkinningSpace) - joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix; - else - joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix; - } - - for (u32 j = 0; j < joint->Children.size(); ++j) - buildAllGlobalAnimatedMatrices(joint->Children[j], joint); -} - -void CSkinnedMesh::getFrameData(f32 frame, SJoint *joint, - core::vector3df &position, s32 &positionHint, - core::vector3df &scale, s32 &scaleHint, - core::quaternion &rotation, s32 &rotationHint) -{ - s32 foundPositionIndex = -1; - s32 foundScaleIndex = -1; - s32 foundRotationIndex = -1; - - if (joint->UseAnimationFrom) { - const core::array &PositionKeys = joint->UseAnimationFrom->PositionKeys; - const core::array &ScaleKeys = joint->UseAnimationFrom->ScaleKeys; - const core::array &RotationKeys = joint->UseAnimationFrom->RotationKeys; - - if (PositionKeys.size()) { - foundPositionIndex = -1; - - // Test the Hints... - if (positionHint >= 0 && (u32)positionHint < PositionKeys.size()) { - // check this hint - if (positionHint > 0 && PositionKeys[positionHint].frame >= frame && PositionKeys[positionHint - 1].frame < frame) - foundPositionIndex = positionHint; - else if (positionHint + 1 < (s32)PositionKeys.size()) { - // check the next index - if (PositionKeys[positionHint + 1].frame >= frame && - PositionKeys[positionHint + 0].frame < frame) { - positionHint++; - foundPositionIndex = positionHint; - } - } - } - - // The hint test failed, do a full scan... - if (foundPositionIndex == -1) { - for (u32 i = 0; i < PositionKeys.size(); ++i) { - if (PositionKeys[i].frame >= frame) { // Keys should to be sorted by frame - foundPositionIndex = i; - positionHint = i; - break; - } - } - } - - // Do interpolation... - if (foundPositionIndex != -1) { - if (InterpolationMode == EIM_CONSTANT || foundPositionIndex == 0) { - position = PositionKeys[foundPositionIndex].position; - } else if (InterpolationMode == EIM_LINEAR) { - const SPositionKey &KeyA = PositionKeys[foundPositionIndex]; - const SPositionKey &KeyB = PositionKeys[foundPositionIndex - 1]; - - const f32 fd1 = frame - KeyA.frame; - const f32 fd2 = KeyB.frame - frame; - position = ((KeyB.position - KeyA.position) / (fd1 + fd2)) * fd1 + KeyA.position; - } - } - } - - //------------------------------------------------------------ - - if (ScaleKeys.size()) { - foundScaleIndex = -1; - - // Test the Hints... - if (scaleHint >= 0 && (u32)scaleHint < ScaleKeys.size()) { - // check this hint - if (scaleHint > 0 && ScaleKeys[scaleHint].frame >= frame && ScaleKeys[scaleHint - 1].frame < frame) - foundScaleIndex = scaleHint; - else if (scaleHint + 1 < (s32)ScaleKeys.size()) { - // check the next index - if (ScaleKeys[scaleHint + 1].frame >= frame && - ScaleKeys[scaleHint + 0].frame < frame) { - scaleHint++; - foundScaleIndex = scaleHint; - } - } - } - - // The hint test failed, do a full scan... - if (foundScaleIndex == -1) { - for (u32 i = 0; i < ScaleKeys.size(); ++i) { - if (ScaleKeys[i].frame >= frame) { // Keys should to be sorted by frame - foundScaleIndex = i; - scaleHint = i; - break; - } - } - } - - // Do interpolation... - if (foundScaleIndex != -1) { - if (InterpolationMode == EIM_CONSTANT || foundScaleIndex == 0) { - scale = ScaleKeys[foundScaleIndex].scale; - } else if (InterpolationMode == EIM_LINEAR) { - const SScaleKey &KeyA = ScaleKeys[foundScaleIndex]; - const SScaleKey &KeyB = ScaleKeys[foundScaleIndex - 1]; - - const f32 fd1 = frame - KeyA.frame; - const f32 fd2 = KeyB.frame - frame; - scale = ((KeyB.scale - KeyA.scale) / (fd1 + fd2)) * fd1 + KeyA.scale; - } - } - } - - //------------------------------------------------------------- - - if (RotationKeys.size()) { - foundRotationIndex = -1; - - // Test the Hints... - if (rotationHint >= 0 && (u32)rotationHint < RotationKeys.size()) { - // check this hint - if (rotationHint > 0 && RotationKeys[rotationHint].frame >= frame && RotationKeys[rotationHint - 1].frame < frame) - foundRotationIndex = rotationHint; - else if (rotationHint + 1 < (s32)RotationKeys.size()) { - // check the next index - if (RotationKeys[rotationHint + 1].frame >= frame && - RotationKeys[rotationHint + 0].frame < frame) { - rotationHint++; - foundRotationIndex = rotationHint; - } - } - } - - // The hint test failed, do a full scan... - if (foundRotationIndex == -1) { - for (u32 i = 0; i < RotationKeys.size(); ++i) { - if (RotationKeys[i].frame >= frame) { // Keys should be sorted by frame - foundRotationIndex = i; - rotationHint = i; - break; - } - } - } - - // Do interpolation... - if (foundRotationIndex != -1) { - if (InterpolationMode == EIM_CONSTANT || foundRotationIndex == 0) { - rotation = RotationKeys[foundRotationIndex].rotation; - } else if (InterpolationMode == EIM_LINEAR) { - const SRotationKey &KeyA = RotationKeys[foundRotationIndex]; - const SRotationKey &KeyB = RotationKeys[foundRotationIndex - 1]; - - const f32 fd1 = frame - KeyA.frame; - const f32 fd2 = KeyB.frame - frame; - const f32 t = fd1 / (fd1 + fd2); - - /* - f32 t = 0; - if (KeyA.frame!=KeyB.frame) - t = (frame-KeyA.frame) / (KeyB.frame - KeyA.frame); - */ - - rotation.slerp(KeyA.rotation, KeyB.rotation, t); - } - } - } - } -} - -//-------------------------------------------------------------------------- -// Software Skinning -//-------------------------------------------------------------------------- - -//! Preforms a software skin on this mesh based of joint positions -void CSkinnedMesh::skinMesh() -{ - if (!HasAnimation || SkinnedLastFrame) - return; - - //---------------- - // This is marked as "Temp!". A shiny dubloon to whomever can tell me why. - buildAllGlobalAnimatedMatrices(); - //----------------- - - SkinnedLastFrame = true; - if (!HardwareSkinning) { - // Software skin.... - u32 i; - - // rigid animation - for (i = 0; i < AllJoints.size(); ++i) { - for (u32 j = 0; j < AllJoints[i]->AttachedMeshes.size(); ++j) { - SSkinMeshBuffer *Buffer = (*SkinningBuffers)[AllJoints[i]->AttachedMeshes[j]]; - Buffer->Transformation = AllJoints[i]->GlobalAnimatedMatrix; - } - } - - // clear skinning helper array - for (i = 0; i < Vertices_Moved.size(); ++i) - for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) - Vertices_Moved[i][j] = false; - - // skin starting with the root joints - for (i = 0; i < RootJoints.size(); ++i) - skinJoint(RootJoints[i], 0); - - for (i = 0; i < SkinningBuffers->size(); ++i) - (*SkinningBuffers)[i]->setDirty(EBT_VERTEX); - } - updateBoundingBox(); -} - -void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) -{ - if (joint->Weights.size()) { - // 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::vector3df thisVertexMove, thisNormalMove; - - core::array &buffersUsed = *SkinningBuffers; - - // Skin Vertices Positions and Normals... - for (u32 i = 0; i < joint->Weights.size(); ++i) { - SWeight &weight = joint->Weights[i]; - - // Pull this vertex... - jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); - - if (AnimateNormals) { - thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal); - thisNormalMove.normalize(); // must renormalize after potentially scaling - } - - if (!(*(weight.Moved))) { - *(weight.Moved) = true; - - buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength; - - if (AnimateNormals) - buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength; - - //*(weight._Pos) = thisVertexMove * weight.strength; - } else { - buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength; - - if (AnimateNormals) - buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength; - - //*(weight._Pos) += thisVertexMove * weight.strength; - } - - buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated(); - } - } - - // Skin all children - for (u32 j = 0; j < joint->Children.size(); ++j) - skinJoint(joint->Children[j], joint); -} - -E_ANIMATED_MESH_TYPE CSkinnedMesh::getMeshType() const -{ - return EAMT_SKINNED; -} - -//! Gets joint count. -u32 CSkinnedMesh::getJointCount() const -{ - return AllJoints.size(); -} - -//! Gets the name of a joint. -const std::optional &CSkinnedMesh::getJointName(u32 number) const -{ - if (number >= getJointCount()) { - static const std::optional nullopt; - return nullopt; - } - return AllJoints[number]->Name; -} - -//! Gets a joint number from its name -std::optional CSkinnedMesh::getJointNumber(const std::string &name) const -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - if (AllJoints[i]->Name == name) - return i; - } - - return std::nullopt; -} - -//! returns amount of mesh buffers. -u32 CSkinnedMesh::getMeshBufferCount() const -{ - return LocalBuffers.size(); -} - -//! returns pointer to a mesh buffer -IMeshBuffer *CSkinnedMesh::getMeshBuffer(u32 nr) const -{ - if (nr < LocalBuffers.size()) - return LocalBuffers[nr]; - else - return 0; -} - -//! Returns pointer to a mesh buffer which fits a material -IMeshBuffer *CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const -{ - for (u32 i = 0; i < LocalBuffers.size(); ++i) { - if (LocalBuffers[i]->getMaterial() == material) - return LocalBuffers[i]; - } - return 0; -} - -u32 CSkinnedMesh::getTextureSlot(u32 meshbufNr) const -{ - return TextureSlots.at(meshbufNr); -} - -void CSkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) { - TextureSlots.at(meshbufNr) = textureSlot; -} - -//! returns an axis aligned bounding box -const core::aabbox3d &CSkinnedMesh::getBoundingBox() const -{ - return BoundingBox; -} - -//! set user axis aligned bounding box -void CSkinnedMesh::setBoundingBox(const core::aabbox3df &box) -{ - BoundingBox = box; -} - -//! set the hardware mapping hint, for driver -void CSkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, - E_BUFFER_TYPE buffer) -{ - for (u32 i = 0; i < LocalBuffers.size(); ++i) - LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer); -} - -//! flags the meshbuffer as changed, reloads hardware buffers -void CSkinnedMesh::setDirty(E_BUFFER_TYPE buffer) -{ - for (u32 i = 0; i < LocalBuffers.size(); ++i) - LocalBuffers[i]->setDirty(buffer); -} - -//! uses animation from another mesh -bool CSkinnedMesh::useAnimationFrom(const ISkinnedMesh *mesh) -{ - bool unmatched = false; - - for (u32 i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - joint->UseAnimationFrom = 0; - - if (joint->Name == "") - unmatched = true; - else { - for (u32 j = 0; j < mesh->getAllJoints().size(); ++j) { - SJoint *otherJoint = mesh->getAllJoints()[j]; - if (joint->Name == otherJoint->Name) { - joint->UseAnimationFrom = otherJoint; - } - } - if (!joint->UseAnimationFrom) - unmatched = true; - } - } - - checkForAnimation(); - - return !unmatched; -} - -//! Update Normals when Animating -//! False= Don't animate them, faster -//! True= Update normals (default) -void CSkinnedMesh::updateNormalsWhenAnimating(bool on) -{ - AnimateNormals = on; -} - -//! Sets Interpolation Mode -void CSkinnedMesh::setInterpolationMode(E_INTERPOLATION_MODE mode) -{ - InterpolationMode = mode; -} - -core::array &CSkinnedMesh::getMeshBuffers() -{ - return LocalBuffers; -} - -core::array &CSkinnedMesh::getAllJoints() -{ - return AllJoints; -} - -const core::array &CSkinnedMesh::getAllJoints() const -{ - return AllJoints; -} - -//! (This feature is not implemented in irrlicht yet) -bool CSkinnedMesh::setHardwareSkinning(bool on) -{ - if (HardwareSkinning != on) { - if (on) { - - // set mesh to static pose... - for (u32 i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (u32 j = 0; j < joint->Weights.size(); ++j) { - const u16 buffer_id = joint->Weights[j].buffer_id; - const u32 vertex_id = joint->Weights[j].vertex_id; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal; - LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated(); - } - } - } - - HardwareSkinning = on; - } - return HardwareSkinning; -} - -void CSkinnedMesh::refreshJointCache() -{ - // copy cache from the mesh... - for (u32 i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (u32 j = 0; j < joint->Weights.size(); ++j) { - const u16 buffer_id = joint->Weights[j].buffer_id; - const u32 vertex_id = joint->Weights[j].vertex_id; - joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; - joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; - } - } -} - -void CSkinnedMesh::resetAnimation() -{ - // copy from the cache to the mesh... - for (u32 i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (u32 j = 0; j < joint->Weights.size(); ++j) { - const u16 buffer_id = joint->Weights[j].buffer_id; - const u32 vertex_id = joint->Weights[j].vertex_id; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal; - } - } - SkinnedLastFrame = false; - LastAnimatedFrame = -1; -} - -void CSkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint) -{ - if (!joint && parentJoint) // bit of protection from endless loops - return; - - // Go through the root bones - if (!joint) { - for (u32 i = 0; i < RootJoints.size(); ++i) - calculateGlobalMatrices(RootJoints[i], 0); - return; - } - - if (!parentJoint) - joint->GlobalMatrix = joint->LocalMatrix; - else - joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix; - - joint->LocalAnimatedMatrix = joint->LocalMatrix; - joint->GlobalAnimatedMatrix = joint->GlobalMatrix; - - if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated - joint->GlobalInversedMatrix = joint->GlobalMatrix; - joint->GlobalInversedMatrix->makeInverse(); // slow - } - - for (u32 j = 0; j < joint->Children.size(); ++j) - calculateGlobalMatrices(joint->Children[j], joint); - SkinnedLastFrame = false; -} - -void CSkinnedMesh::checkForAnimation() -{ - u32 i, j; - // Check for animation... - HasAnimation = false; - for (i = 0; i < AllJoints.size(); ++i) { - if (AllJoints[i]->UseAnimationFrom) { - if (AllJoints[i]->UseAnimationFrom->PositionKeys.size() || - AllJoints[i]->UseAnimationFrom->ScaleKeys.size() || - AllJoints[i]->UseAnimationFrom->RotationKeys.size()) { - HasAnimation = true; - } - } - } - - // meshes with weights, are still counted as animated for ragdolls, etc - if (!HasAnimation) { - for (i = 0; i < AllJoints.size(); ++i) { - if (AllJoints[i]->Weights.size()) - HasAnimation = true; - } - } - - if (HasAnimation) { - //--- Find the length of the animation --- - EndFrame = 0; - for (i = 0; i < AllJoints.size(); ++i) { - if (AllJoints[i]->UseAnimationFrom) { - if (AllJoints[i]->UseAnimationFrom->PositionKeys.size()) - if (AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame > EndFrame) - EndFrame = AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame; - - if (AllJoints[i]->UseAnimationFrom->ScaleKeys.size()) - if (AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame > EndFrame) - EndFrame = AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame; - - if (AllJoints[i]->UseAnimationFrom->RotationKeys.size()) - if (AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame > EndFrame) - EndFrame = AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame; - } - } - } - - if (HasAnimation && !PreparedForSkinning) { - PreparedForSkinning = true; - - // check for bugs: - for (i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (j = 0; j < joint->Weights.size(); ++j) { - const u16 buffer_id = joint->Weights[j].buffer_id; - const u32 vertex_id = joint->Weights[j].vertex_id; - - // check for invalid ids - if (buffer_id >= LocalBuffers.size()) { - os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); - joint->Weights[j].buffer_id = joint->Weights[j].vertex_id = 0; - } else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) { - os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); - joint->Weights[j].buffer_id = joint->Weights[j].vertex_id = 0; - } - } - } - - // An array used in skinning - - for (i = 0; i < Vertices_Moved.size(); ++i) - for (j = 0; j < Vertices_Moved[i].size(); ++j) - Vertices_Moved[i][j] = false; - - // For skinning: cache weight values for speed - - for (i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (j = 0; j < joint->Weights.size(); ++j) { - const u16 buffer_id = joint->Weights[j].buffer_id; - const u32 vertex_id = joint->Weights[j].vertex_id; - - joint->Weights[j].Moved = &Vertices_Moved[buffer_id][vertex_id]; - joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; - joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; - - // joint->Weights[j]._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos; - } - } - - // normalize weights - normalizeWeights(); - } - SkinnedLastFrame = false; -} - -//! called by loader after populating with mesh and bone data -void CSkinnedMesh::finalize() -{ - os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG); - u32 i; - - // Make sure we recalc the next frame - LastAnimatedFrame = -1; - SkinnedLastFrame = false; - - // calculate bounding box - for (i = 0; i < LocalBuffers.size(); ++i) { - LocalBuffers[i]->recalculateBoundingBox(); - } - - if (AllJoints.size() || RootJoints.size()) { - // populate AllJoints or RootJoints, depending on which is empty - if (!RootJoints.size()) { - - for (u32 CheckingIdx = 0; CheckingIdx < AllJoints.size(); ++CheckingIdx) { - - bool foundParent = false; - for (i = 0; i < AllJoints.size(); ++i) { - for (u32 n = 0; n < AllJoints[i]->Children.size(); ++n) { - if (AllJoints[i]->Children[n] == AllJoints[CheckingIdx]) - foundParent = true; - } - } - - if (!foundParent) - RootJoints.push_back(AllJoints[CheckingIdx]); - } - } else { - AllJoints = RootJoints; - } - } - - for (i = 0; i < AllJoints.size(); ++i) { - AllJoints[i]->UseAnimationFrom = AllJoints[i]; - } - - // Set array sizes... - - for (i = 0; i < LocalBuffers.size(); ++i) { - Vertices_Moved.push_back(core::array()); - Vertices_Moved[i].set_used(LocalBuffers[i]->getVertexCount()); - } - - checkForAnimation(); - - if (HasAnimation) { - irr::u32 redundantPosKeys = 0; - irr::u32 unorderedPosKeys = 0; - irr::u32 redundantScaleKeys = 0; - irr::u32 unorderedScaleKeys = 0; - irr::u32 redundantRotationKeys = 0; - irr::u32 unorderedRotationKeys = 0; - - //--- optimize and check keyframes --- - for (i = 0; i < AllJoints.size(); ++i) { - core::array &PositionKeys = AllJoints[i]->PositionKeys; - core::array &ScaleKeys = AllJoints[i]->ScaleKeys; - core::array &RotationKeys = AllJoints[i]->RotationKeys; - - // redundant = identical middle keys - we only need the first and last frame - // unordered = frames which are out of order - we can't handle those - redundantPosKeys += dropMiddleKeys(PositionKeys, identicalPos); - unorderedPosKeys += dropBadKeys(PositionKeys); - redundantScaleKeys += dropMiddleKeys(ScaleKeys, identicalScale); - unorderedScaleKeys += dropBadKeys(ScaleKeys); - redundantRotationKeys += dropMiddleKeys(RotationKeys, identicalRotation); - unorderedRotationKeys += dropBadKeys(RotationKeys); - - // Fill empty keyframe areas - if (PositionKeys.size()) { - SPositionKey *Key; - Key = &PositionKeys[0]; // getFirst - if (Key->frame != 0) { - PositionKeys.push_front(*Key); - Key = &PositionKeys[0]; // getFirst - Key->frame = 0; - } - - Key = &PositionKeys.getLast(); - if (Key->frame != EndFrame) { - PositionKeys.push_back(*Key); - Key = &PositionKeys.getLast(); - Key->frame = EndFrame; - } - } - - if (ScaleKeys.size()) { - SScaleKey *Key; - Key = &ScaleKeys[0]; // getFirst - if (Key->frame != 0) { - ScaleKeys.push_front(*Key); - Key = &ScaleKeys[0]; // getFirst - Key->frame = 0; - } - - Key = &ScaleKeys.getLast(); - if (Key->frame != EndFrame) { - ScaleKeys.push_back(*Key); - Key = &ScaleKeys.getLast(); - Key->frame = EndFrame; - } - } - - if (RotationKeys.size()) { - SRotationKey *Key; - Key = &RotationKeys[0]; // getFirst - if (Key->frame != 0) { - RotationKeys.push_front(*Key); - Key = &RotationKeys[0]; // getFirst - Key->frame = 0; - } - - Key = &RotationKeys.getLast(); - if (Key->frame != EndFrame) { - RotationKeys.push_back(*Key); - Key = &RotationKeys.getLast(); - Key->frame = EndFrame; - } - } - } - - if (redundantPosKeys > 0) { - os::Printer::log("Skinned Mesh - redundant position frames kicked", core::stringc(redundantPosKeys).c_str(), ELL_DEBUG); - } - if (unorderedPosKeys > 0) { - irr::os::Printer::log("Skinned Mesh - unsorted position frames kicked", irr::core::stringc(unorderedPosKeys).c_str(), irr::ELL_DEBUG); - } - if (redundantScaleKeys > 0) { - os::Printer::log("Skinned Mesh - redundant scale frames kicked", core::stringc(redundantScaleKeys).c_str(), ELL_DEBUG); - } - if (unorderedScaleKeys > 0) { - irr::os::Printer::log("Skinned Mesh - unsorted scale frames kicked", irr::core::stringc(unorderedScaleKeys).c_str(), irr::ELL_DEBUG); - } - if (redundantRotationKeys > 0) { - os::Printer::log("Skinned Mesh - redundant rotation frames kicked", core::stringc(redundantRotationKeys).c_str(), ELL_DEBUG); - } - if (unorderedRotationKeys > 0) { - irr::os::Printer::log("Skinned Mesh - unsorted rotation frames kicked", irr::core::stringc(unorderedRotationKeys).c_str(), irr::ELL_DEBUG); - } - } - - // Needed for animation and skinning... - - calculateGlobalMatrices(0, 0); - - // animateMesh(0, 1); - // buildAllLocalAnimatedMatrices(); - // buildAllGlobalAnimatedMatrices(); - - // rigid animation for non animated meshes - for (i = 0; i < AllJoints.size(); ++i) { - for (u32 j = 0; j < AllJoints[i]->AttachedMeshes.size(); ++j) { - SSkinMeshBuffer *Buffer = (*SkinningBuffers)[AllJoints[i]->AttachedMeshes[j]]; - Buffer->Transformation = AllJoints[i]->GlobalAnimatedMatrix; - } - } - - // 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); - } - } -} - -void CSkinnedMesh::updateBoundingBox(void) -{ - if (!SkinningBuffers) - return; - - core::array &buffer = *SkinningBuffers; - BoundingBox.reset(0, 0, 0); - - if (!buffer.empty()) { - for (u32 j = 0; j < buffer.size(); ++j) { - buffer[j]->recalculateBoundingBox(); - core::aabbox3df bb = buffer[j]->BoundingBox; - buffer[j]->Transformation.transformBoxEx(bb); - - BoundingBox.addInternalBox(bb); - } - } -} - -scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer() -{ - scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer(); - TextureSlots.push_back(LocalBuffers.size()); - LocalBuffers.push_back(buffer); - return buffer; -} - -void CSkinnedMesh::addMeshBuffer(SSkinMeshBuffer *meshbuf) -{ - TextureSlots.push_back(LocalBuffers.size()); - LocalBuffers.push_back(meshbuf); -} - -CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent) -{ - SJoint *joint = new SJoint; - - 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; -} - -CSkinnedMesh::SPositionKey *CSkinnedMesh::addPositionKey(SJoint *joint) -{ - if (!joint) - return 0; - - joint->PositionKeys.push_back(SPositionKey()); - return &joint->PositionKeys.getLast(); -} - -CSkinnedMesh::SScaleKey *CSkinnedMesh::addScaleKey(SJoint *joint) -{ - if (!joint) - return 0; - - joint->ScaleKeys.push_back(SScaleKey()); - return &joint->ScaleKeys.getLast(); -} - -CSkinnedMesh::SRotationKey *CSkinnedMesh::addRotationKey(SJoint *joint) -{ - if (!joint) - return 0; - - joint->RotationKeys.push_back(SRotationKey()); - return &joint->RotationKeys.getLast(); -} - -CSkinnedMesh::SWeight *CSkinnedMesh::addWeight(SJoint *joint) -{ - if (!joint) - return 0; - - joint->Weights.push_back(SWeight()); - return &joint->Weights.getLast(); -} - -bool CSkinnedMesh::isStatic() -{ - return !HasAnimation; -} - -void CSkinnedMesh::normalizeWeights() -{ - // note: unsure if weights ids are going to be used. - - // Normalise the weights on bones.... - - u32 i, j; - core::array> verticesTotalWeight; - - verticesTotalWeight.reallocate(LocalBuffers.size()); - for (i = 0; i < LocalBuffers.size(); ++i) { - verticesTotalWeight.push_back(core::array()); - verticesTotalWeight[i].set_used(LocalBuffers[i]->getVertexCount()); - } - - for (i = 0; i < verticesTotalWeight.size(); ++i) - for (j = 0; j < verticesTotalWeight[i].size(); ++j) - verticesTotalWeight[i][j] = 0; - - for (i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (j = 0; j < joint->Weights.size(); ++j) { - if (joint->Weights[j].strength <= 0) { // Check for invalid weights - joint->Weights.erase(j); - --j; - } else { - verticesTotalWeight[joint->Weights[j].buffer_id][joint->Weights[j].vertex_id] += joint->Weights[j].strength; - } - } - } - - for (i = 0; i < AllJoints.size(); ++i) { - SJoint *joint = AllJoints[i]; - for (j = 0; j < joint->Weights.size(); ++j) { - const f32 total = verticesTotalWeight[joint->Weights[j].buffer_id][joint->Weights[j].vertex_id]; - if (total != 0 && total != 1) - joint->Weights[j].strength /= total; - } - } -} - -void CSkinnedMesh::recoverJointsFromMesh(core::array &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->positionHint = joint->positionHint; - node->scaleHint = joint->scaleHint; - node->rotationHint = joint->rotationHint; - - node->updateAbsolutePosition(); - } -} - -void CSkinnedMesh::transferJointsToMesh(const core::array &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->positionHint = node->positionHint; - joint->scaleHint = node->scaleHint; - joint->rotationHint = node->rotationHint; - - joint->GlobalSkinningSpace = (node->getSkinningSpace() == EBSS_GLOBAL); - } - // Make sure we recalc the next frame - LastAnimatedFrame = -1; - SkinnedLastFrame = false; -} - -void CSkinnedMesh::transferOnlyJointsHintsToMesh(const core::array &jointChildSceneNodes) -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - const IBoneSceneNode *const node = jointChildSceneNodes[i]; - SJoint *joint = AllJoints[i]; - - joint->positionHint = node->positionHint; - joint->scaleHint = node->scaleHint; - joint->rotationHint = node->rotationHint; - } - SkinnedLastFrame = false; -} - -void CSkinnedMesh::addJoints(core::array &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 CSkinnedMesh::convertMeshToTangents() -{ - // now calculate tangents - for (u32 b = 0; b < LocalBuffers.size(); ++b) { - if (LocalBuffers[b]) { - LocalBuffers[b]->convertToTangents(); - - const s32 idxCnt = LocalBuffers[b]->getIndexCount(); - - u16 *idx = LocalBuffers[b]->getIndices(); - video::S3DVertexTangents *v = - (video::S3DVertexTangents *)LocalBuffers[b]->getVertices(); - - for (s32 i = 0; i < idxCnt; i += 3) { - calculateTangents( - v[idx[i + 0]].Normal, - v[idx[i + 0]].Tangent, - v[idx[i + 0]].Binormal, - v[idx[i + 0]].Pos, - v[idx[i + 1]].Pos, - v[idx[i + 2]].Pos, - v[idx[i + 0]].TCoords, - v[idx[i + 1]].TCoords, - v[idx[i + 2]].TCoords); - - calculateTangents( - v[idx[i + 1]].Normal, - v[idx[i + 1]].Tangent, - v[idx[i + 1]].Binormal, - v[idx[i + 1]].Pos, - v[idx[i + 2]].Pos, - v[idx[i + 0]].Pos, - v[idx[i + 1]].TCoords, - v[idx[i + 2]].TCoords, - v[idx[i + 0]].TCoords); - - calculateTangents( - v[idx[i + 2]].Normal, - v[idx[i + 2]].Tangent, - v[idx[i + 2]].Binormal, - v[idx[i + 2]].Pos, - v[idx[i + 0]].Pos, - v[idx[i + 1]].Pos, - v[idx[i + 2]].TCoords, - v[idx[i + 0]].TCoords, - v[idx[i + 1]].TCoords); - } - } - } -} - -void CSkinnedMesh::calculateTangents( - core::vector3df &normal, - core::vector3df &tangent, - core::vector3df &binormal, - const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, // vertices - const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3) // texture coords -{ - core::vector3df v1 = vt1 - vt2; - core::vector3df v2 = vt3 - vt1; - normal = v2.crossProduct(v1); - normal.normalize(); - - // binormal - - f32 deltaX1 = tc1.X - tc2.X; - f32 deltaX2 = tc3.X - tc1.X; - binormal = (v1 * deltaX2) - (v2 * deltaX1); - binormal.normalize(); - - // tangent - - f32 deltaY1 = tc1.Y - tc2.Y; - f32 deltaY2 = tc3.Y - tc1.Y; - tangent = (v1 * deltaY2) - (v2 * deltaY1); - tangent.normalize(); - - // adjust - - core::vector3df txb = tangent.crossProduct(binormal); - if (txb.dotProduct(normal) < 0.0f) { - tangent *= -1.0f; - binormal *= -1.0f; - } -} - -} // end namespace scene -} // end namespace irr diff --git a/irr/src/CSkinnedMesh.h b/irr/src/CSkinnedMesh.h deleted file mode 100644 index 576ae1217..000000000 --- a/irr/src/CSkinnedMesh.h +++ /dev/null @@ -1,223 +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 - -// New skinned mesh - -#pragma once - -#include "ISceneManager.h" -#include "ISkinnedMesh.h" -#include "SMeshBuffer.h" -#include "quaternion.h" - -namespace irr -{ -namespace scene -{ - -class IAnimatedMeshSceneNode; -class IBoneSceneNode; -class ISceneManager; - -class CSkinnedMesh : public ISkinnedMesh -{ -public: - //! constructor - CSkinnedMesh(); - - //! destructor - virtual ~CSkinnedMesh(); - - //! If the duration is 0, it is a static (=non animated) mesh. - f32 getMaxFrameNumber() const override; - - //! 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; - - //! 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; - - //! returns the animated mesh for the given frame - IMesh *getMesh(f32) override; - - //! Animates this mesh's joints based on frame input - //! blend: {0-old position, 1-New position} - void animateMesh(f32 frame, f32 blend) override; - - //! Preforms a software skin on this mesh based of joint positions - void skinMesh() override; - - //! returns amount of mesh buffers. - u32 getMeshBufferCount() const override; - - //! returns pointer to a mesh buffer - IMeshBuffer *getMeshBuffer(u32 nr) const override; - - //! 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; - - u32 getTextureSlot(u32 meshbufNr) const override; - - void setTextureSlot(u32 meshbufNr, u32 textureSlot); - - //! returns an axis aligned bounding box - const core::aabbox3d &getBoundingBox() const override; - - //! set user axis aligned bounding box - void setBoundingBox(const core::aabbox3df &box) override; - - //! set the hardware mapping hint, for driver - void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override; - - //! flags the meshbuffer as changed, reloads hardware buffers - void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override; - - //! Returns the type of the animated mesh. - E_ANIMATED_MESH_TYPE getMeshType() const override; - - //! Gets joint count. - u32 getJointCount() const override; - - //! Gets the name of a joint. - const std::optional &getJointName(u32 number) const override; - - //! Gets a joint number from its name - std::optional getJointNumber(const std::string &name) const override; - - //! uses animation from another mesh - bool useAnimationFrom(const ISkinnedMesh *mesh) override; - - //! Update Normals when Animating - //! False= Don't (default) - //! True = Update normals, slower - void updateNormalsWhenAnimating(bool on) override; - - //! Sets Interpolation Mode - void setInterpolationMode(E_INTERPOLATION_MODE mode) override; - - //! Convertes the mesh to contain tangent information - void convertMeshToTangents() override; - - //! Does the mesh have no animation - bool isStatic() override; - - //! (This feature is not implemented in irrlicht yet) - bool setHardwareSkinning(bool on) override; - - //! Refreshes vertex data cached in joints such as positions and normals - void refreshJointCache() override; - - //! Moves the mesh into static position. - void resetAnimation() override; - - // Interface for the mesh loaders (finalize should lock these functions, and they should have some prefix like loader_ - // these functions will use the needed arrays, set values, etc to help the loaders - - //! exposed for loaders to add mesh buffers - core::array &getMeshBuffers() override; - - //! alternative method for adding joints - core::array &getAllJoints() override; - - //! alternative method for adding joints - const core::array &getAllJoints() const override; - - //! loaders should call this after populating the mesh - void finalize() override; - - //! Adds a new meshbuffer to the mesh, access it as last one - SSkinMeshBuffer *addMeshBuffer() override; - - //! Adds a new meshbuffer to the mesh, access it as last one - void addMeshBuffer(SSkinMeshBuffer *meshbuf) override; - - //! Adds a new joint to the mesh, access it as last one - SJoint *addJoint(SJoint *parent = 0) override; - - //! Adds a new position key to the mesh, access it as last one - SPositionKey *addPositionKey(SJoint *joint) override; - //! Adds a new rotation key to the mesh, access it as last one - SRotationKey *addRotationKey(SJoint *joint) override; - //! Adds a new scale key to the mesh, access it as last one - SScaleKey *addScaleKey(SJoint *joint) override; - - //! Adds a new weight to the mesh, access it as last one - SWeight *addWeight(SJoint *joint) override; - - virtual void updateBoundingBox(void); - - //! Recovers the joints from the mesh - void recoverJointsFromMesh(core::array &jointChildSceneNodes); - - //! Tranfers the joint data to the mesh - void transferJointsToMesh(const core::array &jointChildSceneNodes); - - //! Tranfers the joint hints to the mesh - void transferOnlyJointsHintsToMesh(const core::array &jointChildSceneNodes); - - //! Creates an array of joints from this mesh as children of node - void addJoints(core::array &jointChildSceneNodes, - IAnimatedMeshSceneNode *node, - ISceneManager *smgr); - -private: - void checkForAnimation(); - - void normalizeWeights(); - - void buildAllLocalAnimatedMatrices(); - - void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0); - - void getFrameData(f32 frame, SJoint *Node, - core::vector3df &position, s32 &positionHint, - core::vector3df &scale, s32 &scaleHint, - core::quaternion &rotation, s32 &rotationHint); - - 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, - const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3); - - core::array *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers - - core::array LocalBuffers; - //! Mapping from meshbuffer number to bindable texture slot - std::vector TextureSlots; - - core::array AllJoints; - core::array RootJoints; - - // bool can't be used here because std::vector - // doesn't allow taking a reference to individual elements. - core::array> Vertices_Moved; - - core::aabbox3d BoundingBox; - - f32 EndFrame; - f32 FramesPerSecond; - - f32 LastAnimatedFrame; - bool SkinnedLastFrame; - - E_INTERPOLATION_MODE InterpolationMode : 8; - - bool HasAnimation; - bool PreparedForSkinning; - bool AnimateNormals; - bool HardwareSkinning; -}; - -} // end namespace scene -} // end namespace irr diff --git a/irr/src/CWGLManager.cpp b/irr/src/CWGLManager.cpp index 785524fbc..d0dc9bf6a 100644 --- a/irr/src/CWGLManager.cpp +++ b/irr/src/CWGLManager.cpp @@ -19,9 +19,6 @@ namespace video CWGLManager::CWGLManager() : PrimaryContext(SExposedVideoData(0)), PixelFormat(0), libHandle(NULL) { -#ifdef _DEBUG - setDebugName("CWGLManager"); -#endif memset(FunctionPointers, 0, sizeof(FunctionPointers)); } diff --git a/irr/src/CWriteFile.cpp b/irr/src/CWriteFile.cpp index 362b284ee..f6a7efd2b 100644 --- a/irr/src/CWriteFile.cpp +++ b/irr/src/CWriteFile.cpp @@ -13,10 +13,6 @@ namespace io CWriteFile::CWriteFile(const io::path &fileName, bool append) : Filename(fileName), FileSize(0) { -#ifdef _DEBUG - setDebugName("CWriteFile"); -#endif - openFile(append); } diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index 967fc367c..c09f2e481 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CXMeshFileLoader.h" +#include "SkinnedMesh.h" #include "os.h" #include "fast_atof.h" @@ -31,11 +32,7 @@ namespace scene CXMeshFileLoader::CXMeshFileLoader(scene::ISceneManager *smgr) : AnimatedMesh(0), Buffer(0), P(0), End(0), BinaryNumCount(0), Line(0), ErrorState(false), CurFrame(0), MajorVersion(0), MinorVersion(0), BinaryFormat(false), FloatSize(0) -{ -#ifdef _DEBUG - setDebugName("CXMeshFileLoader"); -#endif -} +{} //! returns true if the file maybe is able to be loaded by this class //! based on the file extension (e.g. ".bsp") @@ -57,10 +54,11 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file) u32 time = os::Timer::getRealTime(); #endif - AnimatedMesh = new CSkinnedMesh(); + AnimatedMesh = new SkinnedMeshBuilder(); + SkinnedMesh *res = nullptr; if (load(file)) { - AnimatedMesh->finalize(); + res = AnimatedMesh->finalize(); } else { AnimatedMesh->drop(); AnimatedMesh = 0; @@ -92,7 +90,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file) delete Meshes[i]; Meshes.clear(); - return AnimatedMesh; + return res; } bool CXMeshFileLoader::load(io::IReadFile *file) @@ -124,7 +122,7 @@ bool CXMeshFileLoader::load(io::IReadFile *file) if (!mesh->HasSkinning) { // Set up rigid animation if (mesh->AttachedJointID != -1) { - AnimatedMesh->getAllJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back(AnimatedMesh->getMeshBuffers().size() - 1); + AnimatedMesh->getAllJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back(AnimatedMesh->getMeshBufferCount() - 1); } } } @@ -203,8 +201,8 @@ bool CXMeshFileLoader::load(io::IReadFile *file) } for (u32 j = 0; j < mesh->WeightJoint.size(); ++j) { - ISkinnedMesh::SJoint *joint = AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]; - ISkinnedMesh::SWeight &weight = joint->Weights[mesh->WeightNum[j]]; + SkinnedMesh::SJoint *joint = AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]; + SkinnedMesh::SWeight &weight = joint->Weights[mesh->WeightNum[j]]; u32 id = weight.vertex_id; @@ -219,7 +217,7 @@ bool CXMeshFileLoader::load(io::IReadFile *file) weight.buffer_id = verticesLinkBuffer[id][0]; } else if (verticesLinkBuffer[id].size() != 0) { for (u32 k = 1; k < verticesLinkBuffer[id].size(); ++k) { - ISkinnedMesh::SWeight *WeightClone = AnimatedMesh->addWeight(joint); + SkinnedMesh::SWeight *WeightClone = AnimatedMesh->addWeight(joint); WeightClone->strength = weight.strength; WeightClone->vertex_id = verticesLinkIndex[id][k]; WeightClone->buffer_id = verticesLinkBuffer[id][k]; @@ -314,7 +312,7 @@ bool CXMeshFileLoader::load(io::IReadFile *file) } for (u32 j = 0; j < mesh->WeightJoint.size(); ++j) { - ISkinnedMesh::SWeight &weight = (AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]->Weights[mesh->WeightNum[j]]); + SkinnedMesh::SWeight &weight = (AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]->Weights[mesh->WeightNum[j]]); u32 id = weight.vertex_id; @@ -486,7 +484,7 @@ bool CXMeshFileLoader::parseDataObjectTemplate() return true; } -bool CXMeshFileLoader::parseDataObjectFrame(CSkinnedMesh::SJoint *Parent) +bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading frame", ELL_DEBUG); @@ -508,7 +506,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(CSkinnedMesh::SJoint *Parent) SET_ERR_AND_RETURN(); } - CSkinnedMesh::SJoint *joint = 0; + SkinnedMesh::SJoint *joint = 0; if (name.size()) { auto n = AnimatedMesh->getJointNumber(name.c_str()); @@ -947,7 +945,7 @@ bool CXMeshFileLoader::parseDataObjectSkinWeights(SXMesh &mesh) mesh.HasSkinning = true; auto n = AnimatedMesh->getJointNumber(TransformNodeName.c_str()); - CSkinnedMesh::SJoint *joint = n.has_value() ? AnimatedMesh->getAllJoints()[*n] : nullptr; + SkinnedMesh::SJoint *joint = n.has_value() ? AnimatedMesh->getAllJoints()[*n] : nullptr; if (!joint) { #ifdef _XREADER_DEBUG @@ -965,16 +963,17 @@ bool CXMeshFileLoader::parseDataObjectSkinWeights(SXMesh &mesh) u32 i; const u32 jointStart = joint->Weights.size(); - joint->Weights.reallocate(jointStart + nWeights); + joint->Weights.reserve(jointStart + nWeights); mesh.WeightJoint.reallocate(mesh.WeightJoint.size() + nWeights); mesh.WeightNum.reallocate(mesh.WeightNum.size() + nWeights); for (i = 0; i < nWeights; ++i) { mesh.WeightJoint.push_back(*n); - mesh.WeightNum.push_back(joint->Weights.size()); + mesh.WeightNum.push_back(joint->Weights.size()); // id of weight - CSkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(joint); + // Note: This adds a weight to joint->Weights + SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(joint); weight->buffer_id = 0; weight->vertex_id = readInt(); @@ -1358,7 +1357,7 @@ bool CXMeshFileLoader::parseDataObjectAnimation() // anim.closed = true; // anim.linearPositionQuality = true; - CSkinnedMesh::SJoint animationDump; + SkinnedMesh::SJoint animationDump; core::stringc FrameName; @@ -1400,7 +1399,7 @@ bool CXMeshFileLoader::parseDataObjectAnimation() #endif auto n = AnimatedMesh->getJointNumber(FrameName.c_str()); - CSkinnedMesh::SJoint *joint; + SkinnedMesh::SJoint *joint; if (n.has_value()) { joint = AnimatedMesh->getAllJoints()[*n]; } else { @@ -1411,27 +1410,14 @@ bool CXMeshFileLoader::parseDataObjectAnimation() joint->Name = FrameName.c_str(); } - joint->PositionKeys.reallocate(joint->PositionKeys.size() + animationDump.PositionKeys.size()); - for (u32 n = 0; n < animationDump.PositionKeys.size(); ++n) { - joint->PositionKeys.push_back(animationDump.PositionKeys[n]); - } - - joint->ScaleKeys.reallocate(joint->ScaleKeys.size() + animationDump.ScaleKeys.size()); - for (u32 n = 0; n < animationDump.ScaleKeys.size(); ++n) { - joint->ScaleKeys.push_back(animationDump.ScaleKeys[n]); - } - - joint->RotationKeys.reallocate(joint->RotationKeys.size() + animationDump.RotationKeys.size()); - for (u32 n = 0; n < animationDump.RotationKeys.size(); ++n) { - joint->RotationKeys.push_back(animationDump.RotationKeys[n]); - } + joint->keys.append(animationDump.keys); } else os::Printer::log("joint name was never given", ELL_WARNING); return true; } -bool CXMeshFileLoader::parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint) +bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading animation key", ELL_DEBUG); @@ -1488,10 +1474,9 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint) os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } - ISkinnedMesh::SRotationKey *key = AnimatedMesh->addRotationKey(joint); - key->frame = time; - key->rotation.set(X, Y, Z, W); - key->rotation.normalize(); + core::quaternion rotation(X, Y, Z, W); + rotation.normalize(); + AnimatedMesh->addRotationKey(joint, time, rotation); } break; case 1: // scale case 2: // position @@ -1514,13 +1499,9 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint) } if (keyType == 2) { - ISkinnedMesh::SPositionKey *key = AnimatedMesh->addPositionKey(joint); - key->frame = time; - key->position = vector; + AnimatedMesh->addPositionKey(joint, time, vector); } else { - ISkinnedMesh::SScaleKey *key = AnimatedMesh->addScaleKey(joint); - key->frame = time; - key->scale = vector; + AnimatedMesh->addScaleKey(joint, time, vector); } } break; case 3: @@ -1547,16 +1528,8 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint) // core::vector3df rotation = mat.getRotationDegrees(); - ISkinnedMesh::SRotationKey *keyR = AnimatedMesh->addRotationKey(joint); - keyR->frame = time; - - // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched from mat to mat.getTransposed() for downward compatibility. - // Not tested so far if this was correct or wrong before quaternion fix! - keyR->rotation = core::quaternion(mat.getTransposed()); - - ISkinnedMesh::SPositionKey *keyP = AnimatedMesh->addPositionKey(joint); - keyP->frame = time; - keyP->position = mat.getTranslation(); + AnimatedMesh->addRotationKey(joint, time, core::quaternion(mat.getTransposed())); + AnimatedMesh->addPositionKey(joint, time, mat.getTranslation()); /* core::vector3df scale=mat.getScale(); @@ -1567,7 +1540,7 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint) scale.Y=1; if (scale.Z==0) scale.Z=1; - ISkinnedMesh::SScaleKey *keyS=AnimatedMesh->addScaleKey(joint); + SkinnedMesh::SScaleKey *keyS=AnimatedMesh->addScaleKey(joint); keyS->frame=time; keyS->scale=scale; */ diff --git a/irr/src/CXMeshFileLoader.h b/irr/src/CXMeshFileLoader.h index 711806270..36610d3b3 100644 --- a/irr/src/CXMeshFileLoader.h +++ b/irr/src/CXMeshFileLoader.h @@ -6,7 +6,7 @@ #include "IMeshLoader.h" #include "irrString.h" -#include "CSkinnedMesh.h" +#include "SkinnedMesh.h" namespace irr { @@ -86,7 +86,7 @@ private: bool parseDataObjectTemplate(); - bool parseDataObjectFrame(CSkinnedMesh::SJoint *parent); + bool parseDataObjectFrame(SkinnedMesh::SJoint *parent); bool parseDataObjectTransformationMatrix(core::matrix4 &mat); @@ -110,7 +110,7 @@ private: bool parseDataObjectAnimation(); - bool parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint); + bool parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint); bool parseDataObjectTextureFilename(core::stringc &texturename); @@ -155,7 +155,7 @@ private: bool readRGB(video::SColor &color); bool readRGBA(video::SColor &color); - CSkinnedMesh *AnimatedMesh; + SkinnedMeshBuilder *AnimatedMesh; c8 *Buffer; const c8 *P; @@ -167,7 +167,7 @@ private: bool ErrorState; - CSkinnedMesh::SJoint *CurFrame; + SkinnedMesh::SJoint *CurFrame; core::array Meshes; diff --git a/irr/src/CZipReader.cpp b/irr/src/CZipReader.cpp index 036f6302a..dc5c0a4f0 100644 --- a/irr/src/CZipReader.cpp +++ b/irr/src/CZipReader.cpp @@ -24,11 +24,7 @@ namespace io //! Constructor CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem *fs) : FileSystem(fs) -{ -#ifdef _DEBUG - setDebugName("CArchiveLoaderZIP"); -#endif -} +{} //! returns true if the file maybe is able to be loaded by this class bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path &filename) const @@ -107,10 +103,6 @@ bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile *file) const CZipReader::CZipReader(IFileSystem *fs, IReadFile *file, bool ignoreCase, bool ignorePaths, bool isGZip) : CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file), IsGZip(isGZip) { -#ifdef _DEBUG - setDebugName("CZipReader"); -#endif - if (File) { File->grab(); diff --git a/irr/src/OpenGL/Common.h b/irr/src/OpenGL/Common.h index 84b2ec3e4..75c213bb0 100644 --- a/irr/src/OpenGL/Common.h +++ b/irr/src/OpenGL/Common.h @@ -40,7 +40,7 @@ typedef COpenGLCoreTexture COpenGL3Texture; typedef COpenGLCoreRenderTarget COpenGL3RenderTarget; typedef COpenGLCoreCacheHandler COpenGL3CacheHandler; -enum class OpenGLSpec : u8 +enum OpenGLSpec : u8 { Core, Compat, diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 4b85b1345..5df87861d 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -27,37 +27,32 @@ namespace irr { namespace video { + struct VertexAttribute { - enum class Mode + enum Mode : u8 { Regular, Normalized, - Integral, + Integer, }; - int Index; - int ComponentCount; + u8 Index; + u8 ComponentCount; GLenum ComponentType; Mode mode; - int Offset; + u32 Offset; }; struct VertexType { - int VertexSize; + u32 VertexSize; std::vector Attributes; + + // allow ranged for loops + inline auto begin() const { return Attributes.begin(); } + inline auto end() const { return Attributes.end(); } }; -static const VertexAttribute *begin(const VertexType &type) -{ - return type.Attributes.data(); -} - -static const VertexAttribute *end(const VertexType &type) -{ - return type.Attributes.data() + type.Attributes.size(); -} - static const VertexType vtStandard = { sizeof(S3DVertex), { @@ -68,6 +63,9 @@ static const VertexType vtStandard = { }, }; +// FIXME: this is actually UB because these vertex classes are not "standard-layout" +// they violate the following requirement: +// - only one class in the hierarchy has non-static data members #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Winvalid-offsetof" @@ -157,10 +155,6 @@ COpenGL3DriverBase::COpenGL3DriverBase(const SIrrlichtCreationParameters ¶ms OGLES2ShaderPath(params.OGLES2ShaderPath), ColorFormat(ECF_R8G8B8), ContextManager(contextManager), EnableErrorTest(params.DriverDebug) { -#ifdef _DEBUG - setDebugName("Driver"); -#endif - if (!ContextManager) return; @@ -170,11 +164,15 @@ COpenGL3DriverBase::COpenGL3DriverBase(const SIrrlichtCreationParameters ¶ms ExposedData = ContextManager->getContext(); ContextManager->activateContext(ExposedData, false); GL.LoadAllProcedures(ContextManager); - if (EnableErrorTest) { + if (EnableErrorTest && GL.IsExtensionPresent("GL_KHR_debug")) { GL.Enable(GL_DEBUG_OUTPUT); GL.DebugMessageCallback(debugCb, this); + } else if (EnableErrorTest) { + os::Printer::log("GL debug extension not available"); } initQuadsIndices(); + + TEST_GL_ERROR(this); } COpenGL3DriverBase::~COpenGL3DriverBase() @@ -267,7 +265,6 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS for (s32 i = 0; i < ETS_COUNT; ++i) setTransform(static_cast(i), core::IdentityMatrix); - setAmbientLight(SColorf(0.0f, 0.0f, 0.0f, 0.0f)); GL.ClearDepthf(1.0f); GL.Hint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); @@ -675,6 +672,29 @@ IRenderTarget *COpenGL3DriverBase::addRenderTarget() return renderTarget; } +void COpenGL3DriverBase::blitRenderTarget(IRenderTarget *from, IRenderTarget *to) +{ + if (Version.Spec == OpenGLSpec::ES && Version.Major < 3) { + os::Printer::log("glBlitFramebuffer not supported by OpenGL ES < 3.0", ELL_ERROR); + return; + } + + GLuint prev_fbo_id; + CacheHandler->getFBO(prev_fbo_id); + + COpenGL3RenderTarget *src = static_cast(from); + COpenGL3RenderTarget *dst = static_cast(to); + GL.BindFramebuffer(GL.READ_FRAMEBUFFER, src->getBufferID()); + GL.BindFramebuffer(GL.DRAW_FRAMEBUFFER, dst->getBufferID()); + GL.BlitFramebuffer( + 0, 0, src->getSize().Width, src->getSize().Height, + 0, 0, dst->getSize().Width, dst->getSize().Height, + GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, GL.NEAREST); + + // This resets both read and draw framebuffer. Note that we bypass CacheHandler here. + GL.BindFramebuffer(GL.FRAMEBUFFER, prev_fbo_id); +} + //! draws a vertex primitive list void COpenGL3DriverBase::drawVertexPrimitiveList(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, @@ -737,6 +757,13 @@ void COpenGL3DriverBase::drawVertexPrimitiveList(const void *vertices, u32 verte endDraw(vTypeDesc); } +void COpenGL3DriverBase::draw2DVertexPrimitiveList(const void *vertices, u32 vertexCount, + const void *indexList, u32 primitiveCount, + E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) +{ + os::Printer::log("draw2DVertexPrimitiveList unimplemented", ELL_ERROR); +} + void COpenGL3DriverBase::draw2DImage(const video::ITexture *texture, const core::position2d &destPos, const core::rect &sourceRect, const core::rect *clipRect, SColor color, bool useAlphaChannelOfTexture) @@ -1043,7 +1070,7 @@ void COpenGL3DriverBase::drawElements(GLenum primitiveType, const VertexType &ve void COpenGL3DriverBase::beginDraw(const VertexType &vertexType, uintptr_t verticesBase) { - for (auto attr : vertexType) { + for (auto &attr : vertexType) { GL.EnableVertexAttribArray(attr.Index); switch (attr.mode) { case VertexAttribute::Mode::Regular: @@ -1052,7 +1079,7 @@ void COpenGL3DriverBase::beginDraw(const VertexType &vertexType, uintptr_t verti case VertexAttribute::Mode::Normalized: GL.VertexAttribPointer(attr.Index, attr.ComponentCount, attr.ComponentType, GL_TRUE, vertexType.VertexSize, reinterpret_cast(verticesBase + attr.Offset)); break; - case VertexAttribute::Mode::Integral: + case VertexAttribute::Mode::Integer: GL.VertexAttribIPointer(attr.Index, attr.ComponentCount, attr.ComponentType, vertexType.VertexSize, reinterpret_cast(verticesBase + attr.Offset)); break; } @@ -1061,7 +1088,7 @@ void COpenGL3DriverBase::beginDraw(const VertexType &vertexType, uintptr_t verti void COpenGL3DriverBase::endDraw(const VertexType &vertexType) { - for (auto attr : vertexType) + for (auto &attr : vertexType) GL.DisableVertexAttribArray(attr.Index); } @@ -1317,6 +1344,8 @@ void COpenGL3DriverBase::setBasicRenderStates(const SMaterial &material, const S GL.LineWidth(core::clamp(static_cast(material.Thickness), DimAliasedLine[0], DimAliasedLine[1])); // Anti aliasing + // Deal with MSAA even if it's not enabled in the OpenGL context, we might be + // rendering to an FBO with multisampling. if (resetAllRenderStates || lastmaterial.AntiAliasing != material.AntiAliasing) { if (material.AntiAliasing & EAAM_ALPHA_TO_COVERAGE) GL.Enable(GL_SAMPLE_ALPHA_TO_COVERAGE); @@ -1631,12 +1660,18 @@ IGPUProgrammingServices *COpenGL3DriverBase::getGPUProgrammingServices() ITexture *COpenGL3DriverBase::addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format) +{ + return addRenderTargetTextureMs(size, 0, name, format); +} + +ITexture *COpenGL3DriverBase::addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format) { // disable mip-mapping bool generateMipLevels = getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); - COpenGL3Texture *renderTargetTexture = new COpenGL3Texture(name, size, ETT_2D, format, this); + COpenGL3Texture *renderTargetTexture = new COpenGL3Texture(name, size, msaa > 0 ? ETT_2D_MS : ETT_2D, format, this, msaa); addTexture(renderTargetTexture); renderTargetTexture->drop(); diff --git a/irr/src/OpenGL/Driver.h b/irr/src/OpenGL/Driver.h index 3104b164d..e0787e560 100644 --- a/irr/src/OpenGL/Driver.h +++ b/irr/src/OpenGL/Driver.h @@ -74,11 +74,18 @@ public: IRenderTarget *addRenderTarget() override; + void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override; + //! draws a vertex primitive list virtual void drawVertexPrimitiveList(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) override; + //! draws a vertex primitive list in 2d + virtual void draw2DVertexPrimitiveList(const void *vertices, u32 vertexCount, + const void *indexList, u32 primitiveCount, + E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) override; + //! queries the features of the driver, returns true if feature is available bool queryFeature(E_VIDEO_DRIVER_FEATURE feature) const override { @@ -209,6 +216,9 @@ public: virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a render target texture for a cubemap ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) override; diff --git a/irr/src/OpenGL/ExtensionHandler.h b/irr/src/OpenGL/ExtensionHandler.h index 8f76c0eea..403b828b3 100644 --- a/irr/src/OpenGL/ExtensionHandler.h +++ b/irr/src/OpenGL/ExtensionHandler.h @@ -74,6 +74,8 @@ public: return false; case EVDF_STENCIL_BUFFER: return StencilBuffer; + case EVDF_TEXTURE_MULTISAMPLE: + return TextureMultisampleSupported; default: return false; }; @@ -161,6 +163,7 @@ public: bool AnisotropicFilterSupported = false; bool BlendMinMaxSupported = false; + bool TextureMultisampleSupported = false; }; } diff --git a/irr/src/OpenGL/MaterialRenderer.cpp b/irr/src/OpenGL/MaterialRenderer.cpp index d5bf9004a..7439dba61 100644 --- a/irr/src/OpenGL/MaterialRenderer.cpp +++ b/irr/src/OpenGL/MaterialRenderer.cpp @@ -30,10 +30,6 @@ COpenGL3MaterialRenderer::COpenGL3MaterialRenderer(COpenGL3DriverBase *driver, Driver(driver), CallBack(callback), Alpha(false), Blending(false), Program(0), UserData(userData) { -#ifdef _DEBUG - setDebugName("MaterialRenderer"); -#endif - switch (baseMaterial) { case EMT_TRANSPARENT_VERTEX_ALPHA: case EMT_TRANSPARENT_ALPHA_CHANNEL: diff --git a/irr/src/OpenGL/Renderer2D.cpp b/irr/src/OpenGL/Renderer2D.cpp index b625feda4..1dd717aa8 100644 --- a/irr/src/OpenGL/Renderer2D.cpp +++ b/irr/src/OpenGL/Renderer2D.cpp @@ -22,10 +22,6 @@ COpenGL3Renderer2D::COpenGL3Renderer2D(const c8 *vertexShaderProgram, const c8 * COpenGL3MaterialRenderer(driver, 0, EMT_SOLID), WithTexture(withTexture) { -#ifdef _DEBUG - setDebugName("Renderer2D"); -#endif - int Temp = 0; init(Temp, vertexShaderProgram, pixelShaderProgram, false); diff --git a/irr/src/OpenGL3/Driver.cpp b/irr/src/OpenGL3/Driver.cpp index ac0816445..5277c4dde 100644 --- a/irr/src/OpenGL3/Driver.cpp +++ b/irr/src/OpenGL3/Driver.cpp @@ -62,6 +62,7 @@ void COpenGL3Driver::initFeatures() TextureFormats[ECF_R8G8] = {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}; TextureFormats[ECF_R16] = {GL_R16, GL_RED, GL_UNSIGNED_SHORT}; TextureFormats[ECF_R16G16] = {GL_RG16, GL_RG, GL_UNSIGNED_SHORT}; + TextureFormats[ECF_A2R10G10B10] = {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}; TextureFormats[ECF_D16] = {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; TextureFormats[ECF_D24] = {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}; TextureFormats[ECF_D32] = {GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}; // WARNING: may not be renderable (?!) @@ -69,6 +70,7 @@ void COpenGL3Driver::initFeatures() AnisotropicFilterSupported = isVersionAtLeast(4, 6) || queryExtension("GL_ARB_texture_filter_anisotropic") || queryExtension("GL_EXT_texture_filter_anisotropic"); BlendMinMaxSupported = true; + TextureMultisampleSupported = true; // COGLESCoreExtensionHandler::Feature static_assert(MATERIAL_MAX_TEXTURES <= 16, "Only up to 16 textures are guaranteed"); diff --git a/irr/src/OpenGLES2/Driver.cpp b/irr/src/OpenGLES2/Driver.cpp index 430be736d..7b84675aa 100644 --- a/irr/src/OpenGLES2/Driver.cpp +++ b/irr/src/OpenGLES2/Driver.cpp @@ -43,8 +43,6 @@ void COpenGLES2Driver::initFeatures() } initExtensions(); - static const GLenum BGRA8_EXT = 0x93A1; - if (Version.Major >= 3) { // NOTE floating-point formats may not be suitable for render targets. TextureFormats[ECF_A1R5G5B5] = {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, CColorConverter::convert_A1R5G5B5toR5G5B5A1}; @@ -59,14 +57,16 @@ void COpenGLES2Driver::initFeatures() TextureFormats[ECF_A32B32G32R32F] = {GL_RGBA32F, GL_RGBA, GL_FLOAT}; TextureFormats[ECF_R8] = {GL_R8, GL_RED, GL_UNSIGNED_BYTE}; TextureFormats[ECF_R8G8] = {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}; + TextureFormats[ECF_A2R10G10B10] = {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}; TextureFormats[ECF_D16] = {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; TextureFormats[ECF_D24] = {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}; TextureFormats[ECF_D24S8] = {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}; - if (FeatureAvailable[IRR_GL_EXT_texture_format_BGRA8888]) + // NOTE a recent (2024) revision of EXT_texture_format_BGRA8888 also + // adds a sized format GL_BGRA8_EXT. We have a workaround in place to + // fix up the InternalFormat in case of render targets. + if (FeatureAvailable[IRR_GL_EXT_texture_format_BGRA8888] || FeatureAvailable[IRR_GL_APPLE_texture_format_BGRA8888]) TextureFormats[ECF_A8R8G8B8] = {GL_BGRA, GL_BGRA, GL_UNSIGNED_BYTE}; - else if (FeatureAvailable[IRR_GL_APPLE_texture_format_BGRA8888]) - TextureFormats[ECF_A8R8G8B8] = {BGRA8_EXT, GL_BGRA, GL_UNSIGNED_BYTE}; // OpenGL ES 3 doesn't include a GL_DEPTH_COMPONENT32, so still use // OES_depth_texture for 32-bit depth texture support. @@ -86,10 +86,8 @@ void COpenGLES2Driver::initFeatures() TextureFormats[ECF_R8G8B8] = {GL_RGB, GL_RGB, GL_UNSIGNED_BYTE}; TextureFormats[ECF_A8R8G8B8] = {GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, CColorConverter::convert_A8R8G8B8toA8B8G8R8}; - if (FeatureAvailable[IRR_GL_EXT_texture_format_BGRA8888]) + if (FeatureAvailable[IRR_GL_EXT_texture_format_BGRA8888] || FeatureAvailable[IRR_GL_APPLE_texture_format_BGRA8888]) TextureFormats[ECF_A8R8G8B8] = {GL_BGRA, GL_BGRA, GL_UNSIGNED_BYTE}; - else if (FeatureAvailable[IRR_GL_APPLE_texture_format_BGRA8888]) - TextureFormats[ECF_A8R8G8B8] = {BGRA8_EXT, GL_BGRA, GL_UNSIGNED_BYTE}; if (FeatureAvailable[IRR_GL_OES_texture_half_float]) { TextureFormats[ECF_A16B16G16R16F] = {GL_RGBA, GL_RGBA, HALF_FLOAT_OES}; @@ -124,6 +122,7 @@ void COpenGLES2Driver::initFeatures() const bool MRTSupported = Version.Major >= 3 || queryExtension("GL_EXT_draw_buffers"); AnisotropicFilterSupported = queryExtension("GL_EXT_texture_filter_anisotropic"); BlendMinMaxSupported = (Version.Major >= 3) || FeatureAvailable[IRR_GL_EXT_blend_minmax]; + TextureMultisampleSupported = isVersionAtLeast(3, 1); const bool TextureLODBiasSupported = queryExtension("GL_EXT_texture_lod_bias"); // COGLESCoreExtensionHandler::Feature diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp new file mode 100644 index 000000000..64b25205e --- /dev/null +++ b/irr/src/SkinnedMesh.cpp @@ -0,0 +1,840 @@ +// 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 "SkinnedMesh.h" +#include "IBoneSceneNode.h" +#include "CBoneSceneNode.h" +#include "IAnimatedMeshSceneNode.h" +#include "SSkinMeshBuffer.h" +#include "os.h" +#include + +namespace irr +{ +namespace scene +{ + +//! destructor +SkinnedMesh::~SkinnedMesh() +{ + for (auto *joint : AllJoints) + delete joint; + + for (auto *buffer : LocalBuffers) { + if (buffer) + buffer->drop(); + } +} + +f32 SkinnedMesh::getMaxFrameNumber() const +{ + return EndFrame; +} + +//! 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 SkinnedMesh::getAnimationSpeed() const +{ + 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 SkinnedMesh::setAnimationSpeed(f32 fps) +{ + FramesPerSecond = fps; +} + +//! returns the animated mesh based +IMesh *SkinnedMesh::getMesh(f32 frame) +{ + // animate(frame,startFrameLoop, endFrameLoop); + if (frame == -1) + return this; + + animateMesh(frame); + skinMesh(); + return this; +} + +//-------------------------------------------------------------------------- +// Keyframe Animation +//-------------------------------------------------------------------------- + +//! Animates joints based on frame input +void SkinnedMesh::animateMesh(f32 frame) +{ + if (!HasAnimation || LastAnimatedFrame == frame) + return; + + LastAnimatedFrame = frame; + SkinnedLastFrame = false; + + for (auto *joint : AllJoints) { + // The joints can be animated here with no input from their + // parents, but for setAnimationMode extra checks are needed + // to their parents + joint->keys.updateTransform(frame, + joint->Animatedposition, + joint->Animatedrotation, + joint->Animatedscale); + } + + // Note: + // LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for + // one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once. + // a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move + + //---------------- + // Temp! + buildAllLocalAnimatedMatrices(); + //----------------- + + updateBoundingBox(); +} + +void SkinnedMesh::buildAllLocalAnimatedMatrices() +{ + for (auto *joint : AllJoints) { + // Could be faster: + + if (!joint->keys.empty()) { + joint->GlobalSkinningSpace = false; + + // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility. + // Not tested so far if this was correct or wrong before quaternion fix! + // Note that using getMatrix_transposed inverts the rotation. + joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix); + + // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() --- + f32 *m1 = joint->LocalAnimatedMatrix.pointer(); + core::vector3df &Pos = joint->Animatedposition; + m1[0] += Pos.X * m1[3]; + m1[1] += Pos.Y * m1[3]; + m1[2] += Pos.Z * m1[3]; + m1[4] += Pos.X * m1[7]; + m1[5] += Pos.Y * m1[7]; + m1[6] += Pos.Z * m1[7]; + m1[8] += Pos.X * m1[11]; + m1[9] += Pos.Y * m1[11]; + m1[10] += Pos.Z * m1[11]; + m1[12] += Pos.X * m1[15]; + m1[13] += Pos.Y * m1[15]; + m1[14] += Pos.Z * m1[15]; + // ----------------------------------- + + if (!joint->keys.scale.empty()) { + /* + core::matrix4 scaleMatrix; + scaleMatrix.setScale(joint->Animatedscale); + joint->LocalAnimatedMatrix *= scaleMatrix; + */ + + // -------- joint->LocalAnimatedMatrix *= scaleMatrix ----------------- + core::matrix4 &mat = joint->LocalAnimatedMatrix; + mat[0] *= joint->Animatedscale.X; + mat[1] *= joint->Animatedscale.X; + mat[2] *= joint->Animatedscale.X; + mat[3] *= joint->Animatedscale.X; + mat[4] *= joint->Animatedscale.Y; + mat[5] *= joint->Animatedscale.Y; + mat[6] *= joint->Animatedscale.Y; + mat[7] *= joint->Animatedscale.Y; + mat[8] *= joint->Animatedscale.Z; + mat[9] *= joint->Animatedscale.Z; + mat[10] *= joint->Animatedscale.Z; + mat[11] *= joint->Animatedscale.Z; + // ----------------------------------- + } + } else { + joint->LocalAnimatedMatrix = joint->LocalMatrix; + } + } + SkinnedLastFrame = false; +} + +void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint) +{ + if (!joint) { + for (auto *rootJoint : RootJoints) + buildAllGlobalAnimatedMatrices(rootJoint, 0); + return; + } else { + // Find global matrix... + if (!parentJoint || joint->GlobalSkinningSpace) + joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix; + else + joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix; + } + + for (auto *childJoint : joint->Children) + buildAllGlobalAnimatedMatrices(childJoint, joint); +} + +//-------------------------------------------------------------------------- +// Software Skinning +//-------------------------------------------------------------------------- + +//! Preforms a software skin on this mesh based of joint positions +void SkinnedMesh::skinMesh() +{ + if (!HasAnimation || SkinnedLastFrame) + return; + + //---------------- + // This is marked as "Temp!". A shiny dubloon to whomever can tell me why. + buildAllGlobalAnimatedMatrices(); + //----------------- + + SkinnedLastFrame = true; + if (!HardwareSkinning) { + // rigid animation + for (auto *joint : AllJoints) { + for (u32 attachedMeshIdx : joint->AttachedMeshes) { + SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; + Buffer->Transformation = joint->GlobalAnimatedMatrix; + } + } + + // 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... + // Note: It is assumed that the global inversed matrix has been calculated at this point. + core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value(); + + core::vector3df thisVertexMove, thisNormalMove; + + auto &buffersUsed = *SkinningBuffers; + + // Skin Vertices Positions and Normals... + for (const auto &weight : joint->Weights) { + // Pull this vertex... + jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); + + if (AnimateNormals) { + thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal); + thisNormalMove.normalize(); // must renormalize after potentially scaling + } + + if (!(*(weight.Moved))) { + *(weight.Moved) = true; + + buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength; + + if (AnimateNormals) + buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength; + + //*(weight._Pos) = thisVertexMove * weight.strength; + } else { + buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength; + + if (AnimateNormals) + buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength; + + //*(weight._Pos) += thisVertexMove * weight.strength; + } + + buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated(); + } + } + + // Skin all children + for (auto *childJoint : joint->Children) + skinJoint(childJoint, joint); +} + +//! Gets joint count. +u32 SkinnedMesh::getJointCount() const +{ + return AllJoints.size(); +} + +//! Gets the name of a joint. +const std::optional &SkinnedMesh::getJointName(u32 number) const +{ + if (number >= getJointCount()) { + static const std::optional nullopt; + return nullopt; + } + return AllJoints[number]->Name; +} + +//! Gets a joint number from its name +std::optional SkinnedMesh::getJointNumber(const std::string &name) const +{ + for (u32 i = 0; i < AllJoints.size(); ++i) { + if (AllJoints[i]->Name == name) + return i; + } + + return std::nullopt; +} + +//! returns amount of mesh buffers. +u32 SkinnedMesh::getMeshBufferCount() const +{ + return LocalBuffers.size(); +} + +//! returns pointer to a mesh buffer +IMeshBuffer *SkinnedMesh::getMeshBuffer(u32 nr) const +{ + if (nr < LocalBuffers.size()) + return LocalBuffers[nr]; + else + return 0; +} + +//! Returns pointer to a mesh buffer which fits a material +IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const +{ + for (u32 i = 0; i < LocalBuffers.size(); ++i) { + if (LocalBuffers[i]->getMaterial() == material) + return LocalBuffers[i]; + } + return 0; +} + +u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const +{ + return TextureSlots.at(meshbufNr); +} + +void SkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) { + TextureSlots.at(meshbufNr) = textureSlot; +} + +//! set the hardware mapping hint, for driver +void SkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, + E_BUFFER_TYPE buffer) +{ + for (u32 i = 0; i < LocalBuffers.size(); ++i) + LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer); +} + +//! flags the meshbuffer as changed, reloads hardware buffers +void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer) +{ + for (u32 i = 0; i < LocalBuffers.size(); ++i) + 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... + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; + weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; + } + } +} + +void SkinnedMesh::resetAnimation() +{ + // copy from the cache to the mesh... + 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; + } + } + SkinnedLastFrame = false; + LastAnimatedFrame = -1; +} + +void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint) +{ + 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; + } + + 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() +{ + // Check for animation... + HasAnimation = false; + for (auto *joint : AllJoints) { + if (!joint->keys.empty()) { + HasAnimation = true; + break; + } + } + + // meshes with weights, are still counted as animated for ragdolls, etc + if (!HasAnimation) { + for (auto *joint : AllJoints) { + if (joint->Weights.size()) { + HasAnimation = true; + break; + } + } + } + + if (HasAnimation) { + EndFrame = 0.0f; + for (const auto *joint : AllJoints) { + EndFrame = std::max(EndFrame, joint->keys.getEndFrame()); + } + } + + if (HasAnimation && !PreparedForSkinning) { + PreparedForSkinning = true; + + // check for bugs: + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + 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; + } + } + } + + // 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; + } + } + + // normalize weights + normalizeWeights(); + } + SkinnedLastFrame = false; +} + +//! called by loader after populating with mesh and bone data +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... + + for (u32 i = 0; i < LocalBuffers.size(); ++i) { + Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount()); + } + + checkForAnimation(); + + if (HasAnimation) { + for (auto *joint : AllJoints) { + joint->keys.cleanup(); + } + } + + // Needed for animation and skinning... + + calculateGlobalMatrices(0, 0); + + // rigid animation for non animated meshes + for (auto *joint : AllJoints) { + for (u32 attachedMeshIdx : joint->AttachedMeshes) { + SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; + Buffer->Transformation = joint->GlobalAnimatedMatrix; + } + } + + // 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); + } + } + + return this; +} + +void SkinnedMesh::updateBoundingBox(void) +{ + 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(); + TextureSlots.push_back(LocalBuffers.size()); + LocalBuffers.push_back(buffer); + return buffer; +} + +void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf) +{ + TextureSlots.push_back(LocalBuffers.size()); + LocalBuffers.push_back(meshbuf); +} + +SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent) +{ + SJoint *joint = new SJoint; + + 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; +} + +void SkinnedMeshBuilder::addPositionKey(SJoint *joint, f32 frame, core::vector3df pos) +{ + _IRR_DEBUG_BREAK_IF(!joint); + joint->keys.position.pushBack(frame, pos); +} + +void SkinnedMeshBuilder::addScaleKey(SJoint *joint, f32 frame, core::vector3df scale) +{ + _IRR_DEBUG_BREAK_IF(!joint); + joint->keys.scale.pushBack(frame, scale); +} + +void SkinnedMeshBuilder::addRotationKey(SJoint *joint, f32 frame, core::quaternion rot) +{ + _IRR_DEBUG_BREAK_IF(!joint); + joint->keys.rotation.pushBack(frame, rot); +} + +SkinnedMesh::SWeight *SkinnedMeshBuilder::addWeight(SJoint *joint) +{ + if (!joint) + return nullptr; + + joint->Weights.emplace_back(); + return &joint->Weights.back(); +} + +void SkinnedMesh::normalizeWeights() +{ + // note: unsure if weights ids are going to be used. + + // Normalise the weights on bones.... + + std::vector> verticesTotalWeight; + + verticesTotalWeight.reserve(LocalBuffers.size()); + for (u32 i = 0; i < LocalBuffers.size(); ++i) { + verticesTotalWeight.emplace_back(LocalBuffers[i]->getVertexCount()); + } + + for (u32 i = 0; i < verticesTotalWeight.size(); ++i) + for (u32 j = 0; j < verticesTotalWeight[i].size(); ++j) + verticesTotalWeight[i][j] = 0; + + for (auto *joint : AllJoints) { + auto &weights = joint->Weights; + + weights.erase(std::remove_if(weights.begin(), weights.end(), [](const auto &weight) { + return weight.strength <= 0; + }), weights.end()); + + for (const auto &weight : weights) { + verticesTotalWeight[weight.buffer_id][weight.vertex_id] += weight.strength; + } + } + + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const f32 total = verticesTotalWeight[weight.buffer_id][weight.vertex_id]; + if (total != 0 && total != 1) + weight.strength /= total; + } + } +} + +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 + for (u32 b = 0; b < LocalBuffers.size(); ++b) { + if (LocalBuffers[b]) { + LocalBuffers[b]->convertToTangents(); + + const s32 idxCnt = LocalBuffers[b]->getIndexCount(); + + u16 *idx = LocalBuffers[b]->getIndices(); + video::S3DVertexTangents *v = + (video::S3DVertexTangents *)LocalBuffers[b]->getVertices(); + + for (s32 i = 0; i < idxCnt; i += 3) { + calculateTangents( + v[idx[i + 0]].Normal, + v[idx[i + 0]].Tangent, + v[idx[i + 0]].Binormal, + v[idx[i + 0]].Pos, + v[idx[i + 1]].Pos, + v[idx[i + 2]].Pos, + v[idx[i + 0]].TCoords, + v[idx[i + 1]].TCoords, + v[idx[i + 2]].TCoords); + + calculateTangents( + v[idx[i + 1]].Normal, + v[idx[i + 1]].Tangent, + v[idx[i + 1]].Binormal, + v[idx[i + 1]].Pos, + v[idx[i + 2]].Pos, + v[idx[i + 0]].Pos, + v[idx[i + 1]].TCoords, + v[idx[i + 2]].TCoords, + v[idx[i + 0]].TCoords); + + calculateTangents( + v[idx[i + 2]].Normal, + v[idx[i + 2]].Tangent, + v[idx[i + 2]].Binormal, + v[idx[i + 2]].Pos, + v[idx[i + 0]].Pos, + v[idx[i + 1]].Pos, + v[idx[i + 2]].TCoords, + v[idx[i + 0]].TCoords, + v[idx[i + 1]].TCoords); + } + } + } +} + +void SkinnedMesh::calculateTangents( + core::vector3df &normal, + core::vector3df &tangent, + core::vector3df &binormal, + const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, // vertices + const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3) // texture coords +{ + core::vector3df v1 = vt1 - vt2; + core::vector3df v2 = vt3 - vt1; + normal = v2.crossProduct(v1); + normal.normalize(); + + // binormal + + f32 deltaX1 = tc1.X - tc2.X; + f32 deltaX2 = tc3.X - tc1.X; + binormal = (v1 * deltaX2) - (v2 * deltaX1); + binormal.normalize(); + + // tangent + + f32 deltaY1 = tc1.Y - tc2.Y; + f32 deltaY2 = tc3.Y - tc1.Y; + tangent = (v1 * deltaY2) - (v2 * deltaY1); + tangent.normalize(); + + // adjust + + core::vector3df txb = tangent.crossProduct(binormal); + if (txb.dotProduct(normal) < 0.0f) { + tangent *= -1.0f; + binormal *= -1.0f; + } +} + +} // end namespace scene +} // end namespace irr diff --git a/irr/src/mt_opengl_loader.cpp b/irr/src/mt_opengl_loader.cpp index 084f83118..2b7184036 100755 --- a/irr/src/mt_opengl_loader.cpp +++ b/irr/src/mt_opengl_loader.cpp @@ -5,7 +5,7 @@ #include #include -OpenGLProcedures GL = OpenGLProcedures(); +OpenGLProcedures GL; void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr) { @@ -758,9 +758,11 @@ void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr) if (!NamedBufferPageCommitment) NamedBufferPageCommitment = (PFNGLNAMEDBUFFERPAGECOMMITMENTPROC_MT)cmgr->getProcAddress("glNamedBufferPageCommitmentARB"); if (!TexPageCommitment) TexPageCommitment = (PFNGLTEXPAGECOMMITMENTPROC_MT)cmgr->getProcAddress("glTexPageCommitmentARB"); - // OpenGL 3 way to enumerate extensions + /* OpenGL 3 & ES 3 way to enumerate extensions */ GLint ext_count = 0; GetIntegerv(NUM_EXTENSIONS, &ext_count); + // clear error which is raised if unsupported + while (GetError() != GL.NO_ERROR) {} extensions.reserve(ext_count); for (GLint k = 0; k < ext_count; k++) { auto tmp = GetStringi(EXTENSIONS, k); @@ -770,14 +772,13 @@ void OpenGLProcedures::LoadAllProcedures(irr::video::IContextManager *cmgr) if (!extensions.empty()) return; - // OpenGL 2 / ES 2 way to enumerate extensions + /* OpenGL 2 / ES 2 way to enumerate extensions */ auto ext_str = GetString(EXTENSIONS); if (!ext_str) return; // get the extension string, chop it up - std::stringstream ext_ss((char*)ext_str); + std::istringstream ext_ss((char*)ext_str); std::string tmp; while (std::getline(ext_ss, tmp, ' ')) extensions.emplace(tmp); - } diff --git a/minetest.conf.example b/minetest.conf.example index 6e3e698be..f5fefbcf3 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -10,7 +10,7 @@ # to the program, eg. "luanti.exe --config ../minetest.conf.example". # Further documentation: -# https://wiki.minetest.net/ +# https://wiki.luanti.org/ # # Controls diff --git a/misc/net.minetest.minetest.desktop b/misc/net.minetest.minetest.desktop index c6c99c469..571eb58a9 100644 --- a/misc/net.minetest.minetest.desktop +++ b/misc/net.minetest.minetest.desktop @@ -11,4 +11,4 @@ PrefersNonDefaultGPU=true Type=Application Categories=Game;Simulation; StartupNotify=false -Keywords=sandbox;world;mining;crafting;blocks;nodes;multiplayer;roleplaying; +Keywords=sandbox;world;mining;crafting;blocks;nodes;multiplayer;roleplaying;minetest; diff --git a/misc/net.minetest.minetest.metainfo.xml b/misc/net.minetest.minetest.metainfo.xml index ca61fe80c..9e7d927f1 100644 --- a/misc/net.minetest.minetest.metainfo.xml +++ b/misc/net.minetest.minetest.metainfo.xml @@ -135,8 +135,8 @@ https://www.minetest.net/get-involved/#reporting-issues https://dev.minetest.net/Translation https://www.minetest.net/get-involved/#donate - https://wiki.minetest.net/FAQ - https://wiki.minetest.net + https://wiki.luanti.org/FAQ + https://wiki.luanti.org https://github.com/minetest/minetest https://www.minetest.net/get-involved diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 65e5ab6bc..692651049 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -249,6 +249,19 @@ if(ENABLE_SPATIAL) endif(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR) endif(ENABLE_SPATIAL) +option(ENABLE_OPENSSL "Use OpenSSL's libcrypto for faster SHA implementations" TRUE) +set(USE_OPENSSL FALSE) + +if(ENABLE_OPENSSL) + find_package(OpenSSL 3.0) + if(OPENSSL_FOUND) + set(USE_OPENSSL TRUE) + message(STATUS "OpenSSL's libcrypto SHA enabled.") + else() + message(STATUS "OpenSSL not found!") + endif() +endif(ENABLE_OPENSSL) + find_package(ZLIB REQUIRED) find_package(Zstd REQUIRED) @@ -260,12 +273,11 @@ endif() # Haiku endian support if(HAIKU) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_BSD_SOURCE") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_BSD_SOURCE") + add_compile_definitions(_BSD_SOURCE) endif() # Use cmake_config.h -add_definitions(-DUSE_CMAKE_CONFIG_H) +add_compile_definitions(USE_CMAKE_CONFIG_H) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -274,15 +286,19 @@ set(PLATFORM_LIBS Threads::Threads) if(WIN32) # Windows if(MSVC) # MSVC Specifics - set(PLATFORM_LIBS dbghelp.lib ${PLATFORM_LIBS}) + list(APPEND PLATFORM_LIBS dbghelp.lib) # Surpress some useless warnings - add_definitions ( /D "_CRT_SECURE_NO_DEPRECATE" /W1 ) - # Get M_PI to work - add_definitions(/D "_USE_MATH_DEFINES") - # Don't define min/max macros in minwindef.h - add_definitions(/D "NOMINMAX") + add_compile_options(/W1) + add_compile_definitions( + # Suppress some useless warnings + _CRT_SECURE_NO_DEPRECATE + # Get M_PI to work + _USE_MATH_DEFINES + # Don't define min/max macros in minwindef.h + NOMINMAX + ) endif() - set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib ${PLATFORM_LIBS}) + list(APPEND PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib winmm.lib) set(EXTRA_DLL "" CACHE FILEPATH "Optional paths to additional DLLs that should be packaged") @@ -309,28 +325,28 @@ if(WIN32) endif() else() # Unix probably - set(PLATFORM_LIBS ${PLATFORM_LIBS} ${CMAKE_DL_LIBS}) + list(APPEND PLATFORM_LIBS ${CMAKE_DL_LIBS}) if(APPLE) - set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) + list(APPEND PLATFORM_LIBS "-framework CoreFoundation") else() check_library_exists(rt clock_gettime "" HAVE_LIBRT) if (HAVE_LIBRT) - set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS}) + list(APPEND PLATFORM_LIBS -lrt) endif(HAVE_LIBRT) endif(APPLE) find_library(ICONV_LIBRARY iconv) mark_as_advanced(ICONV_LIBRARY) if (ICONV_LIBRARY) - set(PLATFORM_LIBS ${PLATFORM_LIBS} ${ICONV_LIBRARY}) + list(APPEND PLATFORM_LIBS ${ICONV_LIBRARY}) endif() if (HAIKU) - set(PLATFORM_LIBS ${PLATFORM_LIBS} network) + list(APPEND PLATFORM_LIBS network) endif() if (ANDROID) - set(PLATFORM_LIBS ${PLATFORM_LIBS} android log) + list(APPEND PLATFORM_LIBS android log) endif() endif() @@ -344,7 +360,7 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") check_c_source_compiles("int main(){}" HAVE_LINK_ATOMIC) set(CMAKE_REQUIRED_LIBRARIES "") if(HAVE_LINK_ATOMIC) - set(PLATFORM_LIBS ${PLATFORM_LIBS} "-latomic") + list(APPEND PLATFORM_LIBS "-latomic") endif() endif() @@ -468,21 +484,21 @@ set(common_SRCS ) if(ANDROID) - set(common_SRCS ${common_SRCS} porting_android.cpp) + list(APPEND common_SRCS porting_android.cpp) endif() if(BUILD_UNITTESTS) add_subdirectory(unittest) - set(common_SRCS ${common_SRCS} ${UNITTEST_SRCS}) + list(APPEND common_SRCS ${UNITTEST_SRCS}) endif() if(BUILD_BENCHMARKS) add_subdirectory(benchmark) - set(common_SRCS ${common_SRCS} ${BENCHMARK_SRCS}) + list(APPEND common_SRCS ${BENCHMARK_SRCS}) endif() if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) - set(common_SRCS ${common_SRCS} catch.cpp) + list(APPEND common_SRCS catch.cpp) endif() # This gives us the icon and file version information @@ -513,8 +529,7 @@ if (BUILD_CLIENT) add_subdirectory(irrlicht_changes) endif(BUILD_CLIENT) -set(client_SRCS - ${client_SRCS} +list(APPEND client_SRCS ${common_SRCS} ${gui_SRCS} ${client_network_SRCS} @@ -523,11 +538,11 @@ set(client_SRCS ) if(BUILD_UNITTESTS) - set(client_SRCS ${client_SRCS} ${UNITTEST_CLIENT_SRCS}) + list(APPEND client_SRCS ${UNITTEST_CLIENT_SRCS}) endif() if(BUILD_BENCHMARKS) - set(client_SRCS ${client_SRCS} ${BENCHMARK_CLIENT_SRCS}) + list(APPEND client_SRCS ${BENCHMARK_CLIENT_SRCS}) endif() # Server sources @@ -591,6 +606,9 @@ add_dependencies(EngineCommon GenerateVersion) target_link_libraries(EngineCommon sha256 ) +if(USE_OPENSSL) + target_link_libraries(EngineCommon OpenSSL::Crypto) +endif() get_target_property( IRRLICHT_INCLUDES IrrlichtMt::IrrlichtMt INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(EngineCommon PRIVATE ${IRRLICHT_INCLUDES}) @@ -724,6 +742,9 @@ if(BUILD_CLIENT) if (USE_SPATIAL) target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY}) endif() + if (USE_OPENSSL) + target_link_libraries(${PROJECT_NAME} OpenSSL::Crypto) + endif() if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) target_link_libraries(${PROJECT_NAME} Catch2::Catch2) endif() @@ -793,6 +814,9 @@ if(BUILD_SERVER) if (USE_SPATIAL) target_link_libraries(${PROJECT_NAME}server ${SPATIAL_LIBRARY}) endif() + if (USE_OPENSSL) + target_link_libraries(${PROJECT_NAME}server OpenSSL::Crypto) + endif() if(USE_CURL) target_link_libraries( ${PROJECT_NAME}server @@ -871,7 +895,7 @@ endif() if(MSVC) # Visual Studio - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D _WIN32_WINNT=0x0601 /D WIN32_LEAN_AND_MEAN /D _CRT_SECURE_NO_WARNINGS") + add_compile_definitions(_WIN32_WINNT=0x0601 WIN32_LEAN_AND_MEAN _CRT_SECURE_NO_WARNINGS) # EHa enables SEH exceptions (used for catching segfaults) set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /MD /GS- /Zi /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0") if(CMAKE_SIZEOF_VOID_P EQUAL 4) @@ -893,12 +917,12 @@ if(MSVC) # Flags that cannot be shared between cl and clang-cl # https://clang.llvm.org/docs/UsersManual.html#clang-cl if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=lld") + add_compile_options(-fuse-ld=lld) # Disable pragma-pack warning set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-pragma-pack") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + add_compile_options(/MP) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /TP /FD /GL") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") endif() @@ -915,7 +939,7 @@ else() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads") endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0601 -DWIN32_LEAN_AND_MEAN") + add_compile_definitions(_WIN32_WINNT=0x0601 WIN32_LEAN_AND_MEAN) endif() # Use a safe subset of flags to speed up math calculations: @@ -1103,7 +1127,7 @@ elseif (USE_GETTEXT) COMMENT "mo-update [${LOCALE}]: Creating mo file." ) - set(MO_FILES ${MO_FILES} ${MO_FILE_PATH}) + list(APPEND MO_FILES ${MO_FILE_PATH}) endforeach() add_custom_target(translations ALL COMMENT "mo update" DEPENDS ${MO_FILES}) diff --git a/src/benchmark/CMakeLists.txt b/src/benchmark/CMakeLists.txt index f79fcf1ef..e8150848a 100644 --- a/src/benchmark/CMakeLists.txt +++ b/src/benchmark/CMakeLists.txt @@ -5,6 +5,7 @@ set (BENCHMARK_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapmodify.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_sha.cpp PARENT_SCOPE) set (BENCHMARK_CLIENT_SRCS diff --git a/src/benchmark/benchmark_sha.cpp b/src/benchmark/benchmark_sha.cpp new file mode 100644 index 000000000..616606f55 --- /dev/null +++ b/src/benchmark/benchmark_sha.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 Luanti Contributors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch.h" +#include "util/hashing.h" +#include + +TEST_CASE("benchmark_sha") +{ + std::string input; + input.resize(100000); + + BENCHMARK("sha1_input100000", i) { + input[0] = (char)i; + return hashing::sha1(input); + }; + + BENCHMARK("sha256_input100000", i) { + input[0] = (char)i; + return hashing::sha256(input); + }; +} diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index d451d0911..c17148a35 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -1,7 +1,7 @@ set(sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound.cpp) if(USE_SOUND) - set(sound_SRCS ${sound_SRCS} + list(APPEND sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound/al_extensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sound/al_helpers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sound/ogg_file.cpp @@ -51,6 +51,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/game.cpp ${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/game_formspec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiscalingfilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp ${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp diff --git a/src/client/camera.h b/src/client/camera.h index 9a8e2d36a..3715bebad 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -48,7 +48,7 @@ struct Nametag return bgcolor.value(); else if (!use_fallback) return video::SColor(0, 0, 0, 0); - else if (textcolor.getLuminance() > 186) + else if (textcolor.getBrightness() > 186) // Dark background for light text return video::SColor(50, 50, 50, 50); else diff --git a/src/client/client.cpp b/src/client/client.cpp index b308a9276..222c1a2ac 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -14,7 +14,6 @@ #include "network/networkpacket.h" #include "threading/mutex_auto_lock.h" #include "client/clientevent.h" -#include "client/gameui.h" #include "client/renderingengine.h" #include "client/sound.h" #include "client/texturepaths.h" @@ -94,7 +93,6 @@ Client::Client( ISoundManager *sound, MtEventManager *event, RenderingEngine *rendering_engine, - GameUI *game_ui, ELoginRegister allow_login_or_register ): m_tsrc(tsrc), @@ -117,7 +115,6 @@ Client::Client( m_chosen_auth_mech(AUTH_MECHANISM_NONE), m_media_downloader(new ClientMediaDownloader()), m_state(LC_Created), - m_game_ui(game_ui), m_modchannel_mgr(new ModChannelMgr()) { // Add local player @@ -1128,7 +1125,7 @@ void Client::sendInit(const std::string &playerName) { NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); - pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) 0; + pkt << SER_FMT_VER_HIGHEST_READ << (u16) 0 /* unused */; pkt << CLIENT_PROTOCOL_VERSION_MIN << LATEST_PROTOCOL_VERSION; pkt << playerName; diff --git a/src/client/client.h b/src/client/client.h index c6803cbe1..e4bb77ab2 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -99,7 +99,6 @@ private: }; class ClientScripting; -class GameUI; class Client : public con::PeerHandler, public InventoryManager, public IGameDef { @@ -119,7 +118,6 @@ public: ISoundManager *sound, MtEventManager *event, RenderingEngine *rendering_engine, - GameUI *game_ui, ELoginRegister allow_login_or_register ); @@ -572,8 +570,6 @@ private: // own state LocalClientState m_state; - GameUI *m_game_ui; - // Used for saving server map to disk client-side MapDatabase *m_localdb = nullptr; IntervalLimiter m_localdb_save_interval; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 9a788e3cc..f57a04b60 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -25,19 +25,10 @@ namespace { // A helper struct struct MeshBufListMaps { - struct MaterialHash - { - size_t operator()(const video::SMaterial &m) const noexcept - { - // Only hash first texture. Simple and fast. - return std::hash{}(m.TextureLayers[0].Texture); - } - }; - using MeshBufListMap = std::unordered_map< video::SMaterial, - std::vector>, - MaterialHash>; + std::vector> + >; std::array maps; @@ -73,6 +64,7 @@ static const std::string ClientMap_settings[] = { "trilinear_filter", "bilinear_filter", "anisotropic_filter", + "transparency_sorting_group_by_buffers", "transparency_sorting_distance", "occlusion_culler", "enable_raytraced_culling", @@ -115,6 +107,9 @@ void ClientMap::onSettingChanged(std::string_view name, bool all) m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); if (all || name == "anisotropic_filter") m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); + if (all || name == "transparency_sorting_group_by_buffers") + m_cache_transparency_sorting_group_by_buffers = + g_settings->getBool("transparency_sorting_group_by_buffers"); if (all || name == "transparency_sorting_distance") m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance"); if (all || name == "occlusion_culler") @@ -1337,7 +1332,8 @@ void ClientMap::updateTransparentMeshBuffers() } if (do_sort_block) { - blockmesh->updateTransparentBuffers(m_camera_position, block->getPos()); + blockmesh->updateTransparentBuffers(m_camera_position, block->getPos(), + m_cache_transparency_sorting_group_by_buffers); ++sorted_blocks; } else { blockmesh->consolidateTransparentBuffers(); diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 4855575f6..a72b6f50d 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -182,6 +182,7 @@ private: bool m_cache_trilinear_filter; bool m_cache_bilinear_filter; bool m_cache_anistropic_filter; + bool m_cache_transparency_sorting_group_by_buffers; u16 m_cache_transparency_sorting_distance; bool m_loops_occlusion_culler; diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 2c6cb69b3..2ee0d747b 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -13,7 +13,7 @@ #include "settings.h" #include "util/hex.h" #include "util/serialize.h" -#include "util/sha1.h" +#include "util/hashing.h" #include "util/string.h" #include @@ -537,12 +537,7 @@ bool IClientMediaDownloader::checkAndLoad( std::string sha1_hex = hex_encode(sha1); // Compute actual checksum of data - std::string data_sha1; - { - SHA1 ctx; - ctx.addBytes(data); - data_sha1 = ctx.getDigest(); - } + std::string data_sha1 = hashing::sha1(data); // Check that received file matches announced checksum if (data_sha1 != sha1) { diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 319a6e9ec..62fc2c270 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -37,7 +37,7 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc, m_material.FogEnable = true; m_material.AntiAliasing = video::EAAM_SIMPLE; { - auto sid = ssrc->getShader("cloud_shader", TILE_MATERIAL_ALPHA); + auto sid = ssrc->getShaderRaw("cloud_shader", true); m_material.MaterialType = ssrc->getShaderInfo(sid).material; } diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index b73b3602c..d90d4e8b0 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1663,11 +1663,6 @@ void GenericCAO::processMessage(const std::string &data) bool is_end_position = readU8(is); float update_interval = readF32(is); - // Place us a bit higher if we're physical, to not sink into - // the ground due to sucky collision detection... - if(m_prop.physical) - m_position += v3f(0,0.002,0); - if(getParent() != NULL) // Just in case return; diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 2d294953a..49abbd4fa 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -1644,7 +1644,6 @@ void MapblockMeshGenerator::drawMeshNode() { u8 facedir = 0; scene::IMesh* mesh; - bool private_mesh; // as a grab/drop pair is not thread-safe int degrotate = 0; if (cur_node.f->param_type_2 == CPT2_FACEDIR || @@ -1664,32 +1663,35 @@ void MapblockMeshGenerator::drawMeshNode() if (cur_node.f->mesh_ptr) { // clone and rotate mesh - private_mesh = true; mesh = cloneMesh(cur_node.f->mesh_ptr); + bool modified = true; if (facedir) rotateMeshBy6dFacedir(mesh, facedir); else if (degrotate) rotateMeshXZby(mesh, 1.5f * degrotate); - recalculateBoundingBox(mesh); - meshmanip->recalculateNormals(mesh, true, false); + else + modified = false; + if (modified) { + recalculateBoundingBox(mesh); + } } else { warningstream << "drawMeshNode(): missing mesh" << std::endl; return; } - int mesh_buffer_count = mesh->getMeshBufferCount(); - for (int j = 0; j < mesh_buffer_count; j++) { + for (u32 j = 0; j < mesh->getMeshBufferCount(); j++) { // Only up to 6 tiles are supported - const auto tile = mesh->getTextureSlot(j); + const u32 tile = mesh->getTextureSlot(j); useTile(MYMIN(tile, 5)); + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); - int vertex_count = buf->getVertexCount(); + u32 vertex_count = buf->getVertexCount(); if (data->m_smooth_lighting) { // Mesh is always private here. So the lighting is applied to each // vertex right here. - for (int k = 0; k < vertex_count; k++) { + for (u32 k = 0; k < vertex_count; k++) { video::S3DVertex &vertex = vertices[k]; vertex.Color = blendLightColor(vertex.Pos, vertex.Normal); vertex.Pos += cur_node.origin; @@ -1697,15 +1699,13 @@ void MapblockMeshGenerator::drawMeshNode() collector->append(cur_node.tile, vertices, vertex_count, buf->getIndices(), buf->getIndexCount()); } else { - // Don't modify the mesh, it may not be private here. - // Instead, let the collector process colors, etc. + // Let the collector process colors, etc. collector->append(cur_node.tile, vertices, vertex_count, buf->getIndices(), buf->getIndexCount(), cur_node.origin, cur_node.color, cur_node.f->light_source); } } - if (private_mesh) - mesh->drop(); + mesh->drop(); } // also called when the drawtype is known but should have been pre-converted diff --git a/src/client/game.cpp b/src/client/game.cpp index de9846bdf..830df0376 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -11,6 +11,7 @@ #include "client.h" #include "client/clientevent.h" #include "client/gameui.h" +#include "client/game_formspec.h" #include "client/inputhandler.h" #include "client/texturepaths.h" #include "client/keys.h" @@ -32,11 +33,7 @@ #include "gameparams.h" #include "gettext.h" #include "gui/guiChatConsole.h" -#include "gui/guiFormSpecMenu.h" -#include "gui/guiKeyChangeMenu.h" -#include "gui/guiPasswordChange.h" -#include "gui/guiOpenURL.h" -#include "gui/guiVolumeChange.h" +#include "texturesource.h" #include "gui/mainmenumanager.h" #include "gui/profilergraph.h" #include "minimap.h" @@ -68,171 +65,6 @@ #include "client/sound/sound_openal.h" #endif -/* - Text input system -*/ - -struct TextDestNodeMetadata : public TextDest -{ - TextDestNodeMetadata(v3s16 p, Client *client) - { - m_p = p; - m_client = client; - } - // This is deprecated I guess? -celeron55 - void gotText(const std::wstring &text) - { - std::string ntext = wide_to_utf8(text); - infostream << "Submitting 'text' field of node at (" << m_p.X << "," - << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; - StringMap fields; - fields["text"] = ntext; - m_client->sendNodemetaFields(m_p, "", fields); - } - void gotText(const StringMap &fields) - { - m_client->sendNodemetaFields(m_p, "", fields); - } - - v3s16 m_p; - Client *m_client; -}; - -struct TextDestPlayerInventory : public TextDest -{ - TextDestPlayerInventory(Client *client) - { - m_client = client; - m_formname.clear(); - } - TextDestPlayerInventory(Client *client, const std::string &formname) - { - m_client = client; - m_formname = formname; - } - void gotText(const StringMap &fields) - { - m_client->sendInventoryFields(m_formname, fields); - } - - Client *m_client; -}; - -struct LocalFormspecHandler : public TextDest -{ - LocalFormspecHandler(const std::string &formname) - { - m_formname = formname; - } - - LocalFormspecHandler(const std::string &formname, Client *client): - m_client(client) - { - m_formname = formname; - } - - void gotText(const StringMap &fields) - { - if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); - return; - } - - if (fields.find("btn_key_config") != fields.end()) { - g_gamecallback->keyConfig(); - return; - } - - if (fields.find("btn_exit_menu") != fields.end()) { - g_gamecallback->disconnect(); - return; - } - - if (fields.find("btn_exit_os") != fields.end()) { - g_gamecallback->exitToOS(); -#ifndef __ANDROID__ - RenderingEngine::get_raw_device()->closeDevice(); -#endif - return; - } - - if (fields.find("btn_change_password") != fields.end()) { - g_gamecallback->changePassword(); - return; - } - - return; - } - - if (m_formname == "MT_DEATH_SCREEN") { - assert(m_client != nullptr); - - if (fields.find("quit") != fields.end()) - m_client->sendRespawnLegacy(); - - return; - } - - if (m_client->modsLoaded()) - m_client->getScript()->on_formspec_input(m_formname, fields); - } - - Client *m_client = nullptr; -}; - -/* Form update callback */ - -class NodeMetadataFormSource: public IFormSource -{ -public: - NodeMetadataFormSource(ClientMap *map, v3s16 p): - m_map(map), - m_p(p) - { - } - const std::string &getForm() const - { - static const std::string empty_string = ""; - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return empty_string; - - return meta->getString("formspec"); - } - - virtual std::string resolveText(const std::string &str) - { - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return str; - - return meta->resolveString(str); - } - - ClientMap *m_map; - v3s16 m_p; -}; - -class PlayerInventoryFormSource: public IFormSource -{ -public: - PlayerInventoryFormSource(Client *client): - m_client(client) - { - } - - const std::string &getForm() const - { - LocalPlayer *player = m_client->getEnv().getLocalPlayer(); - return player->inventory_formspec; - } - - Client *m_client; -}; - class NodeDugEvent : public MtEvent { public: @@ -636,8 +468,6 @@ public: } }; -#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode) - /**************************************************************************** ****************************************************************************/ @@ -739,7 +569,6 @@ protected: void updateInteractTimers(f32 dtime); bool checkConnection(); - bool handleCallbacks(); void processQueues(); void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); void updateDebugState(); @@ -753,7 +582,6 @@ protected: bool shouldShowTouchControls(); void dropSelectedItem(bool single_item = false); - void openInventory(); void openConsole(float scale, const wchar_t *line=NULL); void toggleFreeMove(); void toggleFreeMoveAlt(); @@ -855,9 +683,6 @@ private: bool disable_camera_update = false; }; - void showDeathFormspecLegacy(); - void showPauseMenu(); - void pauseAnimation(); void resumeAnimation(); @@ -922,6 +747,7 @@ private: irr_ptr sky; Hud *hud = nullptr; Minimap *mapper = nullptr; + GameFormSpec m_game_formspec; // Map server hud ids to client hud ids std::unordered_map m_hud_server_to_client; @@ -1129,6 +955,8 @@ bool Game::startup(bool *kill, m_rendering_engine->initialize(client, hud); + m_game_formspec.init(client, m_rendering_engine, input); + return true; } @@ -1199,7 +1027,7 @@ void Game::run() if (!checkConnection()) break; - if (!handleCallbacks()) + if (!m_game_formspec.handleCallbacks()) break; processQueues(); @@ -1231,7 +1059,7 @@ void Game::run() updateProfilerGraphs(&graph); if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) { - showPauseMenu(); + m_game_formspec.showPauseMenu(); } } @@ -1243,10 +1071,6 @@ void Game::run() void Game::shutdown() { - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) - formspec->quitMenu(); - // Clear text when exiting. m_game_ui->clearText(); @@ -1268,8 +1092,6 @@ void Game::shutdown() g_menumgr.deleteFront(); } - m_game_ui->deleteFormspec(); - chat_backend->addMessage(L"", L"# Disconnected."); chat_backend->addMessage(L"", L""); @@ -1654,7 +1476,7 @@ bool Game::connectToServer(const GameStartData &start_data, start_data.password, *draw_control, texture_src, shader_src, itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr, - m_rendering_engine, m_game_ui.get(), + m_rendering_engine, start_data.allow_login_or_register); } catch (const BaseException &e) { *error_message = fmtgettext("Error creating client: %s", e.what()); @@ -1862,49 +1684,6 @@ inline bool Game::checkConnection() return true; } - -/* returns false if game should exit, otherwise true - */ -inline bool Game::handleCallbacks() -{ - if (g_gamecallback->disconnect_requested) { - g_gamecallback->disconnect_requested = false; - return false; - } - - if (g_gamecallback->changepassword_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, client, texture_src); - g_gamecallback->changepassword_requested = false; - } - - if (g_gamecallback->changevolume_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src); - g_gamecallback->changevolume_requested = false; - } - - if (g_gamecallback->keyconfig_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src); - g_gamecallback->keyconfig_requested = false; - } - - if (!g_gamecallback->show_open_url_dialog.empty()) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src, g_gamecallback->show_open_url_dialog); - g_gamecallback->show_open_url_dialog.clear(); - } - - if (g_gamecallback->keyconfig_changed) { - input->keycache.populate(); // update the cache with new settings - g_gamecallback->keyconfig_changed = false; - } - - return true; -} - - void Game::processQueues() { texture_src->processQueue(); @@ -1931,10 +1710,7 @@ void Game::updateDebugState() if (!has_debug) { draw_control->show_wireframe = false; m_flags.disable_camera_update = false; - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) { - formspec->setDebugView(false); - } + m_game_formspec.disableDebugView(); } // noclip @@ -2078,10 +1854,7 @@ void Game::processUserInput(f32 dtime) input->step(dtime); #ifdef __ANDROID__ - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) - formspec->getAndroidUIInput(); - else + if (!m_game_formspec.handleAndroidUIInput()) handleAndroidChatInput(); #endif @@ -2106,13 +1879,13 @@ void Game::processKeyInput() if (g_settings->getBool("continuous_forward")) toggleAutoforward(); } else if (wasKeyDown(KeyType::INVENTORY)) { - openInventory(); + m_game_formspec.showPlayerInventory(); } else if (input->cancelPressed()) { #ifdef __ANDROID__ m_android_chat_open = false; #endif if (!gui_chat_console->isOpenInhibited()) { - showPauseMenu(); + m_game_formspec.showPauseMenu(); } } else if (wasKeyDown(KeyType::CHAT)) { openConsole(0.2, L""); @@ -2280,45 +2053,6 @@ void Game::dropSelectedItem(bool single_item) client->inventoryAction(a); } - -void Game::openInventory() -{ - /* - * Don't permit to open inventory is CAO or player doesn't exists. - * This prevent showing an empty inventory at player load - */ - - LocalPlayer *player = client->getEnv().getLocalPlayer(); - if (!player || !player->getCAO()) - return; - - infostream << "Game: Launching inventory" << std::endl; - - PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); - - InventoryLocation inventoryloc; - inventoryloc.setCurrentPlayer(); - - if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { - delete fs_src; - return; - } - - if (fs_src->getForm().empty()) { - delete fs_src; - return; - } - - TextDest *txt_dst = new TextDestPlayerInventory(client); - auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - - formspec->setFormSpec(fs_src->getForm(), inventoryloc); -} - - void Game::openConsole(float scale, const wchar_t *line) { assert(scale > 0.0f && scale <= 1.0f); @@ -2920,28 +2654,13 @@ void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientati void Game::handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam) { - showDeathFormspecLegacy(); + m_game_formspec.showDeathFormspecLegacy(); } void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam) { - if (event->show_formspec.formspec->empty()) { - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec && (event->show_formspec.formname->empty() - || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) { - formspec->quitMenu(); - } - } else { - FormspecFormSource *fs_src = - new FormspecFormSource(*(event->show_formspec.formspec)); - TextDestPlayerInventory *txt_dst = - new TextDestPlayerInventory(client, *(event->show_formspec.formname)); - - auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname)); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - } + m_game_formspec.showFormSpec(*event->show_formspec.formspec, + *event->show_formspec.formname); delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -2949,11 +2668,8 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) { - FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(*event->show_formspec.formname, client); - GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound_manager.get()); + m_game_formspec.showLocalFormSpec(*event->show_formspec.formspec, + *event->show_formspec.formname); delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -3691,21 +3407,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, if (nodedef_manager->get(map.getNode(nodepos)).rightclickable) client->interact(INTERACT_PLACE, pointed); - infostream << "Launching custom inventory view" << std::endl; - - InventoryLocation inventoryloc; - inventoryloc.setNodeMeta(nodepos); - - NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( - &client->getEnv().getClientMap(), nodepos); - TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); - - auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - - formspec->setFormSpec(meta->getString("formspec"), inventoryloc); + m_game_formspec.showNodeFormspec(meta->getString("formspec"), nodepos); return false; } @@ -4241,34 +3943,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console.get(), dtime); - /* - make sure menu is on top - 1. Delete formspec menu reference if menu was removed - 2. Else, make sure formspec menu is on top - */ - auto formspec = m_game_ui->getFormspecGUI(); - do { // breakable. only runs for one iteration - if (!formspec) - break; - - if (formspec->getReferenceCount() == 1) { - // See GUIFormSpecMenu::create what refcnt = 1 means - m_game_ui->deleteFormspec(); - break; - } - - auto &loc = formspec->getFormspecLocation(); - if (loc.type == InventoryLocation::NODEMETA) { - NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p); - if (!meta || meta->getString("formspec").empty()) { - formspec->quitMenu(); - break; - } - } - - if (isMenuActive()) - guiroot->bringToFront(formspec); - } while (false); + m_game_formspec.update(); /* ==================== Drawing begins ==================== @@ -4485,140 +4160,6 @@ void Game::readSettings() m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); } -/****************************************************************************/ -/**************************************************************************** - Shutdown / cleanup - ****************************************************************************/ -/****************************************************************************/ - -void Game::showDeathFormspecLegacy() -{ - static std::string formspec_str = - std::string("formspec_version[1]") + - SIZE_TAG - "bgcolor[#320000b4;true]" - "label[4.85,1.35;" + gettext("You died") + "]" - "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" - ; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); - - auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - formspec->setFocus("btn_respawn"); -} - -#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) -void Game::showPauseMenu() -{ - std::string control_text; - - if (g_touchcontrols) { - control_text = strgettext("Controls:\n" - "No menu open:\n" - "- slide finger: look around\n" - "- tap: place/punch/use (default)\n" - "- long tap: dig/use (default)\n" - "Menu/inventory open:\n" - "- double tap (outside):\n" - " --> close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - ); - } - - float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; - std::ostringstream os; - - os << "formspec_version[1]" << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << strgettext("Continue") << "]"; - - if (!simple_singleplayer_mode) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << strgettext("Change Password") << "]"; - } else { - os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; - } - -#ifndef __ANDROID__ -#if USE_SOUND - if (g_settings->getBool("enable_sound")) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; - } -#endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Controls") << "]"; -#endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << strgettext("Exit to Menu") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << strgettext("Exit to OS") << "]"; - if (!control_text.empty()) { - os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; - } - os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" - << "\n" - << strgettext("Game info:") << "\n"; - const std::string &address = client->getAddressName(); - os << strgettext("- Mode: "); - if (!simple_singleplayer_mode) { - if (address.empty()) - os << strgettext("Hosting server"); - else - os << strgettext("Remote server"); - } else { - os << strgettext("Singleplayer"); - } - os << "\n"; - if (simple_singleplayer_mode || address.empty()) { - static const std::string on = strgettext("On"); - static const std::string off = strgettext("Off"); - // Note: Status of enable_damage and creative_mode settings is intentionally - // NOT shown here because the game might roll its own damage system and/or do - // a per-player Creative Mode, in which case writing it here would mislead. - bool damage = g_settings->getBool("enable_damage"); - const std::string &announced = g_settings->getBool("server_announce") ? on : off; - if (!simple_singleplayer_mode) { - if (damage) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; - //~ PvP = Player versus Player - os << strgettext("- PvP: ") << pvp << "\n"; - } - os << strgettext("- Public: ") << announced << "\n"; - std::string server_name = g_settings->get("server_name"); - str_formspec_escape(server_name); - if (announced == on && !server_name.empty()) - os << strgettext("- Server Name: ") << server_name; - - } - } - os << ";]"; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(os.str()); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - - auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - formspec->setFocus("btn_continue"); - // game will be paused in next step, if in singleplayer (see m_is_paused) - formspec->doPause = true; -} - /****************************************************************************/ /**************************************************************************** extern function for launching the game diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp new file mode 100644 index 000000000..4c3bbb04a --- /dev/null +++ b/src/client/game_formspec.cpp @@ -0,0 +1,524 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2010-2013 celeron55, Perttu Ahola + +#include "game_formspec.h" + +#include "gettext.h" +#include "nodemetadata.h" +#include "renderingengine.h" +#include "client.h" +#include "scripting_client.h" +#include "clientmap.h" +#include "gui/guiFormSpecMenu.h" +#include "gui/mainmenumanager.h" +#include "gui/touchcontrols.h" +#include "gui/touchscreeneditor.h" +#include "gui/guiPasswordChange.h" +#include "gui/guiKeyChangeMenu.h" +#include "gui/guiPasswordChange.h" +#include "gui/guiOpenURL.h" +#include "gui/guiVolumeChange.h" + +/* + Text input system +*/ + +struct TextDestNodeMetadata : public TextDest +{ + TextDestNodeMetadata(v3s16 p, Client *client) + { + m_p = p; + m_client = client; + } + // This is deprecated I guess? -celeron55 + void gotText(const std::wstring &text) + { + std::string ntext = wide_to_utf8(text); + infostream << "Submitting 'text' field of node at (" << m_p.X << "," + << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; + StringMap fields; + fields["text"] = ntext; + m_client->sendNodemetaFields(m_p, "", fields); + } + void gotText(const StringMap &fields) + { + m_client->sendNodemetaFields(m_p, "", fields); + } + + v3s16 m_p; + Client *m_client; +}; + +struct TextDestPlayerInventory : public TextDest +{ + TextDestPlayerInventory(Client *client) + { + m_client = client; + m_formname.clear(); + } + TextDestPlayerInventory(Client *client, const std::string &formname) + { + m_client = client; + m_formname = formname; + } + void gotText(const StringMap &fields) + { + m_client->sendInventoryFields(m_formname, fields); + } + + Client *m_client; +}; + +struct LocalFormspecHandler : public TextDest +{ + LocalFormspecHandler(const std::string &formname) + { + m_formname = formname; + } + + LocalFormspecHandler(const std::string &formname, Client *client): + m_client(client) + { + m_formname = formname; + } + + void gotText(const StringMap &fields) + { + if (m_formname == "MT_PAUSE_MENU") { + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); + return; + } + + if (fields.find("btn_key_config") != fields.end()) { + g_gamecallback->keyConfig(); + return; + } + + if (fields.find("btn_touchscreen_layout") != fields.end()) { + g_gamecallback->touchscreenLayout(); + return; + } + + if (fields.find("btn_exit_menu") != fields.end()) { + g_gamecallback->disconnect(); + return; + } + + if (fields.find("btn_exit_os") != fields.end()) { + g_gamecallback->exitToOS(); +#ifndef __ANDROID__ + RenderingEngine::get_raw_device()->closeDevice(); +#endif + return; + } + + if (fields.find("btn_change_password") != fields.end()) { + g_gamecallback->changePassword(); + return; + } + + return; + } + + if (m_formname == "MT_DEATH_SCREEN") { + assert(m_client != nullptr); + + if (fields.find("quit") != fields.end()) + m_client->sendRespawnLegacy(); + + return; + } + + if (m_client->modsLoaded()) + m_client->getScript()->on_formspec_input(m_formname, fields); + } + + Client *m_client = nullptr; +}; + +/* Form update callback */ + +class NodeMetadataFormSource: public IFormSource +{ +public: + NodeMetadataFormSource(ClientMap *map, v3s16 p): + m_map(map), + m_p(p) + { + } + const std::string &getForm() const + { + static const std::string empty_string = ""; + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return empty_string; + + return meta->getString("formspec"); + } + + virtual std::string resolveText(const std::string &str) + { + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return str; + + return meta->resolveString(str); + } + + ClientMap *m_map; + v3s16 m_p; +}; + +class PlayerInventoryFormSource: public IFormSource +{ +public: + PlayerInventoryFormSource(Client *client): + m_client(client) + { + } + + const std::string &getForm() const + { + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + return player->inventory_formspec; + } + + Client *m_client; +}; + + +//// GameFormSpec + +void GameFormSpec::deleteFormspec() +{ + if (m_formspec) { + m_formspec->drop(); + m_formspec = nullptr; + } + m_formname.clear(); +} + +GameFormSpec::~GameFormSpec() { + if (m_formspec) + m_formspec->quitMenu(); + this->deleteFormspec(); +} + +void GameFormSpec::showFormSpec(const std::string &formspec, const std::string &formname) +{ + if (formspec.empty()) { + if (m_formspec && (formname.empty() || formname == m_formname)) { + m_formspec->quitMenu(); + } + } else { + FormspecFormSource *fs_src = + new FormspecFormSource(formspec); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(m_client, formname); + + m_formname = formname; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + } +} + +void GameFormSpec::showLocalFormSpec(const std::string &formspec, const std::string &formname) +{ + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler(formname, m_client); + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); +} + +void GameFormSpec::showNodeFormspec(const std::string &formspec, const v3s16 &nodepos) +{ + infostream << "Launching custom inventory view" << std::endl; + + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); + + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &m_client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, m_client); + + m_formname = ""; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + + m_formspec->setFormSpec(formspec, inventoryloc); +} + +void GameFormSpec::showPlayerInventory() +{ + /* + * Don't permit to open inventory is CAO or player doesn't exists. + * This prevent showing an empty inventory at player load + */ + + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + if (!player || !player->getCAO()) + return; + + infostream << "Game: Launching inventory" << std::endl; + + PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(m_client); + + InventoryLocation inventoryloc; + inventoryloc.setCurrentPlayer(); + + if (m_client->modsLoaded() && m_client->getScript()->on_inventory_open(m_client->getInventory(inventoryloc))) { + delete fs_src; + return; + } + + if (fs_src->getForm().empty()) { + delete fs_src; + return; + } + + TextDest *txt_dst = new TextDestPlayerInventory(m_client); + m_formname = ""; + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + + m_formspec->setFormSpec(fs_src->getForm(), inventoryloc); +} + +#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode) + +void GameFormSpec::showPauseMenu() +{ + std::string control_text; + + if (g_touchcontrols) { + control_text = strgettext("Controls:\n" + "No menu open:\n" + "- slide finger: look around\n" + "- tap: place/punch/use (default)\n" + "- long tap: dig/use (default)\n" + "Menu/inventory open:\n" + "- double tap (outside):\n" + " --> close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + ); + } + + auto simple_singleplayer_mode = m_client->m_simple_singleplayer_mode; + + float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; + std::ostringstream os; + + os << "formspec_version[1]" << SIZE_TAG + << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" + << strgettext("Continue") << "]"; + + if (!simple_singleplayer_mode) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" + << strgettext("Change Password") << "]"; + } else { + os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; + } + +#ifndef __ANDROID__ +#if USE_SOUND + if (g_settings->getBool("enable_sound")) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" + << strgettext("Sound Volume") << "]"; + } +#endif +#endif + if (g_touchcontrols) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_touchscreen_layout;" + << strgettext("Touchscreen Layout") << "]"; + } else { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" + << strgettext("Controls") << "]"; + } + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" + << strgettext("Exit to Menu") << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" + << strgettext("Exit to OS") << "]"; + if (!control_text.empty()) { + os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; + } + os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" + << "\n" + << strgettext("Game info:") << "\n"; + const std::string &address = m_client->getAddressName(); + os << strgettext("- Mode: "); + if (!simple_singleplayer_mode) { + if (address.empty()) + os << strgettext("Hosting server"); + else + os << strgettext("Remote server"); + } else { + os << strgettext("Singleplayer"); + } + os << "\n"; + if (simple_singleplayer_mode || address.empty()) { + static const std::string on = strgettext("On"); + static const std::string off = strgettext("Off"); + // Note: Status of enable_damage and creative_mode settings is intentionally + // NOT shown here because the game might roll its own damage system and/or do + // a per-player Creative Mode, in which case writing it here would mislead. + bool damage = g_settings->getBool("enable_damage"); + const std::string &announced = g_settings->getBool("server_announce") ? on : off; + if (!simple_singleplayer_mode) { + if (damage) { + const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + //~ PvP = Player versus Player + os << strgettext("- PvP: ") << pvp << "\n"; + } + os << strgettext("- Public: ") << announced << "\n"; + std::string server_name = g_settings->get("server_name"); + str_formspec_escape(server_name); + if (announced == on && !server_name.empty()) + os << strgettext("- Server Name: ") << server_name; + + } + } + os << ";]"; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(os.str()); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); + + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + m_formspec->setFocus("btn_continue"); + // game will be paused in next step, if in singleplayer (see m_is_paused) + m_formspec->doPause = true; +} + +void GameFormSpec::showDeathFormspecLegacy() +{ + static std::string formspec_str = + std::string("formspec_version[1]") + + SIZE_TAG + "bgcolor[#320000b4;true]" + "label[4.85,1.35;" + gettext("You died") + "]" + "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" + ; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(formspec_str); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", m_client); + + GUIFormSpecMenu::create(m_formspec, m_client, m_rendering_engine->get_gui_env(), + &m_input->joystick, fs_src, txt_dst, m_client->getFormspecPrepend(), + m_client->getSoundManager()); + m_formspec->setFocus("btn_respawn"); +} + +void GameFormSpec::update() +{ + /* + make sure menu is on top + 1. Delete formspec menu reference if menu was removed + 2. Else, make sure formspec menu is on top + */ + if (!m_formspec) + return; + + if (m_formspec->getReferenceCount() == 1) { + // See GUIFormSpecMenu::create what refcnt = 1 means + this->deleteFormspec(); + return; + } + + auto &loc = m_formspec->getFormspecLocation(); + if (loc.type == InventoryLocation::NODEMETA) { + NodeMetadata *meta = m_client->getEnv().getClientMap().getNodeMetadata(loc.p); + if (!meta || meta->getString("formspec").empty()) { + m_formspec->quitMenu(); + return; + } + } + + if (isMenuActive()) + guiroot->bringToFront(m_formspec); +} + +void GameFormSpec::disableDebugView() +{ + if (m_formspec) { + m_formspec->setDebugView(false); + } +} + +/* returns false if game should exit, otherwise true + */ +bool GameFormSpec::handleCallbacks() +{ + auto texture_src = m_client->getTextureSource(); + + if (g_gamecallback->disconnect_requested) { + g_gamecallback->disconnect_requested = false; + return false; + } + + if (g_gamecallback->changepassword_requested) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, m_client, texture_src); + g_gamecallback->changepassword_requested = false; + } + + if (g_gamecallback->changevolume_requested) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src); + g_gamecallback->changevolume_requested = false; + } + + if (g_gamecallback->keyconfig_requested) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src); + g_gamecallback->keyconfig_requested = false; + } + + if (g_gamecallback->touchscreenlayout_requested) { + (new GUITouchscreenLayout(guienv, guiroot, -1, + &g_menumgr, texture_src))->drop(); + g_gamecallback->touchscreenlayout_requested = false; + } + + if (!g_gamecallback->show_open_url_dialog.empty()) { + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src, g_gamecallback->show_open_url_dialog); + g_gamecallback->show_open_url_dialog.clear(); + } + + if (g_gamecallback->keyconfig_changed) { + m_input->keycache.populate(); // update the cache with new settings + g_gamecallback->keyconfig_changed = false; + } + + return true; +} + +#ifdef __ANDROID__ +bool GameFormSpec::handleAndroidUIInput() +{ + if (m_formspec) { + m_formspec->getAndroidUIInput(); + return true; + } + return false; +} +#endif diff --git a/src/client/game_formspec.h b/src/client/game_formspec.h new file mode 100644 index 000000000..370370151 --- /dev/null +++ b/src/client/game_formspec.h @@ -0,0 +1,62 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 cx384 + +#pragma once + +#include +#include "irr_v3d.h" + +class Client; +class RenderingEngine; +class InputHandler; +class ISoundManager; +class GUIFormSpecMenu; + +/* +This object intend to contain the core fromspec functionality. +It includes: + - methods to show specific formspec menus + - storing the opened fromspec + - handling fromspec related callbacks + */ +struct GameFormSpec +{ + void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input) + { + m_client = client; + m_rendering_engine = rendering_engine; + m_input = input; + } + + ~GameFormSpec(); + + void showFormSpec(const std::string &formspec, const std::string &formname); + void showLocalFormSpec(const std::string &formspec, const std::string &formname); + void showNodeFormspec(const std::string &formspec, const v3s16 &nodepos); + void showPlayerInventory(); + void showDeathFormspecLegacy(); + void showPauseMenu(); + + void update(); + void disableDebugView(); + + bool handleCallbacks(); + +#ifdef __ANDROID__ + // Returns false if no formspec open + bool handleAndroidUIInput(); +#endif + +private: + Client *m_client; + RenderingEngine *m_rendering_engine; + InputHandler *m_input; + + // Default: "". If other than "": Empty show_formspec packets will only + // close the formspec when the formname matches + std::string m_formname; + GUIFormSpecMenu *m_formspec = nullptr; + + void deleteFormspec(); +}; diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 3408ba196..e09875934 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -8,7 +8,6 @@ #include #include "gui/mainmenumanager.h" #include "gui/guiChatConsole.h" -#include "gui/guiFormSpecMenu.h" #include "gui/touchcontrols.h" #include "util/enriched_string.h" #include "util/pointedthing.h" @@ -201,10 +200,10 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ status_y - status_height, status_x + status_width, status_y)); // Fade out - video::SColor final_color = m_statustext_initial_color; - final_color.setAlpha(0); - video::SColor fade_color = m_statustext_initial_color.getInterpolated_quadratic( - m_statustext_initial_color, final_color, m_statustext_time / statustext_time_max); + video::SColor fade_color = m_statustext_initial_color; + f32 d = m_statustext_time / statustext_time_max; + fade_color.setAlpha(static_cast( + fade_color.getAlpha() * (1.0f - d * d))); guitext_status->setOverrideColor(fade_color); guitext_status->enableOverrideColor(true); } @@ -319,17 +318,6 @@ void GameUI::toggleProfiler() } } - -void GameUI::deleteFormspec() -{ - if (m_formspec) { - m_formspec->drop(); - m_formspec = nullptr; - } - - m_formname.clear(); -} - void GameUI::clearText() { if (m_guitext_chat) { diff --git a/src/client/gameui.h b/src/client/gameui.h index 95674232a..03cbb3b2c 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -13,7 +13,6 @@ using namespace irr; class Client; class EnrichedString; class GUIChatConsole; -class GUIFormSpecMenu; struct MapDrawControl; struct PointedThing; @@ -79,15 +78,6 @@ public: void toggleHud(); void toggleProfiler(); - GUIFormSpecMenu *&updateFormspec(const std::string &formname) - { - m_formname = formname; - return m_formspec; - } - - const std::string &getFormspecName() { return m_formname; } - GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; } - void deleteFormspec(); void clearText(); private: @@ -113,9 +103,4 @@ private: gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text u8 m_profiler_current_page = 0; const u8 m_profiler_max_page = 3; - - // Default: "". If other than "": Empty show_formspec packets will only - // close the formspec when the formname matches - std::string m_formname; - GUIFormSpecMenu *m_formspec = nullptr; }; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 6929d4c9e..0aa847cb7 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -24,6 +24,7 @@ #include "wieldmesh.h" #include "client/renderingengine.h" #include "client/minimap.h" +#include "client/texturesource.h" #include "gui/touchcontrols.h" #include "util/enriched_string.h" #include "irrlicht_changes/CGUITTFont.h" @@ -54,14 +55,14 @@ Hud::Hud(Client *client, LocalPlayer *player, tsrc = client->getTextureSource(); - v3f crosshair_color = g_settings->getV3F("crosshair_color"); + v3f crosshair_color = g_settings->getV3F("crosshair_color").value_or(v3f()); u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255); u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255); u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255); u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255); crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b); - v3f selectionbox_color = g_settings->getV3F("selectionbox_color"); + v3f selectionbox_color = g_settings->getV3F("selectionbox_color").value_or(v3f()); u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255); u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255); u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255); @@ -85,10 +86,11 @@ Hud::Hud(Client *client, LocalPlayer *player, // Initialize m_selection_material IShaderSource *shdrsrc = client->getShaderSource(); - { - auto shader_id = shdrsrc->getShader( - m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", TILE_MATERIAL_ALPHA); + if (m_mode == HIGHLIGHT_HALO) { + auto shader_id = shdrsrc->getShaderRaw("selection_shader", true); m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material; + } else { + m_selection_material.MaterialType = video::EMT_SOLID; } if (m_mode == HIGHLIGHT_BOX) { @@ -102,10 +104,7 @@ Hud::Hud(Client *client, LocalPlayer *player, } // Initialize m_block_bounds_material - { - auto shader_id = shdrsrc->getShader("default_shader", TILE_MATERIAL_ALPHA); - m_block_bounds_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material; - } + m_block_bounds_material.MaterialType = video::EMT_SOLID; m_block_bounds_material.Thickness = rangelim(g_settings->getS16("selectionbox_width"), 1, 5); diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index 0f4e81f97..4adc39834 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -566,7 +566,7 @@ static void apply_hue_saturation(video::IImage *dst, v2u32 dst_pos, v2u32 size, for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) { if (colorize) { - f32 lum = dst->getPixel(x, y).getLuminance() / 255.0f; + f32 lum = dst->getPixel(x, y).getBrightness() / 255.0f; if (norm_l < 0) { lum *= norm_l + 1.0f; @@ -1511,10 +1511,18 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, CHECK_BASEIMG(); - // Apply the "clean transparent" filter, if needed + /* Apply the "clean transparent" filter, if necessary + * This is needed since filtering will sample parts of the image + * that are transparent and PNG optimizers often discard the color + * information in those parts. */ if (m_setting_mipmap || m_setting_bilinear_filter || - m_setting_trilinear_filter || m_setting_anisotropic_filter) - imageCleanTransparent(baseimg, 127); + m_setting_trilinear_filter || m_setting_anisotropic_filter) { + /* Note: in theory we should pass either 0 or 127 depending on + * if the texture is used with an ALPHA or ALPHA_REF material, + * however we don't have this information here. + * It doesn't matter in practice. */ + imageCleanTransparent(baseimg, 0); + } /* Upscale textures to user's requested minimum size. This is a trick to make * filters look as good on low-res textures as on high-res ones, by making diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index c212bd148..c8cd79248 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -3,6 +3,7 @@ // Copyright (C) 2010-2013 celeron55, Perttu Ahola #include "mapblock_mesh.h" +#include "CMeshBuffer.h" #include "client.h" #include "mapblock.h" #include "map.h" @@ -818,7 +819,8 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, return true; } -void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos) +void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos, + bool group_by_buffers) { // nothing to do if the entire block is opaque if (m_transparent_triangles.empty()) @@ -834,24 +836,56 @@ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos) m_transparent_buffers_consolidated = false; m_transparent_buffers.clear(); + std::vector>> ordered_strains; + std::unordered_map strain_idxs; + + if (group_by_buffers) { + // find (reversed) order for strains, by iterating front-to-back + // (if a buffer A has a triangle nearer than all triangles of another + // buffer B, A should be drawn in front of (=after) B) + scene::SMeshBuffer *current_buffer = nullptr; + for (auto it = triangle_refs.rbegin(); it != triangle_refs.rend(); ++it) { + const auto &t = m_transparent_triangles[*it]; + if (current_buffer == t.buffer) + continue; + current_buffer = t.buffer; + auto [_it2, is_new] = + strain_idxs.emplace(current_buffer, ordered_strains.size()); + if (is_new) + ordered_strains.emplace_back(current_buffer, std::vector{}); + } + } + + // find order for triangles, by iterating back-to-front scene::SMeshBuffer *current_buffer = nullptr; - std::vector current_strain; + std::vector *current_strain = nullptr; for (auto i : triangle_refs) { const auto &t = m_transparent_triangles[i]; if (current_buffer != t.buffer) { - if (current_buffer) { - m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); - current_strain.clear(); - } current_buffer = t.buffer; + if (group_by_buffers) { + auto it = strain_idxs.find(current_buffer); + assert(it != strain_idxs.end()); + current_strain = &ordered_strains[it->second].second; + } else { + ordered_strains.emplace_back(current_buffer, std::vector{}); + current_strain = &ordered_strains.back().second; + } } - current_strain.push_back(t.p1); - current_strain.push_back(t.p2); - current_strain.push_back(t.p3); + current_strain->push_back(t.p1); + current_strain->push_back(t.p2); + current_strain->push_back(t.p3); } - if (!current_strain.empty()) - m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); + m_transparent_buffers.reserve(ordered_strains.size()); + if (group_by_buffers) { + // the order was reversed + for (auto it = ordered_strains.rbegin(); it != ordered_strains.rend(); ++it) + m_transparent_buffers.emplace_back(it->first, std::move(it->second)); + } else { + for (auto it = ordered_strains.begin(); it != ordered_strains.end(); ++it) + m_transparent_buffers.emplace_back(it->first, std::move(it->second)); + } } void MapBlockMesh::consolidateTransparentBuffers() diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 5a8daf50c..e7cadb3db 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -209,8 +209,14 @@ public: /// Center of the bounding-sphere, in BS-space, relative to block pos. v3f getBoundingSphereCenter() const { return m_bounding_sphere_center; } - /// update transparent buffers to render towards the camera - void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos); + /** Update transparent buffers to render towards the camera. + * @param group_by_buffers If true, triangles in the same buffer are batched + * into the same PartialMeshBuffer, resulting in fewer draw calls, but + * wrong order. Triangles within a single buffer are still ordered, and + * buffers are ordered relative to each other (with respect to their nearest + * triangle). + */ + void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos, bool group_by_buffers); void consolidateTransparentBuffers(); /// get the list of transparent buffers diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 3f947ae50..eb56d3580 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -249,10 +249,14 @@ static void rotateMesh(scene::IMesh *mesh, float degrees) float c = std::cos(degrees); float s = std::sin(degrees); auto rotator = [c, s] (video::S3DVertex *vertex) { - float u = vertex->Pos.*U; - float v = vertex->Pos.*V; - vertex->Pos.*U = c * u - s * v; - vertex->Pos.*V = s * u + c * v; + auto rotate_vec = [c, s] (v3f &vec) { + float u = vec.*U; + float v = vec.*V; + vec.*U = c * u - s * v; + vec.*V = s * u + c * v; + }; + rotate_vec(vertex->Pos); + rotate_vec(vertex->Normal); }; applyToMesh(mesh, rotator); } @@ -272,9 +276,9 @@ void rotateMeshYZby(scene::IMesh *mesh, f64 degrees) rotateMesh<&v3f::Y, &v3f::Z>(mesh, degrees); } -void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir) +void rotateMeshBy6dFacedir(scene::IMesh *mesh, u8 facedir) { - int axisdir = facedir >> 2; + u8 axisdir = facedir >> 2; facedir &= 0x03; switch (facedir) { case 1: rotateMeshXZby(mesh, -90); break; diff --git a/src/client/mesh.h b/src/client/mesh.h index 5fd0ebb0e..3345e24f7 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -68,7 +68,7 @@ void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, Rotate the mesh by 6d facedir value. Method only for meshnodes, not suitable for entities. */ -void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir); +void rotateMeshBy6dFacedir(scene::IMesh *mesh, u8 facedir); /* Rotate the mesh around the axis and given angle in degrees. diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 6706f84b3..ba9ec967a 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -599,7 +599,7 @@ void Minimap::drawMinimap(core::rect rect) material.TextureLayers[1].Texture = data->heightmap_texture; if (data->mode.type == MINIMAP_TYPE_SURFACE) { - auto sid = m_shdrsrc->getShader("minimap_shader", TILE_MATERIAL_ALPHA); + auto sid = m_shdrsrc->getShaderRaw("minimap_shader", true); material.MaterialType = m_shdrsrc->getShaderInfo(sid).material; } else { material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 6ba7fa701..472ac0321 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -22,6 +22,8 @@ #include "settings.h" #include "profiler.h" +using BlendMode = ParticleParamTypes::BlendMode; + ClientParticleTexture::ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc) { tex = p; @@ -268,6 +270,7 @@ ParticleSpawner::ParticleSpawner( } size_t max_particles = 0; // maximum number of particles likely to be visible at any given time + assert(p.time >= 0); if (p.time != 0) { auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min); max_particles = p.amount / maxGenerations; @@ -603,8 +606,11 @@ video::S3DVertex *ParticleBuffer::getVertices(u16 index) void ParticleBuffer::OnRegisterSceneNode() { - if (IsVisible) - SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT); + if (IsVisible) { + SceneManager->registerNodeForRendering(this, + m_mesh_buffer->getMaterial().MaterialType == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF + ? scene::ESNRP_SOLID : scene::ESNRP_TRANSPARENT_EFFECT); + } scene::ISceneNode::OnRegisterSceneNode(); } @@ -906,6 +912,9 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color)) return; + p.texture.blendmode = f.alpha == ALPHAMODE_BLEND + ? BlendMode::alpha : BlendMode::clip; + p.expirationtime = myrand_range(0, 100) / 100.0f; // Physics @@ -940,40 +949,47 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate) m_particles.reserve(m_particles.size() + max_estimate); } -video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTexRef &texture) +static void setBlendMode(video::SMaterial &material, BlendMode blendmode) { - // translate blend modes to GL blend functions video::E_BLEND_FACTOR bfsrc, bfdst; video::E_BLEND_OPERATION blendop; - const auto blendmode = texture.tex ? texture.tex->blendmode : - ParticleParamTypes::BlendMode::alpha; - switch (blendmode) { - case ParticleParamTypes::BlendMode::add: + case BlendMode::add: bfsrc = video::EBF_SRC_ALPHA; bfdst = video::EBF_DST_ALPHA; blendop = video::EBO_ADD; break; - case ParticleParamTypes::BlendMode::sub: + case BlendMode::sub: bfsrc = video::EBF_SRC_ALPHA; bfdst = video::EBF_DST_ALPHA; blendop = video::EBO_REVSUBTRACT; break; - case ParticleParamTypes::BlendMode::screen: + case BlendMode::screen: bfsrc = video::EBF_ONE; bfdst = video::EBF_ONE_MINUS_SRC_COLOR; blendop = video::EBO_ADD; break; - default: // includes ParticleParamTypes::BlendMode::alpha + default: // includes BlendMode::alpha bfsrc = video::EBF_SRC_ALPHA; bfdst = video::EBF_ONE_MINUS_SRC_ALPHA; blendop = video::EBO_ADD; break; } + material.MaterialTypeParam = video::pack_textureBlendFunc( + bfsrc, bfdst, + video::EMFN_MODULATE_1X, + video::EAS_TEXTURE | video::EAS_VERTEX_COLOR); + material.BlendOperation = blendop; +} + +video::SMaterial ParticleManager::getMaterialForParticle(const Particle *particle) +{ + const ClientParticleTexRef &texture = particle->getTextureRef(); + video::SMaterial material; // Texture @@ -984,17 +1000,18 @@ video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTex tex.MagFilter = video::ETMAGF_NEAREST; }); - // We don't have working transparency sorting. Disable Z-Write for - // correct results for clipped-alpha at least. - material.ZWriteEnable = video::EZW_OFF; - - // enable alpha blending and set blend mode - material.MaterialType = video::EMT_ONETEXTURE_BLEND; - material.MaterialTypeParam = video::pack_textureBlendFunc( - bfsrc, bfdst, - video::EMFN_MODULATE_1X, - video::EAS_TEXTURE | video::EAS_VERTEX_COLOR); - material.BlendOperation = blendop; + const auto blendmode = particle->getBlendMode(); + if (blendmode == BlendMode::clip) { + material.ZWriteEnable = video::EZW_ON; + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + material.MaterialTypeParam = 0.5f; + } else { + // We don't have working transparency sorting. Disable Z-Write for + // correct results for clipped-alpha at least. + material.ZWriteEnable = video::EZW_OFF; + material.MaterialType = video::EMT_ONETEXTURE_BLEND; + setBlendMode(material, blendmode); + } material.setTexture(0, texture.ref); return material; @@ -1004,7 +1021,7 @@ bool ParticleManager::addParticle(std::unique_ptr toadd) { MutexAutoLock lock(m_particle_list_lock); - auto material = getMaterialForParticle(toadd->getTextureRef()); + auto material = getMaterialForParticle(toadd.get()); ParticleBuffer *found = nullptr; // simple shortcut when multiple particles of the same type get added diff --git a/src/client/particles.h b/src/client/particles.h index 6a8d09a47..619877744 100644 --- a/src/client/particles.h +++ b/src/client/particles.h @@ -77,6 +77,9 @@ public: const ClientParticleTexRef &getTextureRef() const { return m_texture; } + ParticleParamTypes::BlendMode getBlendMode() const + { return m_texture.tex ? m_texture.tex->blendmode : m_p.texture.blendmode; } + ParticleBuffer *getBuffer() const { return m_buffer; } bool attachToBuffer(ParticleBuffer *buffer); @@ -231,7 +234,7 @@ protected: ParticleParameters &p, video::ITexture **texture, v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum = 0); - static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture); + static video::SMaterial getMaterialForParticle(const Particle *texture); bool addParticle(std::unique_ptr toadd); diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp index fe3a5f0f8..7baf40322 100644 --- a/src/client/render/anaglyph.cpp +++ b/src/client/render/anaglyph.cpp @@ -21,8 +21,7 @@ void SetColorMaskStep::run(PipelineContext &context) mat.Material.ColorMask = color_mask; mat.EnableProps = video::EMP_COLOR_MASK; mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID | - scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT | - scene::ESNRP_SHADOW; + scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT; } /// ClearDepthBufferTarget diff --git a/src/client/render/interlaced.cpp b/src/client/render/interlaced.cpp index f56d8a3d9..c4014a74d 100644 --- a/src/client/render/interlaced.cpp +++ b/src/client/render/interlaced.cpp @@ -66,10 +66,12 @@ void populateInterlacedPipeline(RenderPipeline *pipeline, Client *client) } pipeline->addStep(0.0f); + IShaderSource *s = client->getShaderSource(); - u32 shader = s->getShader("3d_interlaced_merge", TILE_MATERIAL_BASIC); + auto shader = s->getShaderRaw("3d_interlaced_merge"); video::E_MATERIAL_TYPE material = s->getShaderInfo(shader).material; auto texture_map = { TEXTURE_LEFT, TEXTURE_RIGHT, TEXTURE_MASK }; + auto merge = pipeline->addStep(material, texture_map); merge->setRenderSource(buffer); merge->setRenderTarget(pipeline->createOwned()); diff --git a/src/client/render/pipeline.cpp b/src/client/render/pipeline.cpp index b23517a88..740117111 100644 --- a/src/client/render/pipeline.cpp +++ b/src/client/render/pipeline.cpp @@ -6,6 +6,7 @@ #include "client/client.h" #include "client/hud.h" #include "IRenderTarget.h" +#include "SColor.h" #include #include @@ -26,7 +27,7 @@ video::ITexture *TextureBuffer::getTexture(u8 index) } -void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format, bool clear) +void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format, bool clear, u8 msaa) { assert(index != NO_DEPTH_TEXTURE); @@ -41,9 +42,10 @@ void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::str definition.name = name; definition.format = format; definition.clear = clear; + definition.msaa = msaa; } -void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format, bool clear) +void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format, bool clear, u8 msaa) { assert(index != NO_DEPTH_TEXTURE); @@ -58,6 +60,7 @@ void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &na definition.name = name; definition.format = format; definition.clear = clear; + definition.msaa = msaa; } void TextureBuffer::reset(PipelineContext &context) @@ -119,23 +122,41 @@ bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefini if (!modify) return false; - if (*texture) + if (*texture) { m_driver->removeTexture(*texture); + *texture = nullptr; + } if (definition.valid) { + if (!m_driver->queryTextureFormat(definition.format)) { + errorstream << "Failed to create texture \"" << definition.name + << "\": unsupported format " << video::ColorFormatNames[definition.format] + << std::endl; + return false; + } + if (definition.clear) { + // We're not able to clear a render target texture + // We're not able to create a normal texture with MSAA + // (could be solved by more refactoring in Irrlicht, but not needed for now) + sanity_check(definition.msaa < 1); + video::IImage *image = m_driver->createImage(definition.format, size); // Cannot use image->fill because it's not implemented for all formats. std::memset(image->getData(), 0, image->getDataSizeFromFormat(definition.format, size.Width, size.Height)); *texture = m_driver->addTexture(definition.name.c_str(), image); image->drop(); - } - else { + } else if (definition.msaa > 0) { + *texture = m_driver->addRenderTargetTextureMs(size, definition.msaa, definition.name.c_str(), definition.format); + } else { *texture = m_driver->addRenderTargetTexture(size, definition.name.c_str(), definition.format); } - } - else { - *texture = nullptr; + + if (!*texture) { + errorstream << "Failed to create texture \"" << definition.name + << "\"" << std::endl; + return false; + } } return true; @@ -188,6 +209,12 @@ void TextureBufferOutput::activate(PipelineContext &context) RenderTarget::activate(context); } +video::IRenderTarget *TextureBufferOutput::getIrrRenderTarget(PipelineContext &context) +{ + activate(context); // Needed to make sure that render_target is set up. + return render_target; +} + u8 DynamicSource::getTextureCount() { assert(isConfigured()); diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index 081fed20b..1e2f3d87f 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -117,7 +117,7 @@ public: * @param name unique name of the texture * @param format color format */ - void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false); + void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false, u8 msaa = 0); /** * Configure relative-size texture for the specific index @@ -127,7 +127,7 @@ public: * @param name unique name of the texture * @param format color format */ - void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false); + void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false, u8 msaa = 0); virtual u8 getTextureCount() override { return m_textures.size(); } virtual video::ITexture *getTexture(u8 index) override; @@ -147,6 +147,7 @@ private: core::dimension2du size; std::string name; video::ECOLOR_FORMAT format; + u8 msaa; }; /** @@ -175,6 +176,9 @@ public: TextureBufferOutput(TextureBuffer *buffer, const std::vector &texture_map, u8 depth_stencil); virtual ~TextureBufferOutput() override; void activate(PipelineContext &context) override; + + video::IRenderTarget *getIrrRenderTarget(PipelineContext &context); + private: static const u8 NO_DEPTH_TEXTURE = 255; diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp index fd3e0b9ba..c24ba8837 100644 --- a/src/client/render/plain.cpp +++ b/src/client/render/plain.cpp @@ -155,8 +155,11 @@ void populatePlainPipeline(RenderPipeline *pipeline, Client *client) video::ECOLOR_FORMAT selectColorFormat(video::IVideoDriver *driver) { - if (driver->queryTextureFormat(video::ECF_A16B16G16R16F)) + u32 bits = g_settings->getU32("post_processing_texture_bits"); + if (bits >= 16 && driver->queryTextureFormat(video::ECF_A16B16G16R16F)) return video::ECF_A16B16G16R16F; + if (bits >= 10 && driver->queryTextureFormat(video::ECF_A2R10G10B10)) + return video::ECF_A2R10G10B10; return video::ECF_A8R8G8B8; } diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index 26ce6888e..42858fa84 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -9,6 +9,7 @@ #include "client/shader.h" #include "client/tile.h" #include "settings.h" +#include "mt_opengl.h" PostProcessingStep::PostProcessingStep(u32 _shader_id, const std::vector &_texture_map) : shader_id(_shader_id), texture_map(_texture_map) @@ -102,21 +103,45 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep static const u8 TEXTURE_EXPOSURE_2 = 4; static const u8 TEXTURE_FXAA = 5; static const u8 TEXTURE_VOLUME = 6; + + static const u8 TEXTURE_MSAA_COLOR = 7; + static const u8 TEXTURE_MSAA_DEPTH = 8; + static const u8 TEXTURE_SCALE_DOWN = 10; static const u8 TEXTURE_SCALE_UP = 20; - // Super-sampling is simply rendering into a larger texture. - // Downscaling is done by the final step when rendering to the screen. - const std::string antialiasing = g_settings->get("antialiasing"); const bool enable_bloom = g_settings->getBool("enable_bloom"); + const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom; const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure"); + + const std::string antialiasing = g_settings->get("antialiasing"); + const u16 antialiasing_scale = MYMAX(2, g_settings->getU16("fsaa")); + + // This code only deals with MSAA in combination with post-processing. MSAA without + // post-processing works via a flag at OpenGL context creation instead. + // To make MSAA work with post-processing, we need multisample texture support, + // which has higher OpenGL (ES) version requirements. + // Note: This is not about renderbuffer objects, but about textures, + // since that's what we use and what Irrlicht allows us to use. + + const bool msaa_available = driver->queryFeature(video::EVDF_TEXTURE_MULTISAMPLE); + const bool enable_msaa = antialiasing == "fsaa" && msaa_available; + if (antialiasing == "fsaa" && !msaa_available) + warningstream << "Ignoring configured FSAA. FSAA is not supported in " + << "combination with post-processing by the current video driver." << std::endl; + const bool enable_ssaa = antialiasing == "ssaa"; const bool enable_fxaa = antialiasing == "fxaa"; - const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom; + // Super-sampling is simply rendering into a larger texture. + // Downscaling is done by the final step when rendering to the screen. if (enable_ssaa) { - u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); - scale *= ssaa_scale; + scale *= antialiasing_scale; + } + + if (enable_msaa) { + buffer->setTexture(TEXTURE_MSAA_COLOR, scale, "3d_render_msaa", color_format, false, antialiasing_scale); + buffer->setTexture(TEXTURE_MSAA_DEPTH, scale, "3d_depthmap_msaa", depth_format, false, antialiasing_scale); } buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", color_format); @@ -125,7 +150,14 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep buffer->setTexture(TEXTURE_DEPTH, scale, "3d_depthmap", depth_format); // attach buffer to the previous step - previousStep->setRenderTarget(pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH)); + if (enable_msaa) { + TextureBufferOutput *msaa = pipeline->createOwned(buffer, std::vector { TEXTURE_MSAA_COLOR }, TEXTURE_MSAA_DEPTH); + previousStep->setRenderTarget(msaa); + TextureBufferOutput *normal = pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH); + pipeline->addStep(msaa, normal); + } else { + previousStep->setRenderTarget(pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH)); + } // shared variables u32 shader_id; @@ -133,6 +165,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // Number of mipmap levels of the bloom downsampling texture const u8 MIPMAP_LEVELS = 4; + // color_format can be a normalized integer format, but bloom requires + // values outside of [0,1] so this needs to be a different one. + const auto bloom_format = video::ECF_A16B16G16R16F; // post-processing stage @@ -141,19 +176,19 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // common downsampling step for bloom or autoexposure if (enable_bloom || enable_auto_exposure) { - v2f downscale = scale * 0.5; + v2f downscale = scale * 0.5f; for (u8 i = 0; i < MIPMAP_LEVELS; i++) { - buffer->setTexture(TEXTURE_SCALE_DOWN + i, downscale, std::string("downsample") + std::to_string(i), color_format); + buffer->setTexture(TEXTURE_SCALE_DOWN + i, downscale, std::string("downsample") + std::to_string(i), bloom_format); if (enable_bloom) - buffer->setTexture(TEXTURE_SCALE_UP + i, downscale, std::string("upsample") + std::to_string(i), color_format); - downscale *= 0.5; + buffer->setTexture(TEXTURE_SCALE_UP + i, downscale, std::string("upsample") + std::to_string(i), bloom_format); + downscale *= 0.5f; } if (enable_bloom) { - buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", color_format); + buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", bloom_format); // get bright spots - u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH); + u32 shader_id = client->getShaderSource()->getShaderRaw("extract_bloom"); RenderStep *extract_bloom = pipeline->addStep(shader_id, std::vector { source, TEXTURE_EXPOSURE_1 }); extract_bloom->setRenderSource(buffer); extract_bloom->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_BLOOM)); @@ -163,7 +198,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep if (enable_volumetric_light) { buffer->setTexture(TEXTURE_VOLUME, scale, "volume", color_format); - shader_id = client->getShaderSource()->getShader("volumetric_light", TILE_MATERIAL_PLAIN, NDT_MESH); + shader_id = client->getShaderSource()->getShaderRaw("volumetric_light"); auto volume = pipeline->addStep(shader_id, std::vector { source, TEXTURE_DEPTH }); volume->setRenderSource(buffer); volume->setRenderTarget(pipeline->createOwned(buffer, TEXTURE_VOLUME)); @@ -171,7 +206,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep } // downsample - shader_id = client->getShaderSource()->getShader("bloom_downsample", TILE_MATERIAL_PLAIN, NDT_MESH); + shader_id = client->getShaderSource()->getShaderRaw("bloom_downsample"); for (u8 i = 0; i < MIPMAP_LEVELS; i++) { auto step = pipeline->addStep(shader_id, std::vector { source }); step->setRenderSource(buffer); @@ -184,7 +219,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // Bloom pt 2 if (enable_bloom) { // upsample - shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH); + shader_id = client->getShaderSource()->getShaderRaw("bloom_upsample"); for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) { auto step = pipeline->addStep(shader_id, std::vector { u8(TEXTURE_SCALE_DOWN + i - 1), source }); step->setRenderSource(buffer); @@ -197,7 +232,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep // Dynamic Exposure pt2 if (enable_auto_exposure) { - shader_id = client->getShaderSource()->getShader("update_exposure", TILE_MATERIAL_PLAIN, NDT_MESH); + shader_id = client->getShaderSource()->getShaderRaw("update_exposure"); auto update_exposure = pipeline->addStep(shader_id, std::vector { TEXTURE_EXPOSURE_1, u8(TEXTURE_SCALE_DOWN + MIPMAP_LEVELS - 1) }); update_exposure->setBilinearFilter(1, true); update_exposure->setRenderSource(buffer); @@ -211,7 +246,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep final_stage_source = TEXTURE_FXAA; buffer->setTexture(TEXTURE_FXAA, scale, "fxaa", color_format); - shader_id = client->getShaderSource()->getShader("fxaa", TILE_MATERIAL_PLAIN); + shader_id = client->getShaderSource()->getShaderRaw("fxaa"); PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { TEXTURE_COLOR }); pipeline->addStep(effect); effect->setBilinearFilter(0, true); @@ -220,7 +255,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep } // final merge - shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); + shader_id = client->getShaderSource()->getShaderRaw("second_stage"); PostProcessingStep *effect = pipeline->createOwned(shader_id, std::vector { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 }); pipeline->addStep(effect); if (enable_ssaa) @@ -234,3 +269,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep return effect; } + +void ResolveMSAAStep::run(PipelineContext &context) +{ + context.device->getVideoDriver()->blitRenderTarget(msaa_fbo->getIrrRenderTarget(context), + target_fbo->getIrrRenderTarget(context)); +} diff --git a/src/client/render/secondstage.h b/src/client/render/secondstage.h index 6b0e42289..1765a5ed8 100644 --- a/src/client/render/secondstage.h +++ b/src/client/render/secondstage.h @@ -44,4 +44,18 @@ private: void configureMaterial(); }; + +class ResolveMSAAStep : public TrivialRenderStep +{ +public: + ResolveMSAAStep(TextureBufferOutput *_msaa_fbo, TextureBufferOutput *_target_fbo) : + msaa_fbo(_msaa_fbo), target_fbo(_target_fbo) {}; + void run(PipelineContext &context) override; + +private: + TextureBufferOutput *msaa_fbo; + TextureBufferOutput *target_fbo; +}; + + RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep, v2f scale, Client *client); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 4e45bd4cd..c3cb49eed 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -179,7 +179,10 @@ RenderingEngine::RenderingEngine(MyEventReceiver *receiver) // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); - bool enable_fsaa = g_settings->get("antialiasing") == "fsaa"; + // Don't enable MSAA in OpenGL context creation if post-processing is enabled, + // the post-processing pipeline handles it. + bool enable_fsaa = g_settings->get("antialiasing") == "fsaa" && + !g_settings->getBool("enable_post_processing"); u16 fsaa = enable_fsaa ? MYMAX(2, g_settings->getU16("fsaa")) : 0; // Determine driver diff --git a/src/client/shader.cpp b/src/client/shader.cpp index ddb0f8f85..de9d1980a 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -271,7 +271,7 @@ public: The id 0 points to a null shader. Its material is EMT_SOLID. */ u32 getShaderIdDirect(const std::string &name, - MaterialType material_type, NodeDrawType drawtype) override; + MaterialType material_type, NodeDrawType drawtype); /* If shader specified by the name pointed by the id doesn't @@ -281,10 +281,18 @@ public: and not found in cache, the call is queued to the main thread for processing. */ - u32 getShader(const std::string &name, MaterialType material_type, NodeDrawType drawtype) override; + u32 getShaderRaw(const std::string &name, bool blendAlpha) override + { + // TODO: the shader system should be refactored to be much more generic. + // Just let callers pass arbitrary constants, this would also deal with + // runtime changes cleanly. + return getShader(name, blendAlpha ? TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC, + NodeDrawType_END); + } + ShaderInfo getShaderInfo(u32 id) override; // Processes queued shader requests from other threads. @@ -607,11 +615,17 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, #define textureFlags texture2 )"; + /* Define constants for node and object shaders */ + const bool node_shader = drawtype != NodeDrawType_END; + if (node_shader) { + bool use_discard = fully_programmable; - // For renderers that should use discard instead of GL_ALPHA_TEST - const char *renderer = reinterpret_cast(GL.GetString(GL.RENDERER)); - if (strstr(renderer, "GC7000")) - use_discard = true; + if (!use_discard) { + // workaround for a certain OpenGL implementation lacking GL_ALPHA_TEST + const char *renderer = reinterpret_cast(GL.GetString(GL.RENDERER)); + if (strstr(renderer, "GC7000")) + use_discard = true; + } if (use_discard) { if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) shaders_header << "#define USE_DISCARD 1\n"; @@ -660,12 +674,30 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, bool enable_waving_water = g_settings->getBool("enable_waving_water"); shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n"; - shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n"; - shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n"; - shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n"; + if (enable_waving_water) { + shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n"; + shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n"; + shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n"; + } + switch (material_type) { + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: + case TILE_MATERIAL_WAVING_LIQUID_BASIC: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + shaders_header << "#define MATERIAL_WAVING_LIQUID 1\n"; + break; + default: + shaders_header << "#define MATERIAL_WAVING_LIQUID 0\n"; + break; + } shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n"; shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n"; + + } + + /* Other constants */ + shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n"; if (g_settings->getBool("enable_tinted_fog")) diff --git a/src/client/shader.h b/src/client/shader.h index c1d02bf2c..c78b0078a 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -196,18 +196,33 @@ using CachedStructPixelShaderSetting = CachedStructShaderSettinggetShaderInfo(ssrc->getShader("stars_shader", TILE_MATERIAL_ALPHA)).material; + ssrc->getShaderInfo(ssrc->getShaderRaw("stars_shader", true)).material; m_materials[1] = baseMaterial(); m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -661,7 +661,9 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) return; m_materials[0].ColorParam = color.toSColor(); - auto sky_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f)); + auto day_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f)); + auto orbit_rotation = core::matrix4().setRotationAxisRadians(m_sky_params.body_orbit_tilt * M_PI / 180.0, v3f(1.0f, 0.0f, 0.0f)); + auto sky_rotation = orbit_rotation * day_rotation; auto world_matrix = driver->getTransform(video::ETS_WORLD); driver->setTransform(video::ETS_WORLD, world_matrix * sky_rotation); driver->setMaterial(m_materials[0]); @@ -695,13 +697,11 @@ void Sky::place_sky_body( * day_position: turn the body around the Z axis, to place it depending of the time of the day */ { - v3f centrum = getSkyBodyPosition(horizon_position, day_position, m_sky_params.body_orbit_tilt); - v3f untilted_centrum = getSkyBodyPosition(horizon_position, day_position, 0.f); for (video::S3DVertex &vertex : vertices) { // Body is directed to -Z (south) by default vertex.Pos.rotateXZBy(horizon_position); vertex.Pos.rotateXYBy(day_position); - vertex.Pos += centrum - untilted_centrum; + vertex.Pos.rotateYZBy(m_sky_params.body_orbit_tilt); } } diff --git a/src/client/tile.h b/src/client/tile.h index df02d2244..a2c5bbdfb 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -20,6 +20,7 @@ enum MaterialType{ TILE_MATERIAL_WAVING_LIQUID_BASIC, TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT, TILE_MATERIAL_WAVING_LIQUID_OPAQUE, + // Note: PLAIN isn't a material actually used by tiles, rather just entities. TILE_MATERIAL_PLAIN, TILE_MATERIAL_PLAIN_ALPHA }; diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 5dc6e4b74..2ec91dfd1 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -29,6 +29,7 @@ #cmakedefine01 USE_SYSTEM_GMP #cmakedefine01 USE_SYSTEM_JSONCPP #cmakedefine01 USE_REDIS +#cmakedefine01 USE_OPENSSL #cmakedefine01 HAVE_ENDIAN_H #cmakedefine01 HAVE_STRLCPY #cmakedefine01 HAVE_MALLOC_TRIM diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index e19d51108..5001a2810 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -43,7 +43,7 @@ void PlayerDatabaseFiles::deSerialize(RemotePlayer *p, std::istream &is, } try { - sao->setBasePosition(args.getV3F("position")); + sao->setBasePosition(args.getV3F("position").value_or(v3f())); } catch (SettingNotFoundException &e) {} try { diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index ff16b9b69..6c142dda5 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -78,7 +78,7 @@ void set_default_settings() settings->setDefault("language", ""); settings->setDefault("name", ""); settings->setDefault("bind_address", ""); - settings->setDefault("serverlist_url", "servers.luanti.org"); + settings->setDefault("serverlist_url", "https://servers.luanti.org"); // Client settings->setDefault("address", ""); @@ -302,6 +302,7 @@ void set_default_settings() settings->setDefault("arm_inertia", "true"); settings->setDefault("show_nametag_backgrounds", "true"); settings->setDefault("show_block_bounds_radius_near", "4"); + settings->setDefault("transparency_sorting_group_by_buffers", "true"); settings->setDefault("transparency_sorting_distance", "16"); settings->setDefault("enable_minimap", "true"); @@ -310,6 +311,7 @@ void set_default_settings() // Effects settings->setDefault("enable_post_processing", "true"); + settings->setDefault("post_processing_texture_bits", "16"); settings->setDefault("directional_colored_fog", "true"); settings->setDefault("inventory_items_animations", "false"); settings->setDefault("mip_map", "false"); @@ -547,6 +549,7 @@ void set_default_settings() settings->setDefault("keymap_sneak", "KEY_SHIFT"); #endif + settings->setDefault("touch_layout", ""); settings->setDefault("touchscreen_sensitivity", "0.2"); settings->setDefault("touchscreen_threshold", "20"); settings->setDefault("touch_long_tap_delay", "400"); @@ -570,8 +573,12 @@ void set_default_settings() settings->setDefault("active_block_range", "2"); settings->setDefault("viewing_range", "50"); settings->setDefault("leaves_style", "simple"); + // Note: OpenGL ES 2.0 is not guaranteed to provide depth textures, + // which we would need for PP. settings->setDefault("enable_post_processing", "false"); + // still set these two settings in case someone wants to enable it settings->setDefault("debanding", "false"); + settings->setDefault("post_processing_texture_bits", "8"); settings->setDefault("curl_verify_cert", "false"); // Apply settings according to screen size diff --git a/src/emerge_internal.h b/src/emerge_internal.h index 6dd438603..6a71c7523 100644 --- a/src/emerge_internal.h +++ b/src/emerge_internal.h @@ -93,7 +93,7 @@ public: MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): m_ignorevariable(ignorevariable) { - if (m_ignorevariable->getVolume() == 0) + if (m_ignorevariable->hasEmptyExtent()) *m_ignorevariable = a; else m_ignorevariable = nullptr; @@ -102,7 +102,7 @@ public: ~MapEditEventAreaIgnorer() { if (m_ignorevariable) { - assert(m_ignorevariable->getVolume() != 0); + assert(!m_ignorevariable->hasEmptyExtent()); *m_ignorevariable = VoxelArea(); } } diff --git a/src/filesys.cpp b/src/filesys.cpp index 8881eb2ca..a368bc697 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -833,16 +833,51 @@ std::string RemoveRelativePathComponents(std::string path) std::string AbsolutePath(const std::string &path) { #ifdef _WIN32 + // handle behavior differences on windows + if (path.empty()) + return ""; + else if (!PathExists(path)) + return ""; char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH); #else char *abs_path = realpath(path.c_str(), NULL); #endif - if (!abs_path) return ""; + if (!abs_path) + return ""; std::string abs_path_str(abs_path); free(abs_path); return abs_path_str; } +std::string AbsolutePathPartial(const std::string &path) +{ + if (path.empty()) + return ""; + // Try to determine absolute path + std::string abs_path = fs::AbsolutePath(path); + if (!abs_path.empty()) + return abs_path; + // Remove components until it works + std::string cur_path = path; + std::string removed; + while (abs_path.empty() && !cur_path.empty()) { + std::string component; + cur_path = RemoveLastPathComponent(cur_path, &component); + removed = component + (removed.empty() ? "" : DIR_DELIM + removed); + abs_path = AbsolutePath(cur_path); + } + // If we had a relative path that does not exist, it needs to be joined with cwd + if (cur_path.empty() && !IsPathAbsolute(path)) + abs_path = AbsolutePath("."); + // or there's an error + if (abs_path.empty()) + return ""; + // Put them back together and resolve the remaining relative components + if (!removed.empty()) + abs_path.append(DIR_DELIM).append(removed); + return RemoveRelativePathComponents(abs_path); +} + const char *GetFilenameFromPath(const char *path) { const char *filename = strrchr(path, DIR_DELIM_CHAR); diff --git a/src/filesys.h b/src/filesys.h index dededcdb3..0e974d822 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -131,6 +131,12 @@ std::string RemoveRelativePathComponents(std::string path); // components and symlinks removed. Returns "" on error. std::string AbsolutePath(const std::string &path); +// This is a combination of RemoveRelativePathComponents() and AbsolutePath() +// It will resolve symlinks for the leading path components that exist and +// still remove "." and ".." in the rest of the path. +// Returns "" on error. +std::string AbsolutePathPartial(const std::string &path); + // Returns the filename from a path or the entire path if no directory // delimiter is found. const char *GetFilenameFromPath(const char *path); diff --git a/src/gettext_plural_form.cpp b/src/gettext_plural_form.cpp index 6a5322421..89dd721a4 100644 --- a/src/gettext_plural_form.cpp +++ b/src/gettext_plural_form.cpp @@ -90,16 +90,16 @@ class TernaryOperation: public GettextPluralForm }; typedef std::pair ParserResult; -typedef ParserResult (*Parser)(const size_t, const std::wstring_view &); +typedef ParserResult (*Parser)(const size_t, std::wstring_view); -static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str); +static ParserResult parse_expr(const size_t nplurals, std::wstring_view str); template typename Operator> static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t* pattern) { if (!str_starts_with(res.second, pattern)) return ParserResult(nullptr, res.second); - auto next = Parser(nplurals, res.second.substr(std::char_traits::length(pattern))); + auto next = Parser(nplurals, trim(res.second.substr(std::char_traits::length(pattern)))); if (!next.first) return next; next.first = GettextPluralForm::Ptr(new BinaryOperation(res.first, next.first)); @@ -123,7 +123,7 @@ static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, c } template typename Operator, template typename... Operators> -static ParserResult parse_ltr(const size_t nplurals, const std::wstring_view &str, const wchar_t** patterns) +static ParserResult parse_ltr(const size_t nplurals, std::wstring_view str, const wchar_t** patterns) { auto &&pres = Parser(nplurals, str); if (!pres.first) @@ -139,7 +139,7 @@ static ParserResult parse_ltr(const size_t nplurals, const std::wstring_view &st return pres; } -static ParserResult parse_atomic(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_atomic(const size_t nplurals, std::wstring_view str) { if (str.empty()) return ParserResult(nullptr, str); @@ -151,7 +151,7 @@ static ParserResult parse_atomic(const size_t nplurals, const std::wstring_view return ParserResult(new ConstValue(nplurals, val), trim(str.substr(endp-str.data()))); } -static ParserResult parse_parenthesized(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_parenthesized(const size_t nplurals, std::wstring_view str) { if (str.empty()) return ParserResult(nullptr, str); @@ -167,7 +167,7 @@ static ParserResult parse_parenthesized(const size_t nplurals, const std::wstrin return result; } -static ParserResult parse_negation(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_negation(const size_t nplurals, std::wstring_view str) { if (str.empty()) return ParserResult(nullptr, str); @@ -179,43 +179,43 @@ static ParserResult parse_negation(const size_t nplurals, const std::wstring_vie return result; } -static ParserResult parse_multiplicative(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_multiplicative(const size_t nplurals, std::wstring_view str) { static const wchar_t *patterns[] = { L"*", L"/", L"%" }; return parse_ltr(nplurals, str, patterns); } -static ParserResult parse_additive(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_additive(const size_t nplurals, std::wstring_view str) { static const wchar_t *patterns[] = { L"+", L"-" }; return parse_ltr(nplurals, str, patterns); } -static ParserResult parse_comparison(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_comparison(const size_t nplurals, std::wstring_view str) { static const wchar_t *patterns[] = { L"<=", L">=", L"<", L">" }; return parse_ltr(nplurals, str, patterns); } -static ParserResult parse_equality(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_equality(const size_t nplurals, std::wstring_view str) { static const wchar_t *patterns[] = { L"==", L"!=" }; return parse_ltr(nplurals, str, patterns); } -static ParserResult parse_conjunction(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_conjunction(const size_t nplurals, std::wstring_view str) { static const wchar_t *and_pattern[] = { L"&&" }; return parse_ltr(nplurals, str, and_pattern); } -static ParserResult parse_disjunction(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_disjunction(const size_t nplurals, std::wstring_view str) { static const wchar_t *or_pattern[] = { L"||" }; return parse_ltr(nplurals, str, or_pattern); } -static ParserResult parse_ternary(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_ternary(const size_t nplurals, std::wstring_view str) { auto pres = parse_disjunction(nplurals, str); if (pres.second.empty() || pres.second[0] != '?') // no ? : @@ -229,12 +229,12 @@ static ParserResult parse_ternary(const size_t nplurals, const std::wstring_view return ParserResult(new TernaryOperation(cond, val, pres.first), pres.second); } -static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str) +static ParserResult parse_expr(const size_t nplurals, std::wstring_view str) { return parse_ternary(nplurals, trim(str)); } -GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, const std::wstring_view &str) +GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, std::wstring_view str) { if (nplurals == 0) return nullptr; @@ -244,7 +244,7 @@ GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, const std return result.first; } -GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(const std::wstring_view &str) +GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(std::wstring_view str) { if (!str_starts_with(str, L"Plural-Forms: nplurals=") || !str_ends_with(str, L";")) return nullptr; diff --git a/src/gettext_plural_form.h b/src/gettext_plural_form.h index d73718965..1d3195e9a 100644 --- a/src/gettext_plural_form.h +++ b/src/gettext_plural_form.h @@ -24,8 +24,8 @@ public: } virtual ~GettextPluralForm() {}; - static GettextPluralForm::Ptr parse(const size_t nplurals, const std::wstring_view &str); - static GettextPluralForm::Ptr parseHeaderLine(const std::wstring_view &str); + static GettextPluralForm::Ptr parse(const size_t nplurals, std::wstring_view str); + static GettextPluralForm::Ptr parseHeaderLine(std::wstring_view str); protected: GettextPluralForm(size_t nplurals): nplurals(nplurals) {}; private: diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 04a03609d..876acab74 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -25,5 +25,7 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/profilergraph.cpp ${CMAKE_CURRENT_SOURCE_DIR}/touchcontrols.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/touchscreenlayout.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/touchscreeneditor.cpp PARENT_SCOPE ) diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 28aea66c1..689ad22e0 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -55,7 +55,7 @@ GUIChatConsole::GUIChatConsole( m_background_color.setGreen(255); m_background_color.setBlue(255); } else { - v3f console_color = g_settings->getV3F("console_color"); + v3f console_color = g_settings->getV3F("console_color").value_or(v3f()); m_background_color.setRed(clamp_u8(myround(console_color.X))); m_background_color.setGreen(clamp_u8(myround(console_color.Y))); m_background_color.setBlue(clamp_u8(myround(console_color.Z))); diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index ed5db785b..bc594bba6 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -30,10 +30,6 @@ GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool borde : GUIEditBox(environment, parent, id, rectangle, border, writable), m_background(true), m_bg_color_used(false), m_tsrc(tsrc) { -#ifdef _DEBUG - setDebugName("GUIEditBoxWithScrollBar"); -#endif - Text = text; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index ed1c3009d..8a69f0429 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3010,7 +3010,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_tabheader_upper_edge = 0; { - v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color"); + v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color").value_or(v3f()); m_fullscreen_bgcolor = video::SColor( (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")), clamp_u8(myround(formspec_bgcolor.X)), @@ -3610,10 +3610,25 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, // Calculate and set the tooltip position s32 tooltip_x = m_pointer.X + tooltip_offset_x; s32 tooltip_y = m_pointer.Y + tooltip_offset_y; - if (tooltip_x + tooltip_width > (s32)screenSize.X) - tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height; - if (tooltip_y + tooltip_height > (s32)screenSize.Y) - tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height; + // Bottom/Left limited positions (if the tooltip is too far out) + s32 tooltip_x_alt = (s32)screenSize.X - tooltip_width - m_btn_height; + s32 tooltip_y_alt = (s32)screenSize.Y - tooltip_height - m_btn_height; + + int collision = (tooltip_x_alt < tooltip_x) + 2 * (tooltip_y_alt < tooltip_y); + switch (collision) { + case 1: // x + tooltip_x = tooltip_x_alt; + break; + case 2: // y + tooltip_y = tooltip_y_alt; + break; + case 3: // both + tooltip_x = tooltip_x_alt; + tooltip_y = (s32)screenSize.Y - 2 * tooltip_height - m_btn_height; + break; + default: // OK + break; + } m_tooltip_element->setRelativePosition( core::rect( diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index e3900a1fd..8efd81d0f 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -1015,10 +1015,6 @@ GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment, m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0) { -#ifdef _DEBUG - setDebugName("GUIHyperText"); -#endif - IGUISkin *skin = 0; if (Environment) skin = Environment->getSkin(); diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index d5f43796d..9d10e3960 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -21,6 +21,7 @@ public: virtual void changeVolume() = 0; virtual void showOpenURLDialog(const std::string &url) = 0; virtual void signalKeyConfigChange() = 0; + virtual void touchscreenLayout() = 0; }; extern gui::IGUIEnvironment *guienv; @@ -133,6 +134,11 @@ public: keyconfig_changed = true; } + void touchscreenLayout() override + { + touchscreenlayout_requested = true; + } + void showOpenURLDialog(const std::string &url) override { show_open_url_dialog = url; @@ -142,6 +148,7 @@ public: bool changepassword_requested = false; bool changevolume_requested = false; bool keyconfig_requested = false; + bool touchscreenlayout_requested = false; bool shutdown_requested = false; bool keyconfig_changed = false; std::string show_open_url_dialog = ""; diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index d7e46787b..17352264d 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -5,6 +5,7 @@ // Copyright (C) 2024 grorp, Gregor Parzefall #include "touchcontrols.h" +#include "touchscreenlayout.h" #include "gettime.h" #include "irr_v2d.h" @@ -14,8 +15,10 @@ #include "client/guiscalingfilter.h" #include "client/keycode.h" #include "client/renderingengine.h" +#include "client/texturesource.h" #include "util/numeric.h" -#include "gettext.h" +#include "irr_gui_ptr.h" +#include "IGUIImage.h" #include "IGUIStaticText.h" #include "IGUIFont.h" #include @@ -26,59 +29,6 @@ TouchControls *g_touchcontrols; -static const char *button_image_names[] = { - "jump_btn.png", - "down.png", - "zoom.png", - "aux1_btn.png", - "overflow_btn.png", - - "fly_btn.png", - "noclip_btn.png", - "fast_btn.png", - "debug_btn.png", - "camera_btn.png", - "rangeview_btn.png", - "minimap_btn.png", - "", - - "chat_btn.png", - "inventory_btn.png", - "drop_btn.png", - "exit_btn.png", - - "joystick_off.png", - "joystick_bg.png", - "joystick_center.png", -}; - -// compare with GUIKeyChangeMenu::init_keys -static const char *button_titles[] = { - N_("Jump"), - N_("Sneak"), - N_("Zoom"), - N_("Aux1"), - N_("Overflow menu"), - - N_("Toggle fly"), - N_("Toggle noclip"), - N_("Toggle fast"), - N_("Toggle debug"), - N_("Change camera"), - N_("Range select"), - N_("Toggle minimap"), - N_("Toggle chat log"), - - N_("Chat"), - N_("Inventory"), - N_("Drop"), - N_("Exit"), - - N_("Joystick"), - N_("Joystick"), - N_("Joystick"), -}; - static void load_button_texture(IGUIImage *gui_button, const std::string &path, const recti &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver) { @@ -268,10 +218,22 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) m_long_tap_delay = g_settings->getU16("touch_long_tap_delay"); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1"); + m_screensize = m_device->getVideoDriver()->getScreenSize(); - m_button_size = MYMIN(m_screensize.Y / 4.5f, - RenderingEngine::getDisplayDensity() * 65.0f * - g_settings->getFloat("hud_scaling")); + m_button_size = ButtonLayout::getButtonSize(m_screensize); + applyLayout(ButtonLayout::loadFromSettings()); +} + +void TouchControls::applyLayout(const ButtonLayout &layout) +{ + m_layout = layout; + + m_buttons.clear(); + m_overflow_btn = nullptr; + m_overflow_bg = nullptr; + m_overflow_buttons.clear(); + m_overflow_button_titles.clear(); + m_overflow_button_rects.clear(); // Initialize joystick display "button". // Joystick is placed on the bottom left of screen. @@ -298,47 +260,21 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) m_joystick_btn_center = grab_gui_element(makeButtonDirect(joystick_center_id, recti(0, 0, m_button_size, m_button_size), false)); - // init jump button - addButton(m_buttons, jump_id, button_image_names[jump_id], - recti(m_screensize.X - 1.75f * m_button_size, - m_screensize.Y - m_button_size, - m_screensize.X - 0.25f * m_button_size, - m_screensize.Y)); + for (const auto &[id, meta] : m_layout.layout) { + if (!mayAddButton(id)) + continue; - // init sneak button - addButton(m_buttons, sneak_id, button_image_names[sneak_id], - recti(m_screensize.X - 3.25f * m_button_size, - m_screensize.Y - m_button_size, - m_screensize.X - 1.75f * m_button_size, - m_screensize.Y)); - - // init zoom button - addButton(m_buttons, zoom_id, button_image_names[zoom_id], - recti(m_screensize.X - 1.25f * m_button_size, - m_screensize.Y - 4 * m_button_size, - m_screensize.X - 0.25f * m_button_size, - m_screensize.Y - 3 * m_button_size)); - - // init aux1 button - if (!m_joystick_triggers_aux1) - addButton(m_buttons, aux1_id, button_image_names[aux1_id], - recti(m_screensize.X - 1.25f * m_button_size, - m_screensize.Y - 2.5f * m_button_size, - m_screensize.X - 0.25f * m_button_size, - m_screensize.Y - 1.5f * m_button_size)); - - // init overflow button - m_overflow_btn = grab_gui_element(makeButtonDirect(overflow_id, - recti(m_screensize.X - 1.25f * m_button_size, - m_screensize.Y - 5.5f * m_button_size, - m_screensize.X - 0.25f * m_button_size, - m_screensize.Y - 4.5f * m_button_size), true)); - - const static touch_gui_button_id overflow_buttons[] { - chat_id, inventory_id, drop_id, exit_id, - fly_id, noclip_id, fast_id, debug_id, camera_id, range_id, minimap_id, - toggle_chat_id, - }; + recti rect = m_layout.getRect(id, m_screensize, m_button_size, m_texturesource); + if (id == toggle_chat_id) + // Chat is shown by default, so chat_hide_btn.png is shown first. + addToggleButton(m_buttons, id, "chat_hide_btn.png", + "chat_show_btn.png", rect, true); + else if (id == overflow_id) + m_overflow_btn = grab_gui_element( + makeButtonDirect(id, rect, true)); + else + addButton(m_buttons, id, button_image_names[id], rect, true); + } IGUIStaticText *background = m_guienv->addStaticText(L"", recti(v2s32(0, 0), dimension2du(m_screensize))); @@ -346,32 +282,17 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) background->setVisible(false); m_overflow_bg = grab_gui_element(background); - s32 cols = 4; - s32 rows = 3; - f32 screen_aspect = (f32)m_screensize.X / (f32)m_screensize.Y; - while ((s32)ARRLEN(overflow_buttons) > cols * rows) { - f32 aspect = (f32)cols / (f32)rows; - if (aspect > screen_aspect) - rows++; - else - cols++; - } - - v2s32 size(m_button_size, m_button_size); - v2s32 spacing(m_screensize.X / (cols + 1), m_screensize.Y / (rows + 1)); - v2s32 pos(spacing); - - for (auto id : overflow_buttons) { - if (id_to_keycode(id) == KEY_UNKNOWN) - continue; - - recti rect(pos - size / 2, dimension2du(size.X, size.Y)); - if (rect.LowerRightCorner.X > (s32)m_screensize.X) { - pos.X = spacing.X; - pos.Y += spacing.Y; - rect = recti(pos - size / 2, dimension2du(size.X, size.Y)); - } + auto overflow_buttons = m_layout.getMissingButtons(); + overflow_buttons.erase(std::remove_if( + overflow_buttons.begin(), overflow_buttons.end(), + [&](touch_gui_button_id id) { + // There's no sense in adding the overflow button to the overflow + // menu (also, it's impossible since it doesn't have a keycode). + return !mayAddButton(id) || id == overflow_id; + }), overflow_buttons.end()); + layout_button_grid(m_screensize, m_texturesource, overflow_buttons, + [&] (touch_gui_button_id id, v2s32 pos, recti rect) { if (id == toggle_chat_id) // Chat is shown by default, so chat_hide_btn.png is shown first. addToggleButton(m_overflow_buttons, id, "chat_hide_btn.png", @@ -379,27 +300,23 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) else addButton(m_overflow_buttons, id, button_image_names[id], rect, false); - std::wstring str = wstrgettext(button_titles[id]); - IGUIStaticText *text = m_guienv->addStaticText(str.c_str(), recti()); - IGUIFont *font = text->getActiveFont(); - dimension2du dim = font->getDimension(str.c_str()); - dim = dimension2du(dim.Width * 1.25f, dim.Height * 1.25f); // avoid clipping - text->setRelativePosition(recti(pos.X - dim.Width / 2, pos.Y + size.Y / 2, - pos.X + dim.Width / 2, pos.Y + size.Y / 2 + dim.Height)); - text->setTextAlignment(EGUIA_CENTER, EGUIA_UPPERLEFT); + IGUIStaticText *text = m_guienv->addStaticText(L"", recti()); + make_button_grid_title(text, id, pos, rect); text->setVisible(false); m_overflow_button_titles.push_back(grab_gui_element(text)); rect.addInternalPoint(text->getRelativePosition().UpperLeftCorner); rect.addInternalPoint(text->getRelativePosition().LowerRightCorner); m_overflow_button_rects.push_back(rect); - - pos.X += spacing.X; - } + }); m_status_text = grab_gui_element( m_guienv->addStaticText(L"", recti(), false, false)); m_status_text->setVisible(false); + + // applyLayout can be called at any time, also e.g. while the overflow menu + // is open, so this is necessary to restore correct visibility. + updateVisibility(); } TouchControls::~TouchControls() @@ -407,6 +324,17 @@ TouchControls::~TouchControls() releaseAll(); } +bool TouchControls::mayAddButton(touch_gui_button_id id) +{ + if (!ButtonLayout::isButtonAllowed(id)) + return false; + if (id == aux1_id && m_joystick_triggers_aux1) + return false; + if (id != overflow_id && id_to_keycode(id) == KEY_UNKNOWN) + return false; + return true; +} + void TouchControls::addButton(std::vector &buttons, touch_gui_button_id id, const std::string &image, const recti &rect, bool visible) { @@ -701,6 +629,15 @@ void TouchControls::applyJoystickStatus() void TouchControls::step(float dtime) { + v2u32 screensize = m_device->getVideoDriver()->getScreenSize(); + s32 button_size = ButtonLayout::getButtonSize(screensize); + + if (m_screensize != screensize || m_button_size != button_size) { + m_screensize = screensize; + m_button_size = button_size; + applyLayout(m_layout); + } + // simulate keyboard repeats buttons_step(m_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource); buttons_step(m_overflow_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource); diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index cb3e8e8bf..701baba42 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -5,11 +5,8 @@ #pragma once -#include "IGUIStaticText.h" #include "irrlichttypes.h" -#include -#include -#include +#include "IEventReceiver.h" #include #include @@ -17,41 +14,29 @@ #include #include "itemdef.h" -#include "client/game.h" +#include "touchscreenlayout.h" #include "util/basic_macros.h" -#include "client/texturesource.h" namespace irr { class IrrlichtDevice; + namespace gui + { + class IGUIEnvironment; + class IGUIImage; + class IGUIStaticText; + } + namespace video + { + class IVideoDriver; + } } +class ISimpleTextureSource; using namespace irr::core; using namespace irr::gui; -// We cannot use irr_ptr for Irrlicht GUI elements we own. -// Option 1: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr -// constructor. -// -> We steal the reference owned by IGUIEnvironment and drop it later, -// causing the IGUIElement to be deleted while IGUIEnvironment still -// references it. -// Option 2: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr::grab. -// -> We add another reference and drop it later, but since IGUIEnvironment -// still references the IGUIElement, it is never deleted. -// To make IGUIEnvironment drop its reference to the IGUIElement, we have to call -// IGUIElement::remove, so that's what we'll do. -template -std::shared_ptr grab_gui_element(T *element) -{ - static_assert(std::is_base_of_v, - "grab_gui_element only works for IGUIElement"); - return std::shared_ptr(element, [](T *e) { - e->remove(); - }); -} - - enum class TapState { None, @@ -59,36 +44,6 @@ enum class TapState LongTap, }; -enum touch_gui_button_id -{ - jump_id = 0, - sneak_id, - zoom_id, - aux1_id, - overflow_id, - - // usually in the "settings bar" - fly_id, - noclip_id, - fast_id, - debug_id, - camera_id, - range_id, - minimap_id, - toggle_chat_id, - - // usually in the "rare controls bar" - chat_id, - inventory_id, - drop_id, - exit_id, - - // the joystick - joystick_off_id, - joystick_bg_id, - joystick_center_id, -}; - #define BUTTON_REPEAT_DELAY 0.5f #define BUTTON_REPEAT_INTERVAL 0.333f @@ -168,6 +123,9 @@ public: bool isStatusTextOverriden() { return m_overflow_open; } IGUIStaticText *getStatusText() { return m_status_text.get(); } + ButtonLayout getLayout() { return m_layout; } + void applyLayout(const ButtonLayout &layout); + private: IrrlichtDevice *m_device = nullptr; IGUIEnvironment *m_guienv = nullptr; @@ -233,13 +191,14 @@ private: void releaseAll(); // initialize a button + bool mayAddButton(touch_gui_button_id id); void addButton(std::vector &buttons, touch_gui_button_id id, const std::string &image, - const recti &rect, bool visible=true); + const recti &rect, bool visible); void addToggleButton(std::vector &buttons, touch_gui_button_id id, const std::string &image_1, const std::string &image_2, - const recti &rect, bool visible=true); + const recti &rect, bool visible); IGUIImage *makeButtonDirect(touch_gui_button_id id, const recti &rect, bool visible); @@ -268,6 +227,8 @@ private: bool m_place_pressed = false; u64 m_place_pressed_until = 0; + + ButtonLayout m_layout; }; extern TouchControls *g_touchcontrols; diff --git a/src/gui/touchscreeneditor.cpp b/src/gui/touchscreeneditor.cpp new file mode 100644 index 000000000..b30ce5a20 --- /dev/null +++ b/src/gui/touchscreeneditor.cpp @@ -0,0 +1,404 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 grorp, Gregor Parzefall + +#include "touchscreeneditor.h" +#include "touchcontrols.h" +#include "touchscreenlayout.h" + +#include "client/renderingengine.h" +#include "gettext.h" +#include "irr_gui_ptr.h" +#include "settings.h" + +#include "IGUIButton.h" +#include "IGUIFont.h" +#include "IGUIImage.h" +#include "IGUIStaticText.h" + +GUITouchscreenLayout::GUITouchscreenLayout(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, ISimpleTextureSource *tsrc +): + GUIModalMenu(env, parent, id, menumgr), + m_tsrc(tsrc) +{ + if (g_touchcontrols) + m_layout = g_touchcontrols->getLayout(); + else + m_layout = ButtonLayout::loadFromSettings(); + + m_gui_help_text = grab_gui_element(Environment->addStaticText( + L"", core::recti(), false, false, this, -1)); + m_gui_help_text->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER); + + m_gui_add_btn = grab_gui_element(Environment->addButton( + core::recti(), this, -1, wstrgettext("Add button").c_str())); + m_gui_reset_btn = grab_gui_element(Environment->addButton( + core::recti(), this, -1, wstrgettext("Reset").c_str())); + m_gui_done_btn = grab_gui_element(Environment->addButton( + core::recti(), this, -1, wstrgettext("Done").c_str())); + + m_gui_remove_btn = grab_gui_element(Environment->addButton( + core::recti(), this, -1, wstrgettext("Remove").c_str())); +} + +GUITouchscreenLayout::~GUITouchscreenLayout() +{ + ButtonLayout::clearTextureCache(); +} + +void GUITouchscreenLayout::regenerateGui(v2u32 screensize) +{ + DesiredRect = core::recti(0, 0, screensize.X, screensize.Y); + recalculateAbsolutePosition(false); + + s32 button_size = ButtonLayout::getButtonSize(screensize); + if (m_last_screensize != screensize || m_button_size != button_size) { + m_last_screensize = screensize; + m_button_size = button_size; + // Prevent interpolation when the layout scales. + clearGUIImages(); + } + + // Discard invalid selection. May happen when... + // 1. a button is removed. + // 2. adding a button fails and it disappears from the layout again. + if (m_selected_btn != touch_gui_button_id_END && + m_layout.layout.count(m_selected_btn) == 0) + m_selected_btn = touch_gui_button_id_END; + + if (m_mode == Mode::Add) + regenerateGUIImagesAddMode(screensize); + else + regenerateGUIImagesRegular(screensize); + regenerateMenu(screensize); +} + +void GUITouchscreenLayout::clearGUIImages() +{ + m_gui_images.clear(); + m_gui_images_target_pos.clear(); + + m_add_layout.layout.clear(); + m_add_button_titles.clear(); +} + +void GUITouchscreenLayout::regenerateGUIImagesRegular(v2u32 screensize) +{ + assert(m_mode != Mode::Add); + + auto old_gui_images = m_gui_images; + clearGUIImages(); + + for (const auto &[btn, meta] : m_layout.layout) { + core::recti rect = m_layout.getRect(btn, screensize, m_button_size, m_tsrc); + std::shared_ptr img; + + if (old_gui_images.count(btn) > 0) { + img = old_gui_images.at(btn); + // Update size, keep position. Position is interpolated in interpolateGUIImages. + img->setRelativePosition(core::recti( + img->getRelativePosition().UpperLeftCorner, rect.getSize())); + } else { + img = grab_gui_element(Environment->addImage(rect, this, -1)); + img->setImage(ButtonLayout::getTexture(btn, m_tsrc)); + img->setScaleImage(true); + } + + m_gui_images[btn] = img; + m_gui_images_target_pos[btn] = rect.UpperLeftCorner; + } +} + +void GUITouchscreenLayout::regenerateGUIImagesAddMode(v2u32 screensize) +{ + assert(m_mode == Mode::Add); + + clearGUIImages(); + + auto missing_buttons = m_layout.getMissingButtons(); + + layout_button_grid(screensize, m_tsrc, missing_buttons, + [&] (touch_gui_button_id btn, v2s32 pos, core::recti rect) { + auto img = grab_gui_element(Environment->addImage(rect, this, -1)); + img->setImage(ButtonLayout::getTexture(btn, m_tsrc)); + img->setScaleImage(true); + m_gui_images[btn] = img; + + ButtonMeta meta; + meta.setPos(pos, screensize, m_button_size); + m_add_layout.layout[btn] = meta; + + IGUIStaticText *text = Environment->addStaticText(L"", core::recti(), + false, false,this, -1); + make_button_grid_title(text, btn, pos, rect); + m_add_button_titles.push_back(grab_gui_element(text)); + }); +} + +void GUITouchscreenLayout::interpolateGUIImages() +{ + if (m_mode == Mode::Add) + return; + + for (auto &[btn, gui_image] : m_gui_images) { + bool interpolate = m_mode != Mode::Dragging || m_selected_btn != btn; + + v2s32 cur_pos_int = gui_image->getRelativePosition().UpperLeftCorner; + v2s32 tgt_pos_int = m_gui_images_target_pos.at(btn); + v2f cur_pos(cur_pos_int.X, cur_pos_int.Y); + v2f tgt_pos(tgt_pos_int.X, tgt_pos_int.Y); + + if (interpolate && cur_pos.getDistanceFrom(tgt_pos) > 2.0f) { + v2f pos = cur_pos.getInterpolated(tgt_pos, 0.5f); + gui_image->setRelativePosition(v2s32(core::round32(pos.X), core::round32(pos.Y))); + } else { + gui_image->setRelativePosition(tgt_pos_int); + } + } +} + +static void layout_menu_row(v2u32 screensize, + const std::vector> &row, + const std::vector> &full_row, bool bottom) +{ + s32 spacing = RenderingEngine::getDisplayDensity() * 4.0f; + + s32 btn_w = 0; + s32 btn_h = 0; + for (const auto &btn : full_row) { + IGUIFont *font = btn->getActiveFont(); + core::dimension2du dim = font->getDimension(btn->getText()); + btn_w = std::max(btn_w, (s32)(dim.Width * 1.5f)); + btn_h = std::max(btn_h, (s32)(dim.Height * 2.5f)); + } + + const s32 row_width = ((btn_w + spacing) * row.size()) - spacing; + s32 x = screensize.X / 2 - row_width / 2; + const s32 y = bottom ? screensize.Y - spacing - btn_h : spacing; + + for (const auto &btn : row) { + btn->setRelativePosition(core::recti( + v2s32(x, y), core::dimension2du(btn_w, btn_h))); + x += btn_w + spacing; + } +} + +void GUITouchscreenLayout::regenerateMenu(v2u32 screensize) +{ + bool have_selection = m_selected_btn != touch_gui_button_id_END; + + if (m_mode == Mode::Add) + m_gui_help_text->setText(wstrgettext("Start dragging a button to add. Tap outside to cancel.").c_str()); + else if (!have_selection) + m_gui_help_text->setText(wstrgettext("Tap a button to select it. Drag a button to move it.").c_str()); + else + m_gui_help_text->setText(wstrgettext("Tap outside to deselect.").c_str()); + + IGUIFont *font = m_gui_help_text->getActiveFont(); + core::dimension2du dim = font->getDimension(m_gui_help_text->getText()); + s32 height = dim.Height * 2.5f; + s32 pos_y = (m_mode == Mode::Add || have_selection) ? 0 : screensize.Y - height; + m_gui_help_text->setRelativePosition(core::recti( + v2s32(0, pos_y), + core::dimension2du(screensize.X, height))); + + bool normal_buttons_visible = m_mode != Mode::Add && !have_selection; + bool add_visible = normal_buttons_visible && !m_layout.getMissingButtons().empty(); + + m_gui_add_btn->setVisible(add_visible); + m_gui_reset_btn->setVisible(normal_buttons_visible); + m_gui_done_btn->setVisible(normal_buttons_visible); + + if (normal_buttons_visible) { + std::vector row1{m_gui_add_btn, m_gui_reset_btn, m_gui_done_btn}; + if (add_visible) { + layout_menu_row(screensize, row1, row1, false); + } else { + std::vector row1_reduced{m_gui_reset_btn, m_gui_done_btn}; + layout_menu_row(screensize, row1_reduced, row1, false); + } + } + + bool remove_visible = m_mode != Mode::Add && have_selection && + !ButtonLayout::isButtonRequired(m_selected_btn); + + m_gui_remove_btn->setVisible(remove_visible); + + if (remove_visible) { + std::vector row2{m_gui_remove_btn}; + layout_menu_row(screensize, row2, row2, true); + } +} + +void GUITouchscreenLayout::drawMenu() +{ + video::IVideoDriver *driver = Environment->getVideoDriver(); + + video::SColor bgcolor(140, 0, 0, 0); + video::SColor selection_color(255, 128, 128, 128); + video::SColor error_color(255, 255, 0, 0); + + driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + + // Done here instead of in OnPostRender to avoid drag&drop lagging behind + // input by one frame. + // Must be done before drawing selection rectangle. + interpolateGUIImages(); + + bool draw_selection = m_gui_images.count(m_selected_btn) > 0; + if (draw_selection) + driver->draw2DRectangle(selection_color, + m_gui_images.at(m_selected_btn)->getAbsolutePosition(), + &AbsoluteClippingRect); + + if (m_mode == Mode::Dragging) { + for (const auto &rect : m_error_rects) + driver->draw2DRectangle(error_color, rect, &AbsoluteClippingRect); + } + + IGUIElement::draw(); +} + +void GUITouchscreenLayout::updateDragState(v2u32 screensize, v2s32 mouse_movement) +{ + assert(m_mode == Mode::Dragging); + + core::recti rect = m_layout.getRect(m_selected_btn, screensize, m_button_size, m_tsrc); + rect += mouse_movement; + rect.constrainTo(core::recti(v2s32(0, 0), core::dimension2du(screensize))); + + ButtonMeta &meta = m_layout.layout.at(m_selected_btn); + meta.setPos(rect.getCenter(), screensize, m_button_size); + + rect = m_layout.getRect(m_selected_btn, screensize, m_button_size, m_tsrc); + + m_error_rects.clear(); + for (const auto &[other_btn, other_meta] : m_layout.layout) { + if (other_btn == m_selected_btn) + continue; + core::recti other_rect = m_layout.getRect(other_btn, screensize, m_button_size, m_tsrc); + if (other_rect.isRectCollided(rect)) + m_error_rects.push_back(other_rect); + } + if (m_error_rects.empty()) + m_last_good_layout = m_layout; +} + +bool GUITouchscreenLayout::OnEvent(const SEvent& event) +{ + if (event.EventType == EET_KEY_INPUT_EVENT) { + if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) { + quitMenu(); + return true; + } + } + + core::dimension2du screensize = Environment->getVideoDriver()->getScreenSize(); + + if (event.EventType == EET_MOUSE_INPUT_EVENT) { + v2s32 mouse_pos = v2s32(event.MouseInput.X, event.MouseInput.Y); + + switch (event.MouseInput.Event) { + case EMIE_LMOUSE_PRESSED_DOWN: { + m_mouse_down = true; + m_last_mouse_pos = mouse_pos; + + IGUIElement *el = Environment->getRootGUIElement()->getElementFromPoint(mouse_pos); + // Clicking on nothing deselects. + m_selected_btn = touch_gui_button_id_END; + for (const auto &[btn, gui_image] : m_gui_images) { + if (el == gui_image.get()) { + m_selected_btn = btn; + break; + } + } + + if (m_mode == Mode::Add) { + if (m_selected_btn != touch_gui_button_id_END) { + m_mode = Mode::Dragging; + m_last_good_layout = m_layout; + m_layout.layout[m_selected_btn] = m_add_layout.layout.at(m_selected_btn); + updateDragState(screensize, v2s32(0, 0)); + } else { + // Clicking on nothing quits add mode without adding a button. + m_mode = Mode::Default; + } + } + + regenerateGui(screensize); + return true; + } + case EMIE_MOUSE_MOVED: { + if (m_mouse_down && m_selected_btn != touch_gui_button_id_END) { + if (m_mode != Mode::Dragging) { + m_mode = Mode::Dragging; + m_last_good_layout = m_layout; + } + updateDragState(screensize, mouse_pos - m_last_mouse_pos); + + regenerateGui(screensize); + } + + m_last_mouse_pos = mouse_pos; + return true; + } + case EMIE_LMOUSE_LEFT_UP: { + m_mouse_down = false; + + if (m_mode == Mode::Dragging) { + m_mode = Mode::Default; + if (!m_error_rects.empty()) + m_layout = m_last_good_layout; + + regenerateGui(screensize); + } + + return true; + } + default: + break; + } + } + + if (event.EventType == EET_GUI_EVENT) { + switch (event.GUIEvent.EventType) { + case EGET_BUTTON_CLICKED: { + if (event.GUIEvent.Caller == m_gui_add_btn.get()) { + m_mode = Mode::Add; + regenerateGui(screensize); + return true; + } + + if (event.GUIEvent.Caller == m_gui_reset_btn.get()) { + m_layout = ButtonLayout::predefined; + regenerateGui(screensize); + return true; + } + + if (event.GUIEvent.Caller == m_gui_done_btn.get()) { + if (g_touchcontrols) + g_touchcontrols->applyLayout(m_layout); + std::ostringstream oss; + m_layout.serializeJson(oss); + g_settings->set("touch_layout", oss.str()); + quitMenu(); + return true; + } + + if (event.GUIEvent.Caller == m_gui_remove_btn.get()) { + m_layout.layout.erase(m_selected_btn); + regenerateGui(screensize); + return true; + } + + break; + } + default: + break; + } + } + + return Parent ? Parent->OnEvent(event) : false; +} diff --git a/src/gui/touchscreeneditor.h b/src/gui/touchscreeneditor.h new file mode 100644 index 000000000..dc06fb224 --- /dev/null +++ b/src/gui/touchscreeneditor.h @@ -0,0 +1,81 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 grorp, Gregor Parzefall + +#pragma once + +#include "touchscreenlayout.h" +#include "modalMenu.h" + +#include +#include + +class ISimpleTextureSource; +namespace irr::gui +{ + class IGUIImage; +} + +class GUITouchscreenLayout : public GUIModalMenu +{ +public: + GUITouchscreenLayout(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, ISimpleTextureSource *tsrc); + ~GUITouchscreenLayout(); + + void regenerateGui(v2u32 screensize); + void drawMenu(); + bool OnEvent(const SEvent& event); + +protected: + std::wstring getLabelByID(s32 id) { return L""; } + std::string getNameByID(s32 id) { return ""; } + +private: + ISimpleTextureSource *m_tsrc; + + ButtonLayout m_layout; + v2u32 m_last_screensize; + s32 m_button_size; + + enum class Mode { + Default, + Dragging, + Add, + }; + Mode m_mode = Mode::Default; + + std::unordered_map> m_gui_images; + // unused if m_mode == Mode::Add + std::unordered_map m_gui_images_target_pos; + void clearGUIImages(); + void regenerateGUIImagesRegular(v2u32 screensize); + void regenerateGUIImagesAddMode(v2u32 screensize); + void interpolateGUIImages(); + + // interaction state + bool m_mouse_down = false; + v2s32 m_last_mouse_pos; + touch_gui_button_id m_selected_btn = touch_gui_button_id_END; + + // dragging + ButtonLayout m_last_good_layout; + std::vector m_error_rects; + void updateDragState(v2u32 screensize, v2s32 mouse_movement); + + // add mode + ButtonLayout m_add_layout; + std::vector> m_add_button_titles; + + // Menu GUI elements + std::shared_ptr m_gui_help_text; + + std::shared_ptr m_gui_add_btn; + std::shared_ptr m_gui_reset_btn; + std::shared_ptr m_gui_done_btn; + + std::shared_ptr m_gui_remove_btn; + + void regenerateMenu(v2u32 screensize); +}; diff --git a/src/gui/touchscreenlayout.cpp b/src/gui/touchscreenlayout.cpp new file mode 100644 index 000000000..0a88f5dd5 --- /dev/null +++ b/src/gui/touchscreenlayout.cpp @@ -0,0 +1,345 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 grorp, Gregor Parzefall + +#include "touchscreenlayout.h" +#include "client/renderingengine.h" +#include "client/texturesource.h" +#include "convert_json.h" +#include "gettext.h" +#include "settings.h" +#include + +#include "IGUIFont.h" +#include "IGUIStaticText.h" + +const char *button_names[] = { + "jump", + "sneak", + "zoom", + "aux1", + "overflow", + + "chat", + "inventory", + "drop", + "exit", + + "fly", + "fast", + "noclip", + "debug", + "camera", + "range", + "minimap", + "toggle_chat", + + "joystick_off", + "joystick_bg", + "joystick_center", +}; + +// compare with GUIKeyChangeMenu::init_keys +const char *button_titles[] = { + N_("Jump"), + N_("Sneak"), + N_("Zoom"), + N_("Aux1"), + N_("Overflow menu"), + + N_("Chat"), + N_("Inventory"), + N_("Drop"), + N_("Exit"), + + N_("Toggle fly"), + N_("Toggle fast"), + N_("Toggle noclip"), + N_("Toggle debug"), + N_("Change camera"), + N_("Range select"), + N_("Toggle minimap"), + N_("Toggle chat log"), + + N_("Joystick"), + N_("Joystick"), + N_("Joystick"), +}; + +const char *button_image_names[] = { + "jump_btn.png", + "down.png", + "zoom.png", + "aux1_btn.png", + "overflow_btn.png", + + "chat_btn.png", + "inventory_btn.png", + "drop_btn.png", + "exit_btn.png", + + "fly_btn.png", + "fast_btn.png", + "noclip_btn.png", + "debug_btn.png", + "camera_btn.png", + "rangeview_btn.png", + "minimap_btn.png", + // toggle button: switches between "chat_hide_btn.png" and "chat_show_btn.png" + "chat_hide_btn.png", + + "joystick_off.png", + "joystick_bg.png", + "joystick_center.png", +}; + +v2s32 ButtonMeta::getPos(v2u32 screensize, s32 button_size) const +{ + return v2s32((position.X * screensize.X) + (offset.X * button_size), + (position.Y * screensize.Y) + (offset.Y * button_size)); +} + +void ButtonMeta::setPos(v2s32 pos, v2u32 screensize, s32 button_size) +{ + v2s32 third(screensize.X / 3, screensize.Y / 3); + + if (pos.X < third.X) + position.X = 0.0f; + else if (pos.X < 2 * third.X) + position.X = 0.5f; + else + position.X = 1.0f; + + if (pos.Y < third.Y) + position.Y = 0.0f; + else if (pos.Y < 2 * third.Y) + position.Y = 0.5f; + else + position.Y = 1.0f; + + offset.X = (pos.X - (position.X * screensize.X)) / button_size; + offset.Y = (pos.Y - (position.Y * screensize.Y)) / button_size; +} + +bool ButtonLayout::isButtonAllowed(touch_gui_button_id id) +{ + return id != joystick_off_id && id != joystick_bg_id && id != joystick_center_id && + id != touch_gui_button_id_END; +} + +bool ButtonLayout::isButtonRequired(touch_gui_button_id id) +{ + return id == overflow_id; +} + +s32 ButtonLayout::getButtonSize(v2u32 screensize) +{ + return std::min(screensize.Y / 4.5f, + RenderingEngine::getDisplayDensity() * 65.0f * + g_settings->getFloat("hud_scaling")); +} + +const ButtonLayout ButtonLayout::predefined {{ + {jump_id, { + v2f(1.0f, 1.0f), + v2f(-1.0f, -0.5f), + }}, + {sneak_id, { + v2f(1.0f, 1.0f), + v2f(-2.5f, -0.5f), + }}, + {zoom_id, { + v2f(1.0f, 1.0f), + v2f(-0.75f, -3.5f), + }}, + {aux1_id, { + v2f(1.0f, 1.0f), + v2f(-0.75f, -2.0f), + }}, + {overflow_id, { + v2f(1.0f, 1.0f), + v2f(-0.75f, -5.0f), + }}, +}}; + +ButtonLayout ButtonLayout::loadFromSettings() +{ + bool restored = false; + ButtonLayout layout; + + std::string str = g_settings->get("touch_layout"); + if (!str.empty()) { + std::istringstream iss(str); + try { + layout.deserializeJson(iss); + restored = true; + } catch (const Json::Exception &e) { + warningstream << "Could not parse touchscreen layout: " << e.what() << std::endl; + } + } + + if (!restored) + return predefined; + + return layout; +} + +std::unordered_map> ButtonLayout::texture_cache; + +video::ITexture *ButtonLayout::getTexture(touch_gui_button_id btn, ISimpleTextureSource *tsrc) +{ + if (texture_cache.count(btn) > 0) + return texture_cache.at(btn).get(); + + video::ITexture *tex = tsrc->getTexture(button_image_names[btn]); + if (!tex) + // necessary in the mainmenu + tex = tsrc->getTexture(porting::path_share + "/textures/base/pack/" + + button_image_names[btn]); + irr_ptr ptr; + ptr.grab(tex); + texture_cache[btn] = ptr; + return tex; +} + +void ButtonLayout::clearTextureCache() +{ + texture_cache.clear(); +} + +core::recti ButtonLayout::getRect(touch_gui_button_id btn, + v2u32 screensize, s32 button_size, ISimpleTextureSource *tsrc) +{ + const ButtonMeta &meta = layout.at(btn); + v2s32 pos = meta.getPos(screensize, button_size); + + v2u32 orig_size = getTexture(btn, tsrc)->getOriginalSize(); + v2s32 size((button_size * orig_size.X) / orig_size.Y, button_size); + + return core::recti(pos - size / 2, core::dimension2di(size)); +} + +std::vector ButtonLayout::getMissingButtons() +{ + std::vector missing_buttons; + for (u8 i = 0; i < touch_gui_button_id_END; i++) { + touch_gui_button_id btn = (touch_gui_button_id)i; + if (isButtonAllowed(btn) && layout.count(btn) == 0) + missing_buttons.push_back(btn); + } + return missing_buttons; +} + +void ButtonLayout::serializeJson(std::ostream &os) const +{ + Json::Value root = Json::objectValue; + root["layout"] = Json::objectValue; + + for (const auto &[id, meta] : layout) { + Json::Value button = Json::objectValue; + button["position_x"] = meta.position.X; + button["position_y"] = meta.position.Y; + button["offset_x"] = meta.offset.X; + button["offset_y"] = meta.offset.Y; + + root["layout"][button_names[id]] = button; + } + + fastWriteJson(root, os); +} + +static touch_gui_button_id button_name_to_id(const std::string &name) +{ + for (u8 i = 0; i < touch_gui_button_id_END; i++) { + if (name == button_names[i]) + return (touch_gui_button_id)i; + } + return touch_gui_button_id_END; +} + +void ButtonLayout::deserializeJson(std::istream &is) +{ + layout.clear(); + + Json::Value root; + is >> root; + + if (!root["layout"].isObject()) + throw Json::RuntimeError("invalid type for layout"); + + Json::Value &obj = root["layout"]; + Json::ValueIterator iter; + for (iter = obj.begin(); iter != obj.end(); iter++) { + touch_gui_button_id id = button_name_to_id(iter.name()); + if (!isButtonAllowed(id)) + throw Json::RuntimeError("invalid button name"); + + Json::Value &value = *iter; + if (!value.isObject()) + throw Json::RuntimeError("invalid type for button metadata"); + + ButtonMeta meta; + + if (!value["position_x"].isNumeric() || !value["position_y"].isNumeric()) + throw Json::RuntimeError("invalid type for position_x or position_y in button metadata"); + meta.position.X = value["position_x"].asFloat(); + meta.position.Y = value["position_y"].asFloat(); + + if (!value["offset_x"].isNumeric() || !value["offset_y"].isNumeric()) + throw Json::RuntimeError("invalid type for offset_x or offset_y in button metadata"); + meta.offset.X = value["offset_x"].asFloat(); + meta.offset.Y = value["offset_y"].asFloat(); + + layout.emplace(id, meta); + } +} + +void layout_button_grid(v2u32 screensize, ISimpleTextureSource *tsrc, + const std::vector &buttons, + // pos refers to the center of the button + const std::function &callback) +{ + s32 cols = 4; + s32 rows = 3; + f32 screen_aspect = (f32)screensize.X / (f32)screensize.Y; + while ((s32)buttons.size() > cols * rows) { + f32 aspect = (f32)cols / (f32)rows; + if (aspect > screen_aspect) + rows++; + else + cols++; + } + + s32 button_size = ButtonLayout::getButtonSize(screensize); + v2s32 spacing(screensize.X / (cols + 1), screensize.Y / (rows + 1)); + v2s32 pos(spacing); + + for (touch_gui_button_id btn : buttons) { + v2u32 orig_size = ButtonLayout::getTexture(btn, tsrc)->getOriginalSize(); + v2s32 size((button_size * orig_size.X) / orig_size.Y, button_size); + + core::recti rect(pos - size / 2, core::dimension2di(size)); + + if (rect.LowerRightCorner.X > (s32)screensize.X) { + pos.X = spacing.X; + pos.Y += spacing.Y; + rect = core::recti(pos - size / 2, core::dimension2di(size)); + } + + callback(btn, pos, rect); + + pos.X += spacing.X; + } +} + +void make_button_grid_title(gui::IGUIStaticText *text, touch_gui_button_id btn, v2s32 pos, core::recti rect) +{ + std::wstring str = wstrgettext(button_titles[btn]); + text->setText(str.c_str()); + gui::IGUIFont *font = text->getActiveFont(); + core::dimension2du dim = font->getDimension(str.c_str()); + dim = core::dimension2du(dim.Width * 1.25f, dim.Height * 1.25f); // avoid clipping + text->setRelativePosition(core::recti(pos.X - dim.Width / 2, rect.LowerRightCorner.Y, + pos.X + dim.Width / 2, rect.LowerRightCorner.Y + dim.Height)); + text->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); +} diff --git a/src/gui/touchscreenlayout.h b/src/gui/touchscreenlayout.h new file mode 100644 index 000000000..9c7d72fbf --- /dev/null +++ b/src/gui/touchscreenlayout.h @@ -0,0 +1,104 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 grorp, Gregor Parzefall + +#pragma once + +#include "irr_ptr.h" +#include "irrlichttypes_bloated.h" +#include "rect.h" +#include +#include + +class ISimpleTextureSource; +namespace irr::gui +{ + class IGUIStaticText; +} +namespace irr::video +{ + class ITexture; +} + +enum touch_gui_button_id : u8 +{ + jump_id = 0, + sneak_id, + zoom_id, + aux1_id, + overflow_id, + + // formerly "rare controls bar" + chat_id, + inventory_id, + drop_id, + exit_id, + + // formerly "settings bar" + fly_id, + fast_id, + noclip_id, + debug_id, + camera_id, + range_id, + minimap_id, + toggle_chat_id, + + // the joystick + joystick_off_id, + joystick_bg_id, + joystick_center_id, + + touch_gui_button_id_END, +}; + +extern const char *button_names[]; +extern const char *button_titles[]; +extern const char *button_image_names[]; + +struct ButtonMeta { + // Position, specified as a percentage of the screensize in the range [0,1]. + // The editor currently writes the values 0, 0.5 and 1. + v2f position; + // Offset, multiplied by the global button size before it is applied. + // Together, position and offset define the position of the button's center. + v2f offset; + + // Returns the button's effective center position in pixels. + v2s32 getPos(v2u32 screensize, s32 button_size) const; + // Sets the button's effective center position in pixels. + void setPos(v2s32 pos, v2u32 screensize, s32 button_size); +}; + +struct ButtonLayout { + static bool isButtonAllowed(touch_gui_button_id id); + static bool isButtonRequired(touch_gui_button_id id); + static s32 getButtonSize(v2u32 screensize); + static ButtonLayout loadFromSettings(); + + static video::ITexture *getTexture(touch_gui_button_id btn, ISimpleTextureSource *tsrc); + static void clearTextureCache(); + + std::unordered_map layout; + + core::recti getRect(touch_gui_button_id btn, + v2u32 screensize, s32 button_size, ISimpleTextureSource *tsrc); + + std::vector getMissingButtons(); + + void serializeJson(std::ostream &os) const; + void deserializeJson(std::istream &is); + + static const ButtonLayout predefined; + +private: + static std::unordered_map> texture_cache; +}; + +void layout_button_grid(v2u32 screensize, ISimpleTextureSource *tsrc, + const std::vector &buttons, + // pos refers to the center of the button. + const std::function &callback); + +void make_button_grid_title(gui::IGUIStaticText *text, + touch_gui_button_id btn,v2s32 pos, core::recti rect); diff --git a/src/inventory.cpp b/src/inventory.cpp index 0ba59ceea..246086d95 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -295,10 +295,8 @@ std::string ItemStack::getWieldOverlay(const IItemDefManager *itemdef) const v3f ItemStack::getWieldScale(const IItemDefManager *itemdef) const { std::string scale = metadata.getString("wield_scale"); - if (scale.empty()) - return getDefinition(itemdef).wield_scale; - return str_to_v3f(scale); + return str_to_v3f(scale).value_or(getDefinition(itemdef).wield_scale); } ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef) diff --git a/src/irr_gui_ptr.h b/src/irr_gui_ptr.h new file mode 100644 index 000000000..8c16a5b1e --- /dev/null +++ b/src/irr_gui_ptr.h @@ -0,0 +1,28 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 grorp, Gregor Parzefall + +#pragma once +#include +#include "IGUIElement.h" + +// We cannot use irr_ptr for Irrlicht GUI elements we own. +// Option 1: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr +// constructor. +// -> We steal the reference owned by IGUIEnvironment and drop it later, +// causing the IGUIElement to be deleted while IGUIEnvironment still +// references it. +// Option 2: Pass IGUIElement* returned by IGUIEnvironment::add* into irr_ptr::grab. +// -> We add another reference and drop it later, but since IGUIEnvironment +// still references the IGUIElement, it is never deleted. +// To make IGUIEnvironment drop its reference to the IGUIElement, we have to call +// IGUIElement::remove, so that's what we'll do. +template +std::shared_ptr grab_gui_element(T *element) +{ + static_assert(std::is_base_of_v, + "grab_gui_element only works for IGUIElement"); + return std::shared_ptr(element, [](T *e) { + e->remove(); + }); +} diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index 8337390c1..73962605d 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -254,9 +254,6 @@ CGUITTFont::CGUITTFont(IGUIEnvironment *env) batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0), shadow_offset(0), shadow_alpha(0), fallback(0) { - #ifdef _DEBUG - setDebugName("CGUITTFont"); - #endif if (Environment) { diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp index eba397963..d06419e1f 100644 --- a/src/irrlicht_changes/static_text.cpp +++ b/src/irrlicht_changes/static_text.cpp @@ -30,10 +30,6 @@ StaticText::StaticText(const EnrichedString &text, bool border, RestrainTextInside(true), RightToLeft(false), OverrideFont(0), LastBreakFont(0) { - #ifdef _DEBUG - setDebugName("StaticText"); - #endif - setText(text); } diff --git a/src/main.cpp b/src/main.cpp index 6d30b2f07..2d0c2aa79 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,7 @@ #include "config.h" #include "player.h" #include "porting.h" +#include "serialization.h" // SER_FMT_VER_HIGHEST_* #include "network/socket.h" #include "mapblock.h" #if USE_CURSES diff --git a/src/map.cpp b/src/map.cpp index 1af8684e1..240788944 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -766,7 +766,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, VoxelArea block_area_nodes (p_min*MAP_BLOCKSIZE, (p_max+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); - u32 size_MB = block_area_nodes.getVolume()*4/1000000; + u32 size_MB = block_area_nodes.getVolume() * sizeof(MapNode) / 1000000U; if(size_MB >= 1) { infostream<<"initialEmerge: area: "; @@ -855,7 +855,7 @@ MMVManip *MMVManip::clone() const { MMVManip *ret = new MMVManip(); - const s32 size = m_area.getVolume(); + const u32 size = m_area.getVolume(); ret->m_area = m_area; if (m_data) { ret->m_data = new MapNode[size]; diff --git a/src/map.h b/src/map.h index bbe91f5d6..37d1a713d 100644 --- a/src/map.h +++ b/src/map.h @@ -79,7 +79,7 @@ struct MapEditEvent VoxelArea a; for (v3s16 p : modified_blocks) { v3s16 np1 = p*MAP_BLOCKSIZE; - v3s16 np2 = np1 + v3s16(1,1,1)*MAP_BLOCKSIZE - v3s16(1,1,1); + v3s16 np2 = np1 + v3s16(MAP_BLOCKSIZE-1); a.addPoint(np1); a.addPoint(np2); } diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 9ead0fa5a..f68657d9d 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -307,11 +307,9 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported_write(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); - FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialization version error"); - std::ostringstream os_raw(std::ios_base::binary); std::ostream &os = version >= 29 ? os_raw : os_compressed; @@ -423,7 +421,7 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os) void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported_read(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); TRACESTREAM(<<"MapBlock::deSerialize "<perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 index2d = 0; // Biomemap index for (s16 z = nmin.Z; z <= nmax.Z; z++) @@ -84,7 +84,7 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, u16 depth_riverbed = biome->depth_riverbed; u16 nplaced = 0; - s16 biome_y_min = m_bmgn->getNextTransitionY(nmax.Y); + s16 biome_y_next = m_bmgn->getNextTransitionY(nmax.Y); // Don't excavate the overgenerated stone at nmax.Y + 1, // this creates a 'roof' over the tunnel, preventing light in @@ -94,13 +94,13 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, index3d -= m_ystride, VoxelArea::add_y(em, vi, -1)) { // We need this check to make sure that biomes don't generate too far down - if (y < biome_y_min) { + if (y <= biome_y_next) { biome = m_bmgn->getBiomeAtIndex(index2d, v3s16(x, y, z)); - biome_y_min = m_bmgn->getNextTransitionY(y); + biome_y_next = m_bmgn->getNextTransitionY(y); if (x == nmin.X && z == nmin.Z && false) { dstream << "cavegen: biome at " << y << " is " << biome->name - << ", next at " << biome_y_min << std::endl; + << ", next at " << biome_y_next << std::endl; } } @@ -230,7 +230,7 @@ bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax) //// Place nodes bool near_cavern = false; - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 index2d = 0; for (s16 z = nmin.Z; z <= nmax.Z; z++) diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index 948cc62b1..249c462ba 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -127,7 +127,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) void DungeonGen::makeDungeon(v3s16 start_padding) { - const v3s16 &areasize = vm->m_area.getExtent(); + const v3s32 &areasize = vm->m_area.getExtent(); v3s16 roomsize; v3s16 roomplace; diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index dd416a3e4..3778c3807 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -240,7 +240,7 @@ u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) // Returns -MAX_MAP_GENERATION_LIMIT if not found s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) { - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y); s16 y; @@ -258,7 +258,7 @@ s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) // Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax) { - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y); s16 y; @@ -296,7 +296,7 @@ void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax) void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, std::vector &floors, std::vector &ceilings) { - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); bool is_walkable = false; u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y); @@ -320,7 +320,7 @@ void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, } -inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em) +inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s32 em) { u32 vi_neg_x = vi; VoxelArea::add_x(em, vi_neg_x, -1); @@ -357,7 +357,7 @@ void Mapgen::updateLiquid(UniqueQueue *trans_liquid, v3s16 nmin, v3s16 nm { bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed; content_t was_n; - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); isignored = true; isliquid = false; @@ -481,7 +481,7 @@ void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow) //TimeTaker t("propagateSunlight"); VoxelArea a(nmin, nmax); bool block_is_underground = (water_level >= nmax.Y); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); // NOTE: Direct access to the low 4 bits of param1 is okay here because, // by definition, sunlight will never be in the night lightbank. @@ -629,7 +629,7 @@ void MapgenBasic::generateBiomes() assert(biomegen); assert(biomemap); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 index = 0; noise_filler_depth->perlinMap2D(node_min.X, node_min.Z); @@ -644,7 +644,7 @@ void MapgenBasic::generateBiomes() u16 depth_riverbed = 0; u32 vi = vm->m_area.index(x, node_max.Y, z); - s16 biome_y_min = biomegen->getNextTransitionY(node_max.Y); + s16 biome_y_next = biomegen->getNextTransitionY(node_max.Y); // Check node at base of mapchunk above, either a node of a previously // generated mapchunk or if not, a node of overgenerated base terrain. @@ -661,23 +661,29 @@ void MapgenBasic::generateBiomes() for (s16 y = node_max.Y; y >= node_min.Y; y--) { content_t c = vm->m_data[vi].getContent(); + const bool biome_outdated = !biome || y <= biome_y_next; // Biome is (re)calculated: // 1. At the surface of stone below air or water. // 2. At the surface of water below air. // 3. When stone or water is detected but biome has not yet been calculated. // 4. When stone or water is detected just below a biome's lower limit. bool is_stone_surface = (c == c_stone) && - (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4 + (air_above || water_above || biome_outdated); // 1, 3, 4 bool is_water_surface = (c == c_water_source || c == c_river_water_source) && - (air_above || !biome || y < biome_y_min); // 2, 3, 4 + (air_above || biome_outdated); // 2, 3, 4 if (is_stone_surface || is_water_surface) { - if (!biome || y < biome_y_min) { + if (biome_outdated) { // (Re)calculate biome biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z)); - biome_y_min = biomegen->getNextTransitionY(y); + biome_y_next = biomegen->getNextTransitionY(y); + + if (x == node_min.X && z == node_min.Z && false) { + dstream << "biomegen: biome at " << y << " is " << biome->name + << ", next at " << biome_y_next << std::endl; + } } // Add biome to biomemap at first stone surface detected @@ -768,7 +774,7 @@ void MapgenBasic::dustTopNodes() if (node_max.Y < water_level) return; - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index 5e5ff9f3c..a81b9a361 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -261,7 +261,7 @@ private: // isLiquidHorizontallyFlowable() is a helper function for updateLiquid() // that checks whether there are floodable nodes without liquid beneath // the node at index vi. - inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em); + inline bool isLiquidHorizontallyFlowable(u32 vi, v3s32 em); }; /* diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index ba54a3672..46048b51f 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -445,7 +445,7 @@ int MapgenCarpathian::generateTerrain() noise_rivers->perlinMap2D(node_min.X, node_min.Z); //// Place nodes - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 index2d = 0; diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index f0e7984d5..e96e4e45a 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -278,7 +278,7 @@ s16 MapgenFlat::generateTerrain() MapNode n_stone(c_stone); MapNode n_water(c_water_source); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 ni2d = 0; diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index dcd62f63a..940fe0083 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -103,8 +103,17 @@ void MapgenFractalParams::readParams(const Settings *settings) settings->getS16NoEx("mgfractal_dungeon_ymax", dungeon_ymax); settings->getU16NoEx("mgfractal_fractal", fractal); settings->getU16NoEx("mgfractal_iterations", iterations); - settings->getV3FNoEx("mgfractal_scale", scale); - settings->getV3FNoEx("mgfractal_offset", offset); + + std::optional mgfractal_scale; + if (settings->getV3FNoEx("mgfractal_scale", mgfractal_scale) && mgfractal_scale.has_value()) { + scale = *mgfractal_scale; + } + + std::optional mgfractal_offset; + if (settings->getV3FNoEx("mgfractal_offset", mgfractal_offset) && mgfractal_offset.has_value()) { + offset = *mgfractal_offset; + } + settings->getFloatNoEx("mgfractal_slice_w", slice_w); settings->getFloatNoEx("mgfractal_julia_x", julia_x); settings->getFloatNoEx("mgfractal_julia_y", julia_y); diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 2f5a3cf7a..44243618e 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -222,7 +222,7 @@ void MapgenV6Params::setDefaultSettings(Settings *settings) // Returns Y one under area minimum if not found s16 MapgenV6::find_stone_level(v2s16 p2d) { - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); s16 y_nodes_max = vm->m_area.MaxEdge.Y; s16 y_nodes_min = vm->m_area.MinEdge.Y; u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y); @@ -670,7 +670,7 @@ int MapgenV6::generateGround() BiomeV6Type bt = getBiome(v2s16(x, z)); // Fill ground with stone - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 i = vm->m_area.index(x, node_min.Y, z); for (s16 y = node_min.Y; y <= node_max.Y; y++) { if (vm->m_data[i].getContent() == CONTENT_IGNORE) { @@ -739,7 +739,7 @@ void MapgenV6::addMud() // Add mud on ground s16 mudcount = 0; - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); s16 y_start = surface_y + 1; u32 i = vm->m_area.index(x, y_start, z); for (s16 y = y_start; y <= node_max.Y; y++) { @@ -757,7 +757,7 @@ void MapgenV6::addMud() void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) { - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); static const v3s16 dirs4[4] = { v3s16(0, 0, 1), // Back v3s16(1, 0, 0), // Right @@ -870,7 +870,7 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) void MapgenV6::moveMud(u32 remove_index, u32 place_index, - u32 above_remove_index, v2s16 pos, v3s16 em) + u32 above_remove_index, v2s16 pos, v3s32 em) { MapNode n_air(CONTENT_AIR); // Copy mud from old place to new place @@ -920,7 +920,7 @@ void MapgenV6::placeTreesAndJungleGrass() if (c_junglegrass == CONTENT_IGNORE) c_junglegrass = CONTENT_AIR; MapNode n_junglegrass(c_junglegrass); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); // Divide area into parts s16 div = 8; @@ -1027,7 +1027,7 @@ void MapgenV6::growGrass() // Add surface nodes MapNode n_dirt_with_grass(c_dirt_with_grass); MapNode n_dirt_with_snow(c_dirt_with_snow); MapNode n_snowblock(c_snowblock); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 index = 0; for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++) diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index 30b31f84a..6d776665a 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -150,7 +150,7 @@ public: void addMud(); void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos); void moveMud(u32 remove_index, u32 place_index, - u32 above_remove_index, v2s16 pos, v3s16 em); + u32 above_remove_index, v2s16 pos, v3s32 em); void growGrass(); void placeTreesAndJungleGrass(); virtual void generateCaves(int max_stone_y); diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index 491a1514a..fe052f3b7 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -523,7 +523,7 @@ int MapgenV7::generateTerrain() } //// Place nodes - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 index2d = 0; diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index 196454642..55185c445 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -344,7 +344,7 @@ int MapgenValleys::generateTerrain() noise_inter_valley_fill->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 index_2d = 0; diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index a1c4f5810..a0bb6dee2 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -25,21 +25,7 @@ BiomeManager::BiomeManager(Server *server) : // Create default biome to be used in case none exist Biome *b = new Biome; - b->name = "default"; - b->flags = 0; - b->depth_top = 0; - b->depth_filler = -MAX_MAP_GENERATION_LIMIT; - b->depth_water_top = 0; - b->depth_riverbed = 0; - b->min_pos = v3s16(-MAX_MAP_GENERATION_LIMIT, - -MAX_MAP_GENERATION_LIMIT, -MAX_MAP_GENERATION_LIMIT); - b->max_pos = v3s16(MAX_MAP_GENERATION_LIMIT, - MAX_MAP_GENERATION_LIMIT, MAX_MAP_GENERATION_LIMIT); - b->heat_point = 0.0; - b->humidity_point = 0.0; - b->vertical_blend = 0; - b->weight = 1.0f; b->m_nodenames.emplace_back("mapgen_stone"); b->m_nodenames.emplace_back("mapgen_stone"); @@ -64,11 +50,13 @@ void BiomeManager::clear() { EmergeManager *emerge = m_server->getEmergeManager(); - // Remove all dangling references in Decorations - DecorationManager *decomgr = emerge->getWritableDecorationManager(); - for (size_t i = 0; i != decomgr->getNumObjects(); i++) { - Decoration *deco = (Decoration *)decomgr->getRaw(i); - deco->biomes.clear(); + if (emerge) { + // Remove all dangling references in Decorations + DecorationManager *decomgr = emerge->getWritableDecorationManager(); + for (size_t i = 0; i != decomgr->getNumObjects(); i++) { + Decoration *deco = (Decoration *)decomgr->getRaw(i); + deco->biomes.clear(); + } } // Don't delete the first biome @@ -141,7 +129,10 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, for (size_t i = 0; i < m_bmgr->getNumObjects(); i++) { Biome *b = (Biome *)m_bmgr->getRaw(i); values.push_back(b->max_pos.Y); - values.push_back(b->min_pos.Y); + // We scan for biomes from high Y to low Y (top to bottom). Hence, + // biomes effectively transition at (min_pos.Y - 1). + if (b->min_pos.Y > -MAX_MAP_GENERATION_LIMIT) + values.push_back(b->min_pos.Y - 1); } std::sort(values.begin(), values.end(), std::greater<>()); @@ -299,8 +290,6 @@ ObjDef *Biome::clone() const ObjDef::cloneTo(obj); NodeResolver::cloneTo(obj); - obj->flags = flags; - obj->c_top = c_top; obj->c_filler = c_filler; obj->c_stone = c_stone; diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h index 3e9de89d9..8adce5db6 100644 --- a/src/mapgen/mg_biome.h +++ b/src/mapgen/mg_biome.h @@ -19,6 +19,12 @@ class BiomeManager; typedef u16 biome_t; +constexpr v3s16 MAX_MAP_GENERATION_LIMIT_V3( + MAX_MAP_GENERATION_LIMIT, + MAX_MAP_GENERATION_LIMIT, + MAX_MAP_GENERATION_LIMIT +); + #define BIOME_NONE ((biome_t)0) enum BiomeType { @@ -29,32 +35,32 @@ class Biome : public ObjDef, public NodeResolver { public: ObjDef *clone() const; - u32 flags; - - content_t c_top; - content_t c_filler; - content_t c_stone; - content_t c_water_top; - content_t c_water; - content_t c_river_water; - content_t c_riverbed; - content_t c_dust; + content_t + c_top = CONTENT_IGNORE, + c_filler = CONTENT_IGNORE, + c_stone = CONTENT_IGNORE, + c_water_top = CONTENT_IGNORE, + c_water = CONTENT_IGNORE, + c_river_water = CONTENT_IGNORE, + c_riverbed = CONTENT_IGNORE, + c_dust = CONTENT_IGNORE; std::vector c_cave_liquid; - content_t c_dungeon; - content_t c_dungeon_alt; - content_t c_dungeon_stair; + content_t + c_dungeon = CONTENT_IGNORE, + c_dungeon_alt = CONTENT_IGNORE, + c_dungeon_stair = CONTENT_IGNORE; - s16 depth_top; - s16 depth_filler; - s16 depth_water_top; - s16 depth_riverbed; + s16 depth_top = 0; + s16 depth_filler = -MAX_MAP_GENERATION_LIMIT; + s16 depth_water_top = 0; + s16 depth_riverbed = 0; - v3s16 min_pos; - v3s16 max_pos; - float heat_point; - float humidity_point; - s16 vertical_blend; - float weight; + v3s16 min_pos = -MAX_MAP_GENERATION_LIMIT_V3; + v3s16 max_pos = MAX_MAP_GENERATION_LIMIT_V3; + float heat_point = 0.0f; + float humidity_point = 0.0f; + s16 vertical_blend = 0; + float weight = 1.0f; virtual void resolveNodeNames(); }; @@ -191,7 +197,8 @@ private: Noise *noise_heat_blend; Noise *noise_humidity_blend; - // ordered descending + /// Y values at which biomes may transition. + /// This array may only be used for downwards scanning! std::vector m_transitions_y; }; diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 60183d4e4..e8f381ec6 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -346,7 +346,7 @@ size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) pr->range(deco_param2, deco_param2_max) : deco_param2; bool force_placement = (flags & DECO_FORCE_PLACEMENT); - const v3s16 &em = vm->m_area.getExtent(); + const v3s32 &em = vm->m_area.getExtent(); u32 vi = vm->m_area.index(p); if (ceiling) { diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp index 483ec4797..8198cf266 100644 --- a/src/mapgen/treegen.cpp +++ b/src/mapgen/treegen.cpp @@ -23,8 +23,7 @@ void TreeDef::resolveNodeNames() getIdFromNrBacklog(&leavesnode.param0, "", CONTENT_IGNORE); if (leaves2_chance) getIdFromNrBacklog(&leaves2node.param0, "", CONTENT_IGNORE); - if (fruit_chance) - getIdFromNrBacklog(&fruitnode.param0, "", CONTENT_IGNORE); + getIdFromNrBacklog(&fruitnode.param0, "", CONTENT_IGNORE); } /* @@ -78,7 +77,7 @@ void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2)); Buffer leaves_d(leaves_a.getVolume()); - for (s32 i = 0; i < leaves_a.getVolume(); i++) + for (u32 i = 0; i < leaves_d.getSize(); i++) leaves_d[i] = 0; // Force leaves at near the end of the trunk @@ -698,9 +697,8 @@ void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, p1.Y -= 1; VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3)); - //SharedPtr leaves_d(new u8[leaves_a.getVolume()]); Buffer leaves_d(leaves_a.getVolume()); - for (s32 i = 0; i < leaves_a.getVolume(); i++) + for (u32 i = 0; i < leaves_d.getSize(); i++) leaves_d[i] = 0; // Force leaves at near the end of the trunk @@ -789,7 +787,7 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3)); Buffer leaves_d(leaves_a.getVolume()); - for (s32 i = 0; i < leaves_a.getVolume(); i++) + for (u32 i = 0; i < leaves_d.getSize(); i++) leaves_d[i] = 0; // Upper branches @@ -876,4 +874,15 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef, } } +std::string error_to_string(error e) +{ + switch (e) { + case SUCCESS: + return "success"; + case UNBALANCED_BRACKETS: + return "closing ']' has no matching opening bracket"; + } + return "unknown error"; +} + }; // namespace treegen diff --git a/src/mapgen/treegen.h b/src/mapgen/treegen.h index 159063eb3..dba7ea047 100644 --- a/src/mapgen/treegen.h +++ b/src/mapgen/treegen.h @@ -68,4 +68,7 @@ namespace treegen { treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, const TreeDef &def); // Helper to spawn it directly on map treegen::error spawn_ltree(ServerMap *map, v3s16 p0, const TreeDef &def); + + // Helper to get a string from the error message + std::string error_to_string(error e); }; // namespace treegen diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 01f52d931..5f706a489 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -8,7 +8,7 @@ #include "nodedef.h" #include "map.h" #include "content_mapnode.h" // For mapnode_translate_*_internal -#include "serialization.h" // For ser_ver_supported +#include "serialization.h" // For ser_ver_supported_* #include "util/serialize.h" #include "log.h" #include "util/directiontables.h" @@ -620,7 +620,7 @@ s8 MapNode::addLevel(const NodeDefManager *nodemgr, s16 add) u32 MapNode::serializedLength(u8 version) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported_read(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); if (version == 0) @@ -636,7 +636,7 @@ u32 MapNode::serializedLength(u8 version) } void MapNode::serialize(u8 *dest, u8 version) const { - if(!ser_ver_supported(version)) + if (!ser_ver_supported_write(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); // Can't do this anymore; we have 16-bit dynamically allocated node IDs @@ -651,7 +651,7 @@ void MapNode::serialize(u8 *dest, u8 version) const } void MapNode::deSerialize(u8 *source, u8 version) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported_read(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); if(version <= 21) @@ -679,18 +679,12 @@ Buffer MapNode::serializeBulk(int version, const MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width) { - if (!ser_ver_supported(version)) + if (!ser_ver_supported_write(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); sanity_check(content_width == 2); sanity_check(params_width == 2); - // Can't do this anymore; we have 16-bit dynamically allocated node IDs - // in memory; conversion just won't work in this direction. - if (version < 24) - throw SerializationError("MapNode::serializeBulk: serialization to " - "version < 24 not possible"); - Buffer databuf(nodecount * (content_width + params_width)); // Writing to the buffer linearly is faster @@ -712,13 +706,13 @@ void MapNode::deSerializeBulk(std::istream &is, int version, MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width) { - if(!ser_ver_supported(version)) + if (!ser_ver_supported_read(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); if (version < 22 || (content_width != 1 && content_width != 2) || params_width != 2) - FATAL_ERROR("Deserialize bulk node data error"); + throw SerializationError("Deserialize bulk node data error"); // read data const u32 len = nodecount * (content_width + params_width); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 7a5d34dde..e69718533 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -4,6 +4,7 @@ #include "client/client.h" +#include "exceptions.h" #include "irr_v2d.h" #include "util/base64.h" #include "client/camera.h" @@ -27,7 +28,7 @@ #include "script/scripting_client.h" #include "util/serialize.h" #include "util/srp.h" -#include "util/sha1.h" +#include "util/hashing.h" #include "tileanimation.h" #include "gettext.h" #include "skyparams.h" @@ -62,7 +63,7 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) if (pkt->getSize() < 1) return; - u8 serialization_ver; + u8 serialization_ver; // negotiated value u16 proto_ver; u16 unused_compression_mode; u32 auth_mechs; @@ -79,9 +80,9 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) << ", proto_ver=" << proto_ver << ". Doing auth with mech " << chosen_auth_mechanism << std::endl; - if (!ser_ver_supported(serialization_ver)) { + if (!ser_ver_supported_read(serialization_ver)) { infostream << "Client: TOCLIENT_HELLO: Server sent " - << "unsupported ser_fmt_ver"<< std::endl; + << "unsupported ser_fmt_ver=" << (int)serialization_ver << std::endl; return; } @@ -1007,6 +1008,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) p.amount = readU16(is); p.time = readF32(is); + if (p.time < 0) + throw SerializationError("particle spawner time < 0"); bool missing_end_values = false; if (m_proto_ver >= 42) { @@ -1642,12 +1645,7 @@ void Client::handleCommand_MediaPush(NetworkPacket *pkt) if (!filedata.empty()) { // LEGACY CODEPATH // Compute and check checksum of data - std::string computed_hash; - { - SHA1 ctx; - ctx.addBytes(filedata); - computed_hash = ctx.getDigest(); - } + std::string computed_hash = hashing::sha1(filedata); if (raw_hash != computed_hash) { verbosestream << "Hash of file data mismatches, ignoring." << std::endl; return; diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index a8b4953c7..6b4cf791d 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -60,9 +60,12 @@ Support float animation frame numbers in TOCLIENT_LOCAL_PLAYER_ANIMATIONS Add beta_r0, vignette, specular intensity, foliage translucency and cdl parameters to Lighting packets [scheduled bump for 5.10.0] + PROTOCOL VERSION 47 + Add particle blend mode "clip" + [scheduled bump for 5.11.0] */ -const u16 LATEST_PROTOCOL_VERSION = 46; +const u16 LATEST_PROTOCOL_VERSION = 47; // See also formspec [Version History] in doc/lua_api.md const u16 FORMSPEC_API_VERSION = 8; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 47fd8ef5b..cf1dcacb1 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -12,6 +12,7 @@ #include "remoteplayer.h" #include "rollback_interface.h" #include "scripting_server.h" +#include "serialization.h" #include "settings.h" #include "tool.h" #include "version.h" @@ -83,34 +84,27 @@ void Server::handleCommand_Init(NetworkPacket* pkt) if (denyIfBanned(peer_id)) return; - // First byte after command is maximum supported - // serialization version - u8 client_max; + u8 max_ser_ver; // SER_FMT_VER_HIGHEST_READ (of client) u16 unused; - u16 min_net_proto_version = 0; + u16 min_net_proto_version; u16 max_net_proto_version; std::string playerName; - *pkt >> client_max >> unused >> min_net_proto_version - >> max_net_proto_version >> playerName; + *pkt >> max_ser_ver >> unused + >> min_net_proto_version >> max_net_proto_version + >> playerName; - u8 our_max = SER_FMT_VER_HIGHEST_READ; // Use the highest version supported by both - u8 depl_serial_v = std::min(client_max, our_max); - // If it's lower than the lowest supported, give up. -#if SER_FMT_VER_LOWEST_READ > 0 - if (depl_serial_v < SER_FMT_VER_LOWEST_READ) - depl_serial_v = SER_FMT_VER_INVALID; -#endif + const u8 serialization_ver = std::min(max_ser_ver, SER_FMT_VER_HIGHEST_WRITE); - if (depl_serial_v == SER_FMT_VER_INVALID) { + if (!ser_ver_supported_write(serialization_ver)) { actionstream << "Server: A mismatched client tried to connect from " << - addr_s << " ser_fmt_max=" << (int)client_max << std::endl; + addr_s << " ser_fmt_max=" << (int)serialization_ver << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION); return; } - client->setPendingSerializationVersion(depl_serial_v); + client->setPendingSerializationVersion(serialization_ver); /* Read and check network protocol version @@ -263,8 +257,9 @@ void Server::handleCommand_Init(NetworkPacket* pkt) NetworkPacket resp_pkt(TOCLIENT_HELLO, 0, peer_id); - resp_pkt << depl_serial_v << u16(0) << net_proto_version - << auth_mechs << std::string_view(); + resp_pkt << serialization_ver << u16(0) /* unused */ + << net_proto_version + << auth_mechs << std::string_view() /* unused */; Send(&resp_pkt); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 2301b179f..2f0307d11 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -13,6 +13,7 @@ #include "client/texturesource.h" #include "client/tile.h" #include +#include #endif #include "log.h" #include "settings.h" @@ -943,14 +944,23 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc palette = tsrc->getPalette(palette_name); if (drawtype == NDT_MESH && !mesh.empty()) { - // Meshnode drawtype // Read the mesh and apply scale mesh_ptr = client->getMesh(mesh); if (mesh_ptr) { v3f scale = v3f(BS) * visual_scale; scaleMesh(mesh_ptr, scale); recalculateBoundingBox(mesh_ptr); - meshmanip->recalculateNormals(mesh_ptr, true, false); + if (!checkMeshNormals(mesh_ptr)) { + infostream << "ContentFeatures: recalculating normals for mesh " + << mesh << std::endl; + meshmanip->recalculateNormals(mesh_ptr, true, false); + } else { + // Animation is not supported, but we need to reset it to + // default state if it is animated. + // Note: recalculateNormals() also does this hence the else-block + if (mesh_ptr->getMeshType() == scene::EAMT_SKINNED) + ((scene::SkinnedMesh*) mesh_ptr)->resetAnimation(); + } } } } diff --git a/src/nodedef.h b/src/nodedef.h index c3f88ce83..ab3362872 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -222,6 +222,7 @@ enum NodeDrawType : u8 NDT_MESH, // Combined plantlike-on-solid NDT_PLANTLIKE_ROOTED, + // Dummy for validity check NodeDrawType_END }; diff --git a/src/particles.cpp b/src/particles.cpp index 2c470d2d3..660ea8d2f 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -190,8 +190,10 @@ void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, FlagT flags = 0; if (animated) flags |= FlagT(ParticleTextureFlags::animated); - if (blendmode != BlendMode::alpha) - flags |= FlagT(blendmode) << 1; + // Default to `blend = "alpha"` for older clients that don't support `blend = "clip"` + auto sent_blendmode = (protocol_ver < 47 && blendmode == BlendMode::clip) + ? BlendMode::alpha : blendmode; + flags |= FlagT(sent_blendmode) << 1; serializeParameterValue(os, flags); alpha.serialize(os); @@ -215,6 +217,8 @@ void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, animated = !!(flags & FlagT(ParticleTextureFlags::animated)); blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1); + if (blendmode >= BlendMode::BlendMode_END) + throw SerializationError("invalid blend mode"); alpha.deSerialize(is); scale.deSerialize(is); diff --git a/src/particles.h b/src/particles.h index 52fe26661..00d04dbeb 100644 --- a/src/particles.h +++ b/src/particles.h @@ -233,7 +233,8 @@ namespace ParticleParamTypes } enum class AttractorKind : u8 { none, point, line, plane }; - enum class BlendMode : u8 { alpha, add, sub, screen }; + // Note: Allows at most 8 enum members (due to how this is serialized) + enum class BlendMode : u8 { alpha, add, sub, screen, clip, BlendMode_END }; // these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations using v3fRange = RangedParameter; diff --git a/src/porting.cpp b/src/porting.cpp index f0e2bee0b..faef75b7c 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -688,6 +688,10 @@ void initializePaths() #endif // RUN_IN_PLACE + assert(!path_share.empty()); + assert(!path_user.empty()); + assert(!path_cache.empty()); + infostream << "Detected share path: " << path_share << std::endl; infostream << "Detected user path: " << path_user << std::endl; infostream << "Detected cache path: " << path_cache << std::endl; diff --git a/src/porting.h b/src/porting.h index edbc236a8..d6cec6c1a 100644 --- a/src/porting.h +++ b/src/porting.h @@ -109,8 +109,7 @@ extern std::string path_cache; bool getCurrentExecPath(char *buf, size_t len); /* - Get full path of stuff in data directory. - Example: "stone.png" -> "../data/stone.png" + Concatenate subpath to path_share. */ std::string getDataPath(const char *subpath); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 6367e53f3..5f000acd8 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -2032,12 +2032,11 @@ bool read_tree_def(lua_State *L, int idx, const NodeDefManager *ndef, getstringfield(L, idx, "trunk_type", tree_def.trunk_type); getboolfield(L, idx, "thin_branches", tree_def.thin_branches); tree_def.fruit_chance = 0; + fruit = "air"; getstringfield(L, idx, "fruit", fruit); - if (!fruit.empty()) { + if (!fruit.empty()) getintfield(L, idx, "fruit_chance", tree_def.fruit_chance); - if (tree_def.fruit_chance) - tree_def.m_nodenames.push_back(fruit); - } + tree_def.m_nodenames.push_back(fruit); tree_def.explicit_seed = getintfield(L, idx, "seed", tree_def.seed); // Resolves the node IDs for trunk, leaves, leaves2 and fruit at runtime, diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 40a95ca1a..38094ab8c 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -566,38 +566,23 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, if (write_allowed) *write_allowed = false; - std::string abs_path = fs::AbsolutePath(path); - - // If we couldn't find the absolute path (path doesn't exist) then - // try removing the last components until it works (to allow - // non-existent files/folders for mkdir). - std::string cur_path = path; - std::string removed; - while (abs_path.empty() && !cur_path.empty()) { - std::string component; - cur_path = fs::RemoveLastPathComponent(cur_path, &component); - if (component == "..") { - // Parent components can't be allowed or we could allow something like - // /home/user/minetest/worlds/foo/noexist/../../../../../../etc/passwd. - // If we have previous non-relative elements in the path we might be - // able to remove them so that things like worlds/foo/noexist/../auth.txt - // could be allowed, but those paths will be interpreted as nonexistent - // by the operating system anyways. - return false; - } - removed = component + (removed.empty() ? "" : DIR_DELIM + removed); - abs_path = fs::AbsolutePath(cur_path); - } - if (abs_path.empty()) - return false; - // Add the removed parts back so that you can e.g. create a - // directory in worldmods if worldmods doesn't exist. - if (!removed.empty()) - abs_path += DIR_DELIM + removed; - + // We can't use AbsolutePath() here since we want to allow creating paths that + // do not yet exist. But RemoveRelativePathComponents() would also be incorrect + // since that wouldn't normalize subpaths that *do* exist. + // This is required so that comparisons with other normalized paths work correctly. + std::string abs_path = fs::AbsolutePathPartial(path); tracestream << "ScriptApiSecurity: path \"" << path << "\" resolved to \"" << abs_path << "\"" << std::endl; + if (abs_path.empty()) + return false; + + // Note: abs_path can be a valid path while path isn't, e.g. + // abs_path = "/home/user/.luanti" + // path = "/home/user/.luanti/noexist/.." + // Letting this through the sandbox isn't a concern as any actual attempts to + // use the path would fail. + // Ask the environment-specific implementation auto *sec = ModApiBase::getScriptApi(L); return sec->checkPathInternal(abs_path, write_required, write_allowed); @@ -617,9 +602,11 @@ bool ScriptApiSecurity::checkPathWithGamedef(lua_State *L, if (!gamedef) return false; - if (!abs_path.empty()) { + assert(!abs_path.empty()); + + if (!g_settings_path.empty()) { // Don't allow accessing the settings file - str = fs::AbsolutePath(g_settings_path); + str = fs::AbsolutePathPartial(g_settings_path); if (str == abs_path) return false; } diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 666dee712..5ebb2c3e4 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -1307,11 +1307,7 @@ int ModApiEnv::l_spawn_tree(lua_State *L) ServerMap *map = &env->getServerMap(); treegen::error e; if ((e = treegen::spawn_ltree (map, p0, tree_def)) != treegen::SUCCESS) { - if (e == treegen::UNBALANCED_BRACKETS) { - luaL_error(L, "spawn_tree(): closing ']' has no matching opening bracket"); - } else { - luaL_error(L, "spawn_tree(): unknown error"); - } + throw LuaError("spawn_tree(): " + treegen::error_to_string(e)); } lua_pushboolean(L, true); @@ -1614,11 +1610,7 @@ int ModApiEnvVM::l_spawn_tree(lua_State *L) treegen::error e; if ((e = treegen::make_ltree(*vm, p0, tree_def)) != treegen::SUCCESS) { - if (e == treegen::UNBALANCED_BRACKETS) { - throw LuaError("spawn_tree(): closing ']' has no matching opening bracket"); - } else { - throw LuaError("spawn_tree(): unknown error"); - } + throw LuaError("spawn_tree(): " + treegen::error_to_string(e)); } lua_pushboolean(L, true); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index e8c969268..0353efe1d 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -11,6 +11,7 @@ #include "gui/guiMainMenu.h" #include "gui/guiKeyChangeMenu.h" #include "gui/guiPathSelectMenu.h" +#include "gui/touchscreeneditor.h" #include "version.h" #include "porting.h" #include "filesys.h" @@ -535,6 +536,22 @@ int ModApiMainMenu::l_show_keys_menu(lua_State *L) return 0; } +/******************************************************************************/ +int ModApiMainMenu::l_show_touchscreen_layout(lua_State *L) +{ + GUIEngine *engine = getGuiEngine(L); + sanity_check(engine != NULL); + + GUITouchscreenLayout *gui = new GUITouchscreenLayout( + engine->m_rendering_engine->get_gui_env(), + engine->m_parent, + -1, + engine->m_menumanager, + engine->m_texture_source.get()); + gui->drop(); + return 0; +} + /******************************************************************************/ int ModApiMainMenu::l_create_world(lua_State *L) { @@ -1080,6 +1097,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(start); API_FCT(close); API_FCT(show_keys_menu); + API_FCT(show_touchscreen_layout); API_FCT(create_world); API_FCT(delete_world); API_FCT(set_background); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index d0f72b6c4..704924bf3 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -69,6 +69,8 @@ private: static int l_show_keys_menu(lua_State *L); + static int l_show_touchscreen_layout(lua_State *L); + static int l_show_path_select_dialog(lua_State *L); static int l_set_topleft_text(lua_State *L); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 89323b2f6..a7101ee92 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -366,22 +366,19 @@ Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef) ModApiMapgen::es_BiomeTerrainType, BIOMETYPE_NORMAL); Biome *b = BiomeManager::create(biometype); - b->name = getstringfield_default(L, index, "name", ""); - b->depth_top = getintfield_default(L, index, "depth_top", 0); - b->depth_filler = getintfield_default(L, index, "depth_filler", -31000); - b->depth_water_top = getintfield_default(L, index, "depth_water_top", 0); - b->depth_riverbed = getintfield_default(L, index, "depth_riverbed", 0); - b->heat_point = getfloatfield_default(L, index, "heat_point", 0.f); - b->humidity_point = getfloatfield_default(L, index, "humidity_point", 0.f); - b->vertical_blend = getintfield_default(L, index, "vertical_blend", 0); - b->weight = getfloatfield_default(L, index, "weight", 1.f); - b->flags = 0; // reserved + getstringfield(L, index, "name", b->name); + getintfield(L, index, "depth_top", b->depth_top); + getintfield(L, index, "depth_filler", b->depth_filler); + getintfield(L, index, "depth_water_top", b->depth_water_top); + getintfield(L, index, "depth_riverbed", b->depth_riverbed); + getfloatfield(L, index, "heat_point", b->heat_point); + getfloatfield(L, index, "humidity_point", b->humidity_point); + getintfield(L, index, "vertical_blend", b->vertical_blend); + getfloatfield(L, index, "weight", b->weight); - b->min_pos = getv3s16field_default( - L, index, "min_pos", v3s16(-31000, -31000, -31000)); + b->min_pos = getv3s16field_default(L, index, "min_pos", b->min_pos); getintfield(L, index, "y_min", b->min_pos.Y); - b->max_pos = getv3s16field_default( - L, index, "max_pos", v3s16(31000, 31000, 31000)); + b->max_pos = getv3s16field_default(L, index, "max_pos", b->max_pos); getintfield(L, index, "y_max", b->max_pos.Y); std::vector &nn = b->m_nodenames; @@ -1773,6 +1770,27 @@ int ModApiMapgen::l_place_schematic_on_vmanip(lua_State *L) return 1; } +// spawn_tree_on_vmanip(vmanip, pos, treedef) +int ModApiMapgen::l_spawn_tree_on_vmanip(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + MMVManip *vm = checkObject(L, 1)->vm; + v3s16 p0 = read_v3s16(L, 2); + treegen::TreeDef tree_def; + const NodeDefManager *ndef = getGameDef(L)->ndef(); + if (!read_tree_def(L, 3, ndef, tree_def)) + return 0; + + treegen::error e = treegen::make_ltree(*vm, p0, tree_def); + if (e != treegen::SUCCESS) { + throw LuaError("spawn_tree_on_vmanip(): " + treegen::error_to_string(e)); + } + + lua_pushboolean(L, true); + return 1; +} + // serialize_schematic(schematic, format, options={...}) int ModApiMapgen::l_serialize_schematic(lua_State *L) @@ -2023,6 +2041,7 @@ void ModApiMapgen::Initialize(lua_State *L, int top) API_FCT(create_schematic); API_FCT(place_schematic); API_FCT(place_schematic_on_vmanip); + API_FCT(spawn_tree_on_vmanip); API_FCT(serialize_schematic); API_FCT(read_schematic); } @@ -2049,6 +2068,7 @@ void ModApiMapgen::InitializeEmerge(lua_State *L, int top) API_FCT(generate_ores); API_FCT(generate_decorations); API_FCT(place_schematic_on_vmanip); + API_FCT(spawn_tree_on_vmanip); API_FCT(serialize_schematic); API_FCT(read_schematic); } diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h index d72cbcbad..860daa7e8 100644 --- a/src/script/lua_api/l_mapgen.h +++ b/src/script/lua_api/l_mapgen.h @@ -131,6 +131,9 @@ private: // replacements, force_placement, flagstring) static int l_place_schematic_on_vmanip(lua_State *L); + // spawn_tree_on_vmanip(vmanip, pos, treedef) + static int l_spawn_tree_on_vmanip(lua_State *L); + // serialize_schematic(schematic, format, options={...}) static int l_serialize_schematic(lua_State *L); diff --git a/src/script/lua_api/l_particleparams.h b/src/script/lua_api/l_particleparams.h index 7303081db..0e508148f 100644 --- a/src/script/lua_api/l_particleparams.h +++ b/src/script/lua_api/l_particleparams.h @@ -118,13 +118,14 @@ namespace LuaParticleParams {(int)BlendMode::add, "add"}, {(int)BlendMode::sub, "sub"}, {(int)BlendMode::screen, "screen"}, + {(int)BlendMode::clip, "clip"}, {0, nullptr}, }; luaL_checktype(L, -1, LUA_TSTRING); int v = (int)BlendMode::alpha; if (!string_to_enum(opts, v, lua_tostring(L, -1))) { - throw LuaError("blend mode must be one of ('alpha', 'add', 'sub', 'screen')"); + throw LuaError("blend mode must be one of ('alpha', 'clip', 'add', 'sub', 'screen')"); } ret = (BlendMode)v; } diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index 80c3f27c2..efcf3725e 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -3,6 +3,7 @@ // Copyright (C) 2013 celeron55, Perttu Ahola #include "lua_api/l_particles.h" +#include "common/c_types.h" #include "lua_api/l_object.h" #include "lua_api/l_internal.h" #include "lua_api/l_particleparams.h" @@ -280,6 +281,9 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile); } + if (p.time < 0) + throw LuaError("particle spawner 'time' must be >= 0"); + u32 id = getServer(L)->addParticleSpawner(p, attached, playername); lua_pushnumber(L, id); diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index f3f1a57bf..241eb2542 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -10,13 +10,15 @@ #include "settings.h" #include "noise.h" #include "log.h" +#include "common/c_converter.h" -/* This protects the following from being set: - * 'secure.*' settings - * some security-relevant settings +/* + * This protects the following from being set: + * - 'secure.*' settings + * - some security-relevant settings * (better solution pending) - * some mapgen settings + * - some mapgen settings * (not security-criticial, just to avoid messing up user configs) */ #define CHECK_SETTING_SECURITY(L, name) \ @@ -27,14 +29,16 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name) { +#if CHECK_CLIENT_BUILD() + // Main menu is allowed everything + if (ModApiBase::getGuiEngine(L) != nullptr) + return 0; +#endif + if (ScriptApiSecurity::isSecure(L) && name.compare(0, 7, "secure.") == 0) throw LuaError("Attempted to set secure setting."); - bool is_mainmenu = false; -#if CHECK_CLIENT_BUILD() - is_mainmenu = ModApiBase::getGuiEngine(L) != nullptr; -#endif - if (!is_mainmenu && (name == "mg_name" || name == "mg_flags")) { + if (name == "mg_name" || name == "mg_flags") { errorstream << "Tried to set global setting " << name << ", ignoring. " "minetest.set_mapgen_setting() should be used instead." << std::endl; infostream << script_get_backtrace(L) << std::endl; @@ -45,11 +49,9 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name) "main_menu_script", "shader_path", "texture_path", "screenshot_path", "serverlist_file", "serverlist_url", "map-dir", "contentdb_url", }; - if (!is_mainmenu) { - for (const char *name2 : disallowed) { - if (name == name2) - throw LuaError("Attempted to set disallowed setting."); - } + for (const char *name2 : disallowed) { + if (name == name2) + throw LuaError("Attempted to set disallowed setting."); } return 0; @@ -176,6 +178,21 @@ int LuaSettings::l_get_flags(lua_State *L) return 1; } +// get_pos(self, key) -> vector or nil +int LuaSettings::l_get_pos(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaSettings *o = checkObject(L, 1); + std::string key = luaL_checkstring(L, 2); + + std::optional pos; + if (o->m_settings->getV3FNoEx(key, pos) && pos.has_value()) + push_v3f(L, *pos); + else + lua_pushnil(L); + return 1; +} + // set(self, key, value) int LuaSettings::l_set(lua_State* L) { @@ -226,6 +243,22 @@ int LuaSettings::l_set_np_group(lua_State *L) return 0; } +// set_pos(self, key, value) +int LuaSettings::l_set_pos(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaSettings *o = checkObject(L, 1); + + std::string key = luaL_checkstring(L, 2); + v3f value = check_v3f(L, 3); + + CHECK_SETTING_SECURITY(L, key); + + o->m_settings->setV3F(key, value); + + return 0; +} + // remove(self, key) -> success int LuaSettings::l_remove(lua_State* L) { @@ -355,9 +388,11 @@ const luaL_Reg LuaSettings::methods[] = { luamethod(LuaSettings, get_bool), luamethod(LuaSettings, get_np_group), luamethod(LuaSettings, get_flags), + luamethod(LuaSettings, get_pos), luamethod(LuaSettings, set), luamethod(LuaSettings, set_bool), luamethod(LuaSettings, set_np_group), + luamethod(LuaSettings, set_pos), luamethod(LuaSettings, remove), luamethod(LuaSettings, get_names), luamethod(LuaSettings, has), diff --git a/src/script/lua_api/l_settings.h b/src/script/lua_api/l_settings.h index ca609fc1f..63741477a 100644 --- a/src/script/lua_api/l_settings.h +++ b/src/script/lua_api/l_settings.h @@ -29,6 +29,9 @@ private: // get_flags(self, key) -> key/value table static int l_get_flags(lua_State *L); + // get_pos(self, key) -> vector or nil + static int l_get_pos(lua_State *L); + // set(self, key, value) static int l_set(lua_State *L); @@ -38,6 +41,9 @@ private: // set_np_group(self, key, value) static int l_set_np_group(lua_State *L); + // set_pos(self, key, value) + static int l_set_pos(lua_State *L); + // remove(self, key) -> success static int l_remove(lua_State *L); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index cfea974b3..f84835e8b 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -27,8 +27,7 @@ #include "config.h" #include "version.h" #include "util/hex.h" -#include "util/sha1.h" -#include "my_sha256.h" +#include "util/hashing.h" #include "util/png.h" #include "player.h" #include "daynightratio.h" @@ -540,12 +539,7 @@ int ModApiUtil::l_sha1(lua_State *L) bool hex = !lua_isboolean(L, 2) || !readParam(L, 2); // Compute actual checksum of data - std::string data_sha1; - { - SHA1 ctx; - ctx.addBytes(data); - data_sha1 = ctx.getDigest(); - } + std::string data_sha1 = hashing::sha1(data); if (hex) { std::string sha1_hex = hex_encode(data_sha1); @@ -564,10 +558,7 @@ int ModApiUtil::l_sha256(lua_State *L) auto data = readParam(L, 1); bool hex = !lua_isboolean(L, 2) || !readParam(L, 2); - std::string data_sha256; - data_sha256.resize(SHA256_DIGEST_LENGTH); - SHA256(reinterpret_cast(data.data()), data.size(), - reinterpret_cast(data_sha256.data())); + std::string data_sha256 = hashing::sha256(data); if (hex) { lua_pushstring(L, hex_encode(data_sha256).c_str()); diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index d5434c325..88f80a001 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -76,10 +76,11 @@ void MainMenuScripting::registerLuaClasses(lua_State *L, int top) bool MainMenuScripting::mayModifyPath(const std::string &path) { - if (fs::PathStartsWith(path, fs::TempPath())) + std::string path_temp = fs::AbsolutePathPartial(fs::TempPath()); + if (fs::PathStartsWith(path, path_temp)) return true; - std::string path_user = fs::RemoveRelativePathComponents(porting::path_user); + std::string path_user = fs::AbsolutePathPartial(porting::path_user); if (fs::PathStartsWith(path, path_user + DIR_DELIM "client")) return true; @@ -92,7 +93,7 @@ bool MainMenuScripting::mayModifyPath(const std::string &path) if (fs::PathStartsWith(path, path_user + DIR_DELIM "worlds")) return true; - if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_cache))) + if (fs::PathStartsWith(path, fs::AbsolutePathPartial(porting::path_cache))) return true; return false; diff --git a/src/serialization.h b/src/serialization.h index 7e63ec23c..48c7464c1 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -50,23 +50,30 @@ 28: Added "private" flag to NodeMetadata 29: Switched compression to zstd, a bit of reorganization */ + // This represents an uninitialized or invalid format -#define SER_FMT_VER_INVALID 255 +constexpr u8 SER_FMT_VER_INVALID = 255; // Highest supported serialization version -#define SER_FMT_VER_HIGHEST_READ 29 +constexpr u8 SER_FMT_VER_HIGHEST_READ = 29; // Saved on disk version -#define SER_FMT_VER_HIGHEST_WRITE 29 +constexpr u8 SER_FMT_VER_HIGHEST_WRITE = 29; // Lowest supported serialization version -#define SER_FMT_VER_LOWEST_READ 0 +constexpr u8 SER_FMT_VER_LOWEST_READ = 0; // Lowest serialization version for writing // Can't do < 24 anymore; we have 16-bit dynamically allocated node IDs // in memory; conversion just won't work in this direction. -#define SER_FMT_VER_LOWEST_WRITE 24 +constexpr u8 SER_FMT_VER_LOWEST_WRITE = 24; -inline bool ser_ver_supported(s32 v) { +inline bool ser_ver_supported_read(s32 v) +{ return v >= SER_FMT_VER_LOWEST_READ && v <= SER_FMT_VER_HIGHEST_READ; } +inline bool ser_ver_supported_write(s32 v) +{ + return v >= SER_FMT_VER_LOWEST_WRITE && v <= SER_FMT_VER_HIGHEST_WRITE; +} + /* Compression functions */ diff --git a/src/server.cpp b/src/server.cpp index 82aa8cf13..6df86f890 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -21,6 +21,7 @@ #include "filesys.h" #include "mapblock.h" #include "server/serveractiveobject.h" +#include "serialization.h" // SER_FMT_VER_INVALID #include "settings.h" #include "profiler.h" #include "log.h" @@ -43,7 +44,7 @@ #include "defaultsettings.h" #include "server/mods.h" #include "util/base64.h" -#include "util/sha1.h" +#include "util/hashing.h" #include "util/hex.h" #include "database/database.h" #include "chatmessage.h" @@ -2557,14 +2558,11 @@ bool Server::addMediaFile(const std::string &filename, return false; } - SHA1 sha1; - sha1.addBytes(filedata); - - std::string digest = sha1.getDigest(); - std::string sha1_base64 = base64_encode(digest); - std::string sha1_hex = hex_encode(digest); + std::string sha1 = hashing::sha1(filedata); + std::string sha1_base64 = base64_encode(sha1); + std::string sha1_hex = hex_encode(sha1); if (digest_to) - *digest_to = digest; + *digest_to = sha1; // Put in list m_media[filename] = MediaInfo(filepath, sha1_base64); @@ -3872,9 +3870,14 @@ void Server::addShutdownError(const ModError &e) v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); - v3f nodeposf; - if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf)) - return nodeposf * BS; + + std::optional staticSpawnPoint; + if (g_settings->getV3FNoEx("static_spawnpoint", staticSpawnPoint) && staticSpawnPoint.has_value()) + { + return *staticSpawnPoint * BS; + } + + v3f nodeposf; bool is_good = false; // Limit spawn range to mapgen edges (determined by 'mapgen_limit') @@ -4201,7 +4204,7 @@ ModStorageDatabase *Server::openModStorageDatabase(const std::string &world_path warningstream << "/!\\ You are using the old mod storage files backend. " << "This backend is deprecated and may be removed in a future release /!\\" << std::endl << "Switching to SQLite3 is advised, " - << "please read http://wiki.minetest.net/Database_backends." << std::endl; + << "please read https://wiki.luanti.org/Database_backends." << std::endl; return openModStorageDatabase(backend, world_path, world_mt); } diff --git a/src/server.h b/src/server.h index f2d767e99..a9ba35c63 100644 --- a/src/server.h +++ b/src/server.h @@ -8,7 +8,6 @@ #include "map.h" #include "hud.h" #include "gamedef.h" -#include "serialization.h" // For SER_FMT_VER_INVALID #include "content/mods.h" #include "inventorymanager.h" #include "content/subgames.h" diff --git a/src/server/ban.cpp b/src/server/ban.cpp index 737f5a57e..e63c4708f 100644 --- a/src/server/ban.cpp +++ b/src/server/ban.cpp @@ -7,11 +7,11 @@ #include #include "threading/mutex_auto_lock.h" #include -#include #include "util/strfnd.h" #include "util/string.h" #include "log.h" #include "filesys.h" +#include "exceptions.h" BanManager::BanManager(const std::string &banfilepath): m_banfilepath(banfilepath) @@ -68,13 +68,13 @@ void BanManager::save() m_modified = false; } -bool BanManager::isIpBanned(const std::string &ip) +bool BanManager::isIpBanned(const std::string &ip) const { MutexAutoLock lock(m_mutex); return m_ips.find(ip) != m_ips.end(); } -std::string BanManager::getBanDescription(const std::string &ip_or_name) +std::string BanManager::getBanDescription(const std::string &ip_or_name) const { MutexAutoLock lock(m_mutex); std::string s; @@ -88,10 +88,10 @@ std::string BanManager::getBanDescription(const std::string &ip_or_name) return s; } -std::string BanManager::getBanName(const std::string &ip) +std::string BanManager::getBanName(const std::string &ip) const { MutexAutoLock lock(m_mutex); - StringMap::iterator it = m_ips.find(ip); + StringMap::const_iterator it = m_ips.find(ip); if (it == m_ips.end()) return ""; return it->second; @@ -118,9 +118,8 @@ void BanManager::remove(const std::string &ip_or_name) } -bool BanManager::isModified() +bool BanManager::isModified() const { MutexAutoLock lock(m_mutex); return m_modified; } - diff --git a/src/server/ban.h b/src/server/ban.h index 146f3210a..4acd58fba 100644 --- a/src/server/ban.h +++ b/src/server/ban.h @@ -5,9 +5,6 @@ #pragma once #include "util/string.h" -#include "threading/thread.h" -#include "exceptions.h" -#include #include #include @@ -18,16 +15,16 @@ public: ~BanManager(); void load(); void save(); - bool isIpBanned(const std::string &ip); + bool isIpBanned(const std::string &ip) const; // Supplying ip_or_name = "" lists all bans. - std::string getBanDescription(const std::string &ip_or_name); - std::string getBanName(const std::string &ip); + std::string getBanDescription(const std::string &ip_or_name) const; + std::string getBanName(const std::string &ip) const; void add(const std::string &ip, const std::string &name); void remove(const std::string &ip_or_name); - bool isModified(); + bool isModified() const; private: - std::mutex m_mutex; + mutable std::mutex m_mutex; std::string m_banfilepath = ""; StringMap m_ips; bool m_modified = false; diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index f50b4273f..b114c6c84 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -8,6 +8,7 @@ #include "network/connection.h" #include "network/serveropcodes.h" #include "remoteplayer.h" +#include "serialization.h" // SER_FMT_VER_INVALID #include "settings.h" #include "mapblock.h" #include "serverenvironment.h" @@ -51,6 +52,8 @@ std::string ClientInterface::state2Name(ClientState state) } RemoteClient::RemoteClient() : + serialization_version(SER_FMT_VER_INVALID), + m_pending_serialization_version(SER_FMT_VER_INVALID), m_max_simul_sends(g_settings->getU16("max_simultaneous_block_sends_per_client")), m_min_time_from_building( g_settings->getFloat("full_block_send_enable_min_time_from_building")), diff --git a/src/server/clientiface.h b/src/server/clientiface.h index 4546f045e..294bcbd26 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -7,7 +7,6 @@ #include "irr_v3d.h" // for irrlicht datatypes #include "constants.h" -#include "serialization.h" // for SER_FMT_VER_INVALID #include "network/networkpacket.h" #include "network/networkprotocol.h" #include "network/address.h" @@ -219,7 +218,7 @@ public: // Also, the client must be moved to some other container. session_t peer_id = PEER_ID_INEXISTENT; // The serialization version to use with the client - u8 serialization_version = SER_FMT_VER_INVALID; + u8 serialization_version; // u16 net_proto_version = 0; @@ -333,7 +332,7 @@ public: private: // Version is stored in here after INIT before INIT2 - u8 m_pending_serialization_version = SER_FMT_VER_INVALID; + u8 m_pending_serialization_version; /* current state of client */ ClientState m_state = CS_Created; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index d60e41ad9..44645ca34 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -526,14 +526,14 @@ void ServerEnvironment::init() warningstream << "/!\\ You are using old player file backend. " << "This backend is deprecated and will be removed in a future release /!\\" << std::endl << "Switching to SQLite3 or PostgreSQL is advised, " - << "please read http://wiki.minetest.net/Database_backends." << std::endl; + << "please read https://wiki.luanti.org/Database_backends." << std::endl; } if (auth_backend_name == "files") { warningstream << "/!\\ You are using old auth file backend. " << "This backend is deprecated and will be removed in a future release /!\\" << std::endl << "Switching to SQLite3 is advised, " - << "please read http://wiki.minetest.net/Database_backends." << std::endl; + << "please read https://wiki.luanti.org/Database_backends." << std::endl; } m_player_database = openPlayerDatabase(player_backend_name, world_path, conf); diff --git a/src/settings.cpp b/src/settings.cpp index 02c84437d..cec3f5239 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -541,7 +541,7 @@ v2f Settings::getV2F(const std::string &name) const } -v3f Settings::getV3F(const std::string &name) const +std::optional Settings::getV3F(const std::string &name) const { return str_to_v3f(get(name)); } @@ -626,7 +626,11 @@ bool Settings::getNoiseParamsFromGroup(const std::string &name, group->getFloatNoEx("offset", np.offset); group->getFloatNoEx("scale", np.scale); - group->getV3FNoEx("spread", np.spread); + + std::optional spread; + if (group->getV3FNoEx("spread", spread) && spread.has_value()) + np.spread = *spread; + group->getS32NoEx("seed", np.seed); group->getU16NoEx("octaves", np.octaves); group->getFloatNoEx("persistence", np.persist); @@ -783,7 +787,7 @@ bool Settings::getV2FNoEx(const std::string &name, v2f &val) const } -bool Settings::getV3FNoEx(const std::string &name, v3f &val) const +bool Settings::getV3FNoEx(const std::string &name, std::optional &val) const { try { val = getV3F(name); diff --git a/src/settings.h b/src/settings.h index 0638dad2d..9bc0e80d9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -150,7 +150,7 @@ public: float getFloat(const std::string &name) const; float getFloat(const std::string &name, float min, float max) const; v2f getV2F(const std::string &name) const; - v3f getV3F(const std::string &name) const; + std::optional getV3F(const std::string &name) const; u32 getFlagStr(const std::string &name, const FlagDesc *flagdesc, u32 *flagmask) const; bool getNoiseParams(const std::string &name, NoiseParams &np) const; @@ -179,7 +179,7 @@ public: bool getU64NoEx(const std::string &name, u64 &val) const; bool getFloatNoEx(const std::string &name, float &val) const; bool getV2FNoEx(const std::string &name, v2f &val) const; - bool getV3FNoEx(const std::string &name, v3f &val) const; + bool getV3FNoEx(const std::string &name, std::optional &val) const; // Like other getters, but handling each flag individualy: // 1) Read default flags (or 0) diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 46e4f9a18..b7ca17199 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -17,6 +17,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapblock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_mapgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp @@ -52,6 +53,7 @@ set (UNITTEST_CLIENT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp PARENT_SCOPE) diff --git a/src/unittest/test_filesys.cpp b/src/unittest/test_filesys.cpp index 9bab4fb5d..34666f611 100644 --- a/src/unittest/test_filesys.cpp +++ b/src/unittest/test_filesys.cpp @@ -24,6 +24,7 @@ public: void testRemoveLastPathComponent(); void testRemoveLastPathComponentWithTrailingDelimiter(); void testRemoveRelativePathComponent(); + void testAbsolutePath(); void testSafeWriteToFile(); void testCopyFileContents(); void testNonExist(); @@ -39,6 +40,7 @@ void TestFileSys::runTests(IGameDef *gamedef) TEST(testRemoveLastPathComponent); TEST(testRemoveLastPathComponentWithTrailingDelimiter); TEST(testRemoveRelativePathComponent); + TEST(testAbsolutePath); TEST(testSafeWriteToFile); TEST(testCopyFileContents); TEST(testNonExist); @@ -55,7 +57,7 @@ static std::string p(std::string path) for (size_t i = 0; i < path.size(); ++i) { if (path[i] == '/') { path.replace(i, 1, DIR_DELIM); - i += std::string(DIR_DELIM).size() - 1; // generally a no-op + i += strlen(DIR_DELIM) - 1; // generally a no-op } } @@ -259,6 +261,46 @@ void TestFileSys::testRemoveRelativePathComponent() } +void TestFileSys::testAbsolutePath() +{ + const auto dir_path = getTestTempDirectory(); + + /* AbsolutePath */ + UASSERTEQ(auto, fs::AbsolutePath(""), ""); // empty is a not valid path + const auto cwd = fs::AbsolutePath("."); + UASSERTCMP(auto, !=, cwd, ""); + { + const auto dir_path2 = getTestTempFile(); + UASSERTEQ(auto, fs::AbsolutePath(dir_path2), ""); // doesn't exist + fs::CreateDir(dir_path2); + UASSERTCMP(auto, !=, fs::AbsolutePath(dir_path2), ""); // now it does + UASSERTEQ(auto, fs::AbsolutePath(dir_path2 + DIR_DELIM ".."), fs::AbsolutePath(dir_path)); + } + + /* AbsolutePathPartial */ + // equivalent to AbsolutePath if it exists + UASSERTEQ(auto, fs::AbsolutePathPartial("."), cwd); + UASSERTEQ(auto, fs::AbsolutePathPartial(dir_path), fs::AbsolutePath(dir_path)); + // usual usage of the function with a partially existing path + auto expect = cwd + DIR_DELIM + p("does/not/exist"); + UASSERTEQ(auto, fs::AbsolutePathPartial("does/not/exist"), expect); + UASSERTEQ(auto, fs::AbsolutePathPartial(expect), expect); + + // a nonsense combination as you couldn't actually access it, but allowed by function + UASSERTEQ(auto, fs::AbsolutePathPartial("bla/blub/../.."), cwd); + UASSERTEQ(auto, fs::AbsolutePathPartial("./bla/blub/../.."), cwd); + +#ifdef __unix__ + // one way to produce the error case is to remove more components than there are + // but only if the path does not actually exist ("/.." does exist). + UASSERTEQ(auto, fs::AbsolutePathPartial("/.."), "/"); + UASSERTEQ(auto, fs::AbsolutePathPartial("/noexist/../.."), ""); +#endif + // or with an empty path + UASSERTEQ(auto, fs::AbsolutePathPartial(""), ""); +} + + void TestFileSys::testSafeWriteToFile() { const std::string dest_path = getTestTempFile(); diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index 2d87db4e3..b1b0d99a1 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -12,7 +12,7 @@ #include "IFileSystem.h" #include "IReadFile.h" #include "ISceneManager.h" -#include "ISkinnedMesh.h" +#include "SkinnedMesh.h" #include "irrlicht.h" #include "catch.h" @@ -379,14 +379,14 @@ SECTION("simple sparse accessor") // https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/SimpleSkin SECTION("simple skin") { - using ISkinnedMesh = irr::scene::ISkinnedMesh; + using SkinnedMesh = irr::scene::SkinnedMesh; const auto mesh = loadMesh(model_stem + "simple_skin.gltf"); REQUIRE(mesh != nullptr); - auto csm = dynamic_cast(mesh); + auto csm = dynamic_cast(mesh); const auto joints = csm->getAllJoints(); REQUIRE(joints.size() == 3); - const auto findJoint = [&](const std::function &predicate) { + const auto findJoint = [&](const std::function &predicate) { for (std::size_t i = 0; i < joints.size(); ++i) { if (predicate(joints[i])) { return joints[i]; @@ -420,7 +420,7 @@ SECTION("simple skin") SECTION("weights are correct") { - const auto weights = [&](const ISkinnedMesh::SJoint *joint) { + const auto weights = [&](const SkinnedMesh::SJoint *joint) { std::unordered_map weights; for (std::size_t i = 0; i < joint->Weights.size(); ++i) { const auto weight = joint->Weights[i]; diff --git a/src/unittest/test_irr_matrix4.cpp b/src/unittest/test_irr_matrix4.cpp new file mode 100644 index 000000000..6272c8c26 --- /dev/null +++ b/src/unittest/test_irr_matrix4.cpp @@ -0,0 +1,69 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch.h" +#include "irrMath.h" +#include "matrix4.h" +#include "irr_v3d.h" + +using matrix4 = core::matrix4; + +static bool matrix_equals(const matrix4 &a, const matrix4 &b) { + return a.equals(b, 0.00001f); +} + +constexpr v3f x{1, 0, 0}; +constexpr v3f y{0, 1, 0}; +constexpr v3f z{0, 0, 1}; + +TEST_CASE("matrix4") { + +SECTION("setRotationRadians") { + SECTION("rotation order is ZYX (matrix notation)") { + v3f rot{1, 2, 3}; + matrix4 X, Y, Z, ZYX; + X.setRotationRadians({rot.X, 0, 0}); + Y.setRotationRadians({0, rot.Y, 0}); + Z.setRotationRadians({0, 0, rot.Z}); + ZYX.setRotationRadians(rot); + CHECK(!matrix_equals(X * Y * Z, ZYX)); + CHECK(!matrix_equals(X * Z * Y, ZYX)); + CHECK(!matrix_equals(Y * X * Z, ZYX)); + CHECK(!matrix_equals(Y * Z * X, ZYX)); + CHECK(!matrix_equals(Z * X * Y, ZYX)); + CHECK(matrix_equals(Z * Y * X, ZYX)); + } + + const f32 quarter_turn = core::PI / 2; + + // See https://en.wikipedia.org/wiki/Right-hand_rule#/media/File:Cartesian_coordinate_system_handedness.svg + // for a visualization of what handedness means for rotations + + SECTION("rotation is right-handed") { + SECTION("rotation around the X-axis is Z-up, counter-clockwise") { + matrix4 X; + X.setRotationRadians({quarter_turn, 0, 0}); + CHECK(X.transformVect(x).equals(x)); + CHECK(X.transformVect(y).equals(z)); + CHECK(X.transformVect(z).equals(-y)); + } + + SECTION("rotation around the Y-axis is Z-up, clockwise") { + matrix4 Y; + Y.setRotationRadians({0, quarter_turn, 0}); + CHECK(Y.transformVect(y).equals(y)); + CHECK(Y.transformVect(x).equals(-z)); + CHECK(Y.transformVect(z).equals(x)); + } + + SECTION("rotation around the Z-axis is Y-up, counter-clockwise") { + matrix4 Z; + Z.setRotationRadians({0, 0, quarter_turn}); + CHECK(Z.transformVect(z).equals(z)); + CHECK(Z.transformVect(x).equals(y)); + CHECK(Z.transformVect(y).equals(-x)); + } + } +} + +} diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp index 234b40cda..8fb074e17 100644 --- a/src/unittest/test_map_settings_manager.cpp +++ b/src/unittest/test_map_settings_manager.cpp @@ -7,7 +7,7 @@ #include "noise.h" #include "settings.h" #include "mapgen/mapgen_v5.h" -#include "util/sha1.h" +#include "util/hashing.h" #include "map_settings_manager.h" class TestMapSettingsManager : public TestBase { @@ -171,11 +171,9 @@ void TestMapSettingsManager::testMapSettingsManager() 0x78, 0x56, 0x95, 0x2d, 0xdc, 0x6a, 0xf7, 0x61, 0x36, 0x5f }; - SHA1 ctx; std::string metafile_contents; UASSERT(fs::ReadFile(test_mapmeta_path, metafile_contents)); - ctx.addBytes(metafile_contents); - std::string sha1_result = ctx.getDigest(); + std::string sha1_result = hashing::sha1(metafile_contents); int resultdiff = memcmp(sha1_result.data(), expected_contents_hash, 20); UASSERT(!resultdiff); diff --git a/src/unittest/test_mapgen.cpp b/src/unittest/test_mapgen.cpp new file mode 100644 index 000000000..62bd7e54f --- /dev/null +++ b/src/unittest/test_mapgen.cpp @@ -0,0 +1,113 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 Minetest core developers & community + +#include "test.h" + +#include "emerge.h" +#include "mapgen/mapgen.h" +#include "mapgen/mg_biome.h" +#include "mock_server.h" + +class TestMapgen : public TestBase +{ +public: + TestMapgen() { TestManager::registerTestModule(this); } + const char *getName() { return "TestMapgen"; } + + void runTests(IGameDef *gamedef); + + void testBiomeGen(IGameDef *gamedef); +}; + +static TestMapgen g_test_instance; + +namespace { + class MockBiomeManager : public BiomeManager { + public: + MockBiomeManager(Server *server) : BiomeManager(server) {} + + void setNodeDefManager(const NodeDefManager *ndef) + { + m_ndef = ndef; + } + }; +} + +void TestMapgen::runTests(IGameDef *gamedef) +{ + TEST(testBiomeGen, gamedef); +} + +void TestMapgen::testBiomeGen(IGameDef *gamedef) +{ + MockServer server(getTestTempDirectory()); + MockBiomeManager bmgr(&server); + bmgr.setNodeDefManager(gamedef->getNodeDefManager()); + + { + // Add some biomes (equivalent to l_register_biome) + // Taken from minetest_game/mods/default/mapgen.lua + size_t bmgr_count = bmgr.getNumObjects(); // this is 1 ? + + Biome *b = BiomeManager::create(BIOMETYPE_NORMAL); + b->name = "deciduous_forest"; + b->c_top = t_CONTENT_GRASS; + b->depth_top = 1; + b->c_filler = t_CONTENT_BRICK; // dirt + b->depth_filler = 3; + b->c_stone = t_CONTENT_STONE; + b->min_pos.Y = 1; + b->heat_point = 60.0f; + b->humidity_point = 68.0f; + UASSERT(bmgr.add(b) != OBJDEF_INVALID_HANDLE); + + b = BiomeManager::create(BIOMETYPE_NORMAL); + b->name = "deciduous_forest_shore"; + b->c_top = t_CONTENT_BRICK; // dirt + b->depth_top = 1; + b->c_filler = t_CONTENT_BRICK; // dirt + b->depth_filler = 3; + b->c_stone = t_CONTENT_STONE; + b->max_pos.Y = 0; + b->heat_point = 60.0f; + b->humidity_point = 68.0f; + UASSERT(bmgr.add(b) != OBJDEF_INVALID_HANDLE); + UASSERT(bmgr.getNumObjects() - bmgr_count == 2); + } + + + std::unique_ptr params(BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL)); + + constexpr v3s16 CSIZE(16, 16, 16); // misleading name. measured in nodes. + std::unique_ptr biomegen( + bmgr.createBiomeGen(BIOMEGEN_ORIGINAL, params.get(), CSIZE) + ); + + { + // Test biome transitions + // getBiomeAtIndex (Y only) + // getNextTransitionY + const struct { + s16 check_y; + const char *name; + s16 next_y; + } expected_biomes[] = { + { MAX_MAP_GENERATION_LIMIT, "deciduous_forest", 0 }, + { 1, "deciduous_forest", 0 }, + { 0, "deciduous_forest_shore", S16_MIN }, + { -100, "deciduous_forest_shore", S16_MIN }, + }; + for (const auto expected : expected_biomes) { + Biome *biome = biomegen->getBiomeAtIndex( + (1 * CSIZE.X) + 1, // index in CSIZE 2D noise map + v3s16(2000, expected.check_y, -1000) // absolute coordinates + ); + s16 next_y = biomegen->getNextTransitionY(expected.check_y); + + UASSERTEQ(auto, biome->name, expected.name); + UASSERTEQ(auto, next_y, expected.next_y); + } + } +} + diff --git a/src/unittest/test_settings.cpp b/src/unittest/test_settings.cpp index 94b222c1f..411ecb8de 100644 --- a/src/unittest/test_settings.cpp +++ b/src/unittest/test_settings.cpp @@ -42,6 +42,14 @@ const char *TestSettings::config_text_before = "floaty_thing = 1.1\n" "stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n" "coord = (1, 2, 4.5)\n" + "coord_invalid = (1,2,3\n" + "coord_invalid_2 = 1, 2, 3 test\n" + "coord_invalid_3 = (test, something, stupid)\n" + "coord_invalid_4 = (1, test, 3)\n" + "coord_invalid_5 = ()\n" + "coord_invalid_6 = (1, 2)\n" + "coord_invalid_7 = (1)\n" + "coord_no_parenthesis = 1,2,3\n" " # this is just a comment\n" "this is an invalid line\n" "asdf = {\n" @@ -94,7 +102,15 @@ const char *TestSettings::config_text_after = " spread = (250,250,250)\n" "}\n" "zoop = true\n" - "coord2 = (1,2,3.3)\n" + "coord2 = (1,2,3.25)\n" + "coord_invalid = (1,2,3\n" + "coord_invalid_2 = 1, 2, 3 test\n" + "coord_invalid_3 = (test, something, stupid)\n" + "coord_invalid_4 = (1, test, 3)\n" + "coord_invalid_5 = ()\n" + "coord_invalid_6 = (1, 2)\n" + "coord_invalid_7 = (1)\n" + "coord_no_parenthesis = 1,2,3\n" "floaty_thing_2 = 1.25\n" "groupy_thing = {\n" " animals = cute\n" @@ -140,18 +156,33 @@ void TestSettings::testAllSettings() // Not sure if 1.1 is an exact value as a float, but doesn't matter UASSERT(fabs(s.getFloat("floaty_thing") - 1.1) < 0.001); UASSERT(s.get("stringy_thing") == u8"asd /( ¤%&(/\" BLÖÄRP"); - UASSERT(fabs(s.getV3F("coord").X - 1.0) < 0.001); - UASSERT(fabs(s.getV3F("coord").Y - 2.0) < 0.001); - UASSERT(fabs(s.getV3F("coord").Z - 4.5) < 0.001); + UASSERT(s.getV3F("coord").value().X == 1.0); + UASSERT(s.getV3F("coord").value().Y == 2.0); + UASSERT(s.getV3F("coord").value().Z == 4.5); // Test the setting of settings too s.setFloat("floaty_thing_2", 1.25); - s.setV3F("coord2", v3f(1, 2, 3.3)); + s.setV3F("coord2", v3f(1, 2, 3.25)); UASSERT(s.get("floaty_thing_2").substr(0,4) == "1.25"); - UASSERT(fabs(s.getFloat("floaty_thing_2") - 1.25) < 0.001); - UASSERT(fabs(s.getV3F("coord2").X - 1.0) < 0.001); - UASSERT(fabs(s.getV3F("coord2").Y - 2.0) < 0.001); - UASSERT(fabs(s.getV3F("coord2").Z - 3.3) < 0.001); + UASSERT(s.getFloat("floaty_thing_2") == 1.25); + UASSERT(s.getV3F("coord2").value().X == 1.0); + UASSERT(s.getV3F("coord2").value().Y == 2.0); + UASSERT(s.getV3F("coord2").value().Z == 3.25); + + std::optional testNotExist; + UASSERT(!s.getV3FNoEx("coord_not_exist", testNotExist)); + EXCEPTION_CHECK(SettingNotFoundException, s.getV3F("coord_not_exist")); + + UASSERT(!s.getV3F("coord_invalid").has_value()); + UASSERT(!s.getV3F("coord_invalid_2").has_value()); + UASSERT(!s.getV3F("coord_invalid_3").has_value()); + UASSERT(!s.getV3F("coord_invalid_4").has_value()); + UASSERT(!s.getV3F("coord_invalid_5").has_value()); + UASSERT(!s.getV3F("coord_invalid_6").has_value()); + UASSERT(!s.getV3F("coord_invalid_7").has_value()); + + std::optional testNoParenthesis = s.getV3F("coord_no_parenthesis"); + UASSERT(testNoParenthesis.value() == v3f(1, 2, 3)); // Test settings groups Settings *group = s.getGroup("asdf"); diff --git a/src/unittest/test_translations.cpp b/src/unittest/test_translations.cpp index 37fc78ee4..5bab3e15c 100644 --- a/src/unittest/test_translations.cpp +++ b/src/unittest/test_translations.cpp @@ -25,12 +25,41 @@ TEST_CASE("test translations") { SECTION("Plural-Forms function for translations") { - auto form = GettextPluralForm::parseHeaderLine(L"Plural-Forms: nplurals=3; plural= (n-1+1)<=1 ? n||1?0:1 : 1?(!!2):2;"); - REQUIRE(form); - REQUIRE(form->size() == 3); +#define REQUIRE_FORM_SIZE(x) {REQUIRE(form); REQUIRE(form->size() == (x));} + // Test cases from https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html + auto form = GettextPluralForm::parseHeaderLine(L"Plural-Forms: nplurals=2; plural=n != 1;"); + REQUIRE_FORM_SIZE(2); + CHECK((*form)(0) == 1); + CHECK((*form)(1) == 0); + CHECK((*form)(2) == 1); + + form = GettextPluralForm::parseHeaderLine(L"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;"); + REQUIRE_FORM_SIZE(3); + CHECK((*form)(0) == 2); + CHECK((*form)(1) == 0); + CHECK((*form)(102) == 1); + CHECK((*form)(111) == 1); + + form = GettextPluralForm::parseHeaderLine(L"Plural-Forms: nplurals=3; " + "plural=n%10==1 && n%100!=11 ? 0 : " + "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;"); + REQUIRE_FORM_SIZE(3); + CHECK((*form)(0) == 2); + CHECK((*form)(1) == 0); + CHECK((*form)(102) == 1); + CHECK((*form)(104) == 1); + CHECK((*form)(111) == 2); + CHECK((*form)(112) == 2); + CHECK((*form)(121) == 0); + CHECK((*form)(122) == 1); + + // Edge cases + form = GettextPluralForm::parseHeaderLine(L"Plural-Forms: nplurals=3; plural= (n-1+1)<=1 ? n||1?0:1 : 1?(!!2):2;"); + REQUIRE_FORM_SIZE(3); CHECK((*form)(0) == 0); CHECK((*form)(1) == 0); CHECK((*form)(2) == 1); +#undef REQUIRE_FORM_SIZE } SECTION("PO file parser") diff --git a/src/unittest/test_voxelalgorithms.cpp b/src/unittest/test_voxelalgorithms.cpp index 2a98412b4..b8ba11248 100644 --- a/src/unittest/test_voxelalgorithms.cpp +++ b/src/unittest/test_voxelalgorithms.cpp @@ -98,8 +98,8 @@ void TestVoxelAlgorithms::testLighting(IGameDef *gamedef) std::map modified_blocks; MMVManip vm(&map); vm.initialEmerge(bpmin, bpmax, false); - s32 volume = vm.m_area.getVolume(); - for (s32 i = 0; i < volume; i++) + u32 volume = vm.m_area.getVolume(); + for (u32 i = 0; i < volume; i++) vm.m_data[i] = MapNode(CONTENT_AIR); for (s16 z = -10; z <= 10; z++) for (s16 y = -10; y <= 10; y++) diff --git a/src/unittest/test_voxelarea.cpp b/src/unittest/test_voxelarea.cpp index 384fda0d1..f594a9be7 100644 --- a/src/unittest/test_voxelarea.cpp +++ b/src/unittest/test_voxelarea.cpp @@ -107,25 +107,34 @@ void TestVoxelArea::test_pad() void TestVoxelArea::test_extent() { VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); - UASSERT(v1.getExtent() == v3s16(1191, 995, 1459)); + UASSERT(v1.getExtent() == v3s32(1191, 995, 1459)); VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); - UASSERT(v2.getExtent() == v3s16(16, 16, 16)); + UASSERT(v2.getExtent() == v3s32(16, 16, 16)); + // side length bigger than S16_MAX + VoxelArea v3({-20000, 12, 34}, {20000, 12, 34}); + UASSERT(v3.getExtent() == v3s32(40001, 1, 1)); + + UASSERT(VoxelArea().hasEmptyExtent()); UASSERT(VoxelArea({2,3,4}, {1,2,3}).hasEmptyExtent()); - UASSERT(VoxelArea({2,3,4}, {2,2,3}).hasEmptyExtent() == false); + UASSERT(VoxelArea({2,3,4}, {2,2,3}).hasEmptyExtent()); } void TestVoxelArea::test_volume() { VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); - UASSERTEQ(s32, v1.getVolume(), 1728980655); + UASSERTEQ(u32, v1.getVolume(), 1728980655); VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); - UASSERTEQ(s32, v2.getVolume(), 4096); + UASSERTEQ(u32, v2.getVolume(), 4096); - UASSERTEQ(s32, VoxelArea({2,3,4}, {1,2,3}).getVolume(), 0); - UASSERTEQ(s32, VoxelArea({2,3,4}, {2,2,3}).getVolume(), 0); + // volume bigger than S32_MAX + VoxelArea v3({1, 1, 1}, {1337, 1337, 1337}); + UASSERTEQ(u32, v3.getVolume(), 2389979753U); + + UASSERTEQ(u32, VoxelArea({2,3,4}, {1,2,3}).getVolume(), 0); + UASSERTEQ(u32, VoxelArea({2,3,4}, {2,2,3}).getVolume(), 0); } void TestVoxelArea::test_contains_voxelarea() @@ -388,7 +397,7 @@ void TestVoxelArea::test_index_v3s16_all_neg() void TestVoxelArea::test_add_x() { - v3s16 extent; + v3s32 extent; u32 i = 4; VoxelArea::add_x(extent, i, 8); UASSERTEQ(u32, i, 12) @@ -396,7 +405,7 @@ void TestVoxelArea::test_add_x() void TestVoxelArea::test_add_y() { - v3s16 extent(740, 16, 87); + v3s32 extent(740, 16, 87); u32 i = 8; VoxelArea::add_y(extent, i, 88); UASSERTEQ(u32, i, 65128) @@ -404,7 +413,7 @@ void TestVoxelArea::test_add_y() void TestVoxelArea::test_add_z() { - v3s16 extent(114, 80, 256); + v3s32 extent(114, 80, 256); u32 i = 4; VoxelArea::add_z(extent, i, 8); UASSERTEQ(u32, i, 72964) @@ -412,7 +421,7 @@ void TestVoxelArea::test_add_z() void TestVoxelArea::test_add_p() { - v3s16 extent(33, 14, 742); + v3s32 extent(33, 14, 742); v3s16 a(15, 12, 369); u32 i = 4; VoxelArea::add_p(extent, i, a); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 761e51a4a..ec88a33c2 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,10 +1,11 @@ set(util_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/colorize.cpp ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/colorize.cpp ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/hashing.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ieee_float.cpp ${CMAKE_CURRENT_SOURCE_DIR}/metricsbackend.cpp ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp diff --git a/src/util/auth.cpp b/src/util/auth.cpp index 3c8e5763a..040d15bf9 100644 --- a/src/util/auth.cpp +++ b/src/util/auth.cpp @@ -6,7 +6,7 @@ #include #include "auth.h" #include "base64.h" -#include "sha1.h" +#include "util/hashing.h" #include "srp.h" #include "util/string.h" #include "debug.h" @@ -23,9 +23,7 @@ std::string translate_password(const std::string &name, return ""; std::string slt = name + password; - SHA1 sha1; - sha1.addBytes(slt); - std::string digest = sha1.getDigest(); + std::string digest = hashing::sha1(slt); std::string pwd = base64_encode(digest); return pwd; } diff --git a/src/util/hashing.cpp b/src/util/hashing.cpp new file mode 100644 index 000000000..452cd6818 --- /dev/null +++ b/src/util/hashing.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 Luanti Contributors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "hashing.h" + +#include "debug.h" +#include "config.h" +#if USE_OPENSSL +#include +#include +#else +#include "util/sha1.h" +#include "my_sha256.h" +#endif + +namespace hashing +{ + +std::string sha1(std::string_view data) +{ +#if USE_OPENSSL + std::string digest(SHA1_DIGEST_SIZE, '\000'); + auto src = reinterpret_cast(data.data()); + auto dst = reinterpret_cast(digest.data()); + SHA1(src, data.size(), dst); + return digest; +#else + SHA1 sha1; + sha1.addBytes(data); + return sha1.getDigest(); +#endif +} + +std::string sha256(std::string_view data) +{ + std::string digest(SHA256_DIGEST_SIZE, '\000'); + auto src = reinterpret_cast(data.data()); + auto dst = reinterpret_cast(digest.data()); +#if USE_OPENSSL + // can't call SHA256(), because it's defined by our sha256.c fallback + auto success = EVP_Digest(src, data.size(), dst, nullptr, EVP_sha256(), nullptr) == 1; + FATAL_ERROR_IF(!success, "sha256 failed"); +#else + SHA256(src, data.size(), dst); +#endif + return digest; +} + +} diff --git a/src/util/hashing.h b/src/util/hashing.h new file mode 100644 index 000000000..3e9a0fee7 --- /dev/null +++ b/src/util/hashing.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Luanti Contributors +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +namespace hashing +{ + +// Size of raw digest in bytes +constexpr size_t SHA1_DIGEST_SIZE = 20; +constexpr size_t SHA256_DIGEST_SIZE = 32; + +// Returns the digest of data +std::string sha1(std::string_view data); +std::string sha256(std::string_view data); + +} diff --git a/src/util/string.cpp b/src/util/string.cpp index 43e936788..a0eee85c1 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -1068,14 +1068,41 @@ void safe_print_string(std::ostream &os, std::string_view str) os.setf(flags); } - -v3f str_to_v3f(std::string_view str) +std::optional str_to_v3f(std::string_view str) { - v3f value; - Strfnd f(str); - f.next("("); - value.X = stof(f.next(",")); - value.Y = stof(f.next(",")); - value.Z = stof(f.next(")")); - return value; + str = trim(str); + + if (str.empty()) + return std::nullopt; + + // Strip parentheses if they exist + if (str.front() == '(' && str.back() == ')') { + str.remove_prefix(1); + str.remove_suffix(1); + str = trim(str); + } + + std::istringstream iss((std::string(str))); + + const auto expect_delimiter = [&]() { + const auto c = iss.get(); + return c == ' ' || c == ','; + }; + + v3f value; + if (!(iss >> value.X)) + return std::nullopt; + if (!expect_delimiter()) + return std::nullopt; + if (!(iss >> value.Y)) + return std::nullopt; + if (!expect_delimiter()) + return std::nullopt; + if (!(iss >> value.Z)) + return std::nullopt; + + if (!iss.eof()) + return std::nullopt; + + return value; } diff --git a/src/util/string.h b/src/util/string.h index 1874ccd2a..16c337b7f 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -20,6 +20,7 @@ #include #include #include +#include class Translations; @@ -789,9 +790,9 @@ std::string sanitize_untrusted(std::string_view str, bool keep_escapes = true); void safe_print_string(std::ostream &os, std::string_view str); /** - * Parses a string of form `(1, 2, 3)` to a v3f + * Parses a string of form `(1, 2, 3)` or `1, 2, 4` to a v3f * * @param str string * @return float vector */ -v3f str_to_v3f(std::string_view str); +std::optional str_to_v3f(std::string_view str); diff --git a/src/voxel.cpp b/src/voxel.cpp index b0a63b4f0..8f3858a1f 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -38,7 +38,7 @@ void VoxelManipulator::clear() void VoxelManipulator::print(std::ostream &o, const NodeDefManager *ndef, VoxelPrintMode mode) const { - const v3s16 &em = m_area.getExtent(); + auto &em = m_area.getExtent(); v3s16 of = m_area.MinEdge; o<<"size: "<= 0 && i < getVolume()); + return i >= 0 && static_cast(i) < getVolume(); } + bool operator==(const VoxelArea &other) const { return (MinEdge == other.MinEdge @@ -206,7 +206,7 @@ public: if(a.hasEmptyExtent()) { VoxelArea b = *this; - if (b.getVolume() != 0) + if (!b.hasEmptyExtent()) result.push_back(b); return; } @@ -215,7 +215,7 @@ public: const auto &take = [&result] (v3s16 min, v3s16 max) { VoxelArea b(min, max); - if (b.getVolume() != 0) + if (!b.hasEmptyExtent()) result.push_back(b); }; @@ -280,15 +280,16 @@ public: /** * Translate index in the X coordinate */ - static void add_x(const v3s16 &extent, u32 &i, s16 a) + static void add_x(const v3s32 &extent, u32 &i, s16 a) { + (void)extent; i += a; } /** * Translate index in the Y coordinate */ - static void add_y(const v3s16 &extent, u32 &i, s16 a) + static void add_y(const v3s32 &extent, u32 &i, s16 a) { i += a * extent.X; } @@ -296,7 +297,7 @@ public: /** * Translate index in the Z coordinate */ - static void add_z(const v3s16 &extent, u32 &i, s16 a) + static void add_z(const v3s32 &extent, u32 &i, s16 a) { i += a * extent.X * extent.Y; } @@ -304,7 +305,7 @@ public: /** * Translate index in space */ - static void add_p(const v3s16 &extent, u32 &i, v3s16 a) + static void add_p(const v3s32 &extent, u32 &i, v3s16 a) { i += a.Z * extent.X * extent.Y + a.Y * extent.X + a.X; } @@ -329,15 +330,20 @@ public: private: void cacheExtent() { - m_cache_extent = MaxEdge - MinEdge + v3s16(1,1,1); + m_cache_extent = { + MaxEdge.X - MinEdge.X + 1, + MaxEdge.Y - MinEdge.Y + 1, + MaxEdge.Z - MinEdge.Z + 1 + }; // If positions were sorted correctly this must always hold. // Note that this still permits empty areas (where MinEdge = MaxEdge + 1). - assert(m_cache_extent.X >= 0); - assert(m_cache_extent.Y >= 0); - assert(m_cache_extent.Z >= 0); + assert(m_cache_extent.X >= 0 && m_cache_extent.X <= MAX_EXTENT); + assert(m_cache_extent.Y >= 0 && m_cache_extent.Y <= MAX_EXTENT); + assert(m_cache_extent.Z >= 0 && m_cache_extent.Z <= MAX_EXTENT); } - v3s16 m_cache_extent = v3s16(0,0,0); + static constexpr s32 MAX_EXTENT = S16_MAX - S16_MIN + 1; + v3s32 m_cache_extent; }; enum : u8 { diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index cd2664938..5da39d6ed 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -767,7 +767,7 @@ void fill_with_sunlight(MMVManip *vm, const NodeDefManager *ndef, v2s16 offset, bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) { // Distance in array between two nodes on top of each other. - s16 ystride = vm->m_area.getExtent().X; + s32 ystride = vm->m_area.getExtent().X; // Cache the ignore node. MapNode ignore = MapNode(CONTENT_IGNORE); // For each column of nodes: diff --git a/textures/base/pack/down.png b/textures/base/pack/down.png index 7183ee536..594863dba 100644 Binary files a/textures/base/pack/down.png and b/textures/base/pack/down.png differ diff --git a/textures/base/pack/jump_btn.png b/textures/base/pack/jump_btn.png index 17b7cb586..dbb455b95 100644 Binary files a/textures/base/pack/jump_btn.png and b/textures/base/pack/jump_btn.png differ diff --git a/textures/base/pack/server_url.png b/textures/base/pack/server_url.png new file mode 100644 index 000000000..3f0490083 Binary files /dev/null and b/textures/base/pack/server_url.png differ diff --git a/textures/base/pack/server_view_clients.png b/textures/base/pack/server_view_clients.png new file mode 100644 index 000000000..87b569f93 Binary files /dev/null and b/textures/base/pack/server_view_clients.png differ diff --git a/util/ci/common.sh b/util/ci/common.sh index 201b182f2..4d4fe1195 100644 --- a/util/ci/common.sh +++ b/util/ci/common.sh @@ -7,6 +7,7 @@ install_linux_deps() { libpng-dev libjpeg-dev libgl1-mesa-dev libsdl2-dev libfreetype-dev libsqlite3-dev libhiredis-dev libogg-dev libgmp-dev libvorbis-dev libopenal-dev libpq-dev libleveldb-dev libcurl4-openssl-dev libzstd-dev + libssl-dev ) sudo apt-get update