diff --git a/SConstruct b/SConstruct index 0ae8f1a3872..5262a698216 100644 --- a/SConstruct +++ b/SConstruct @@ -1070,7 +1070,6 @@ if "check_c_headers" in env: env.AppendUnique(CPPDEFINES=[headers[header]]) -# FIXME: This method mixes both cosmetic progress stuff and cache handling... methods.show_progress(env) # TODO: replace this with `env.Dump(format="json")` # once we start requiring SCons 4.0 as min version. @@ -1102,3 +1101,5 @@ def purge_flaky_files(): atexit.register(purge_flaky_files) + +methods.clean_cache(env) diff --git a/core/SCsub b/core/SCsub index 1bd4eae16c4..c8267ae9600 100644 --- a/core/SCsub +++ b/core/SCsub @@ -140,7 +140,7 @@ if env["builtin_zstd"]: "decompress/zstd_decompress_block.c", "decompress/zstd_decompress.c", ] - if env["platform"] in ["android", "ios", "linuxbsd", "macos"]: + if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64": # Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S") thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources] diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 37a2608c102..f231e4010f3 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1489,15 +1489,6 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0); GLOBAL_DEF("audio/general/ios/mix_with_others", false); - PackedStringArray extensions; - extensions.push_back("gd"); - if (ClassDB::class_exists("CSharpScript")) { - extensions.push_back("cs"); - } - extensions.push_back("gdshader"); - - GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions); - _add_builtin_input_map(); // Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum. diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 8e2366fc95e..cb6832ea398 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -781,23 +781,14 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb } } - String actual_lib_path; OS::GDExtensionData data = { true, // also_set_library_path - &actual_lib_path, // r_resolved_path + &library_path, // r_resolved_path Engine::get_singleton()->is_editor_hint(), // generate_temp_files &abs_dependencies_paths, // library_dependencies }; Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data); - if (actual_lib_path.get_file() != abs_path.get_file()) { - // If temporary files are generated, let's change the library path to point at the original, - // because that's what we want to check to see if it's changed. - library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); - } else { - library_path = actual_lib_path; - } - ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path); diff --git a/core/input/godotcontrollerdb.txt b/core/input/godotcontrollerdb.txt index f5158bfabbf..8e8ec4c7186 100644 --- a/core/input/godotcontrollerdb.txt +++ b/core/input/godotcontrollerdb.txt @@ -8,7 +8,7 @@ __XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android, # Web -standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web, +standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a4,righttrigger:+a5,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web, Linux24c6581a,PowerA Xbox One Cabled,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, Linux0e6f0301,Logic 3 Controller (xbox compatible),a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, Linux045e028e,Microsoft X-Box 360 pad,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, diff --git a/core/math/expression.cpp b/core/math/expression.cpp index 636c2c16bf3..0692ece1e6f 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -30,12 +30,7 @@ #include "expression.h" -#include "core/io/marshalls.h" -#include "core/math/math_funcs.h" #include "core/object/class_db.h" -#include "core/object/ref_counted.h" -#include "core/os/os.h" -#include "core/variant/variant_parser.h" Error Expression::_get_token(Token &r_token) { while (true) { @@ -392,7 +387,6 @@ Error Expression::_get_token(Token &r_token) { if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; - } else { reading = READING_DONE; } @@ -419,7 +413,9 @@ Error Expression::_get_token(Token &r_token) { is_first_char = false; } - str_ofs--; + if (c != 0) { + str_ofs--; + } r_token.type = TK_CONSTANT; diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 3060f31970c..7cb44656394 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -105,6 +105,9 @@ public: static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); } static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); } + static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); } + static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); } + static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); } static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); } diff --git a/core/math/random_pcg.cpp b/core/math/random_pcg.cpp index 55787a0b57e..c286a604218 100644 --- a/core/math/random_pcg.cpp +++ b/core/math/random_pcg.cpp @@ -60,6 +60,11 @@ int64_t RandomPCG::rand_weighted(const Vector &p_weights) { } } + for (int64_t i = weights_size - 1; i >= 0; --i) { + if (weights[i] > 0) { + return i; + } + } return -1; } diff --git a/core/object/object.cpp b/core/object/object.cpp index e4d1a8fc9ab..3f5378b7f68 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -45,14 +45,17 @@ #ifdef DEBUG_ENABLED struct _ObjectDebugLock { - Object *obj; + ObjectID obj_id; _ObjectDebugLock(Object *p_obj) { - obj = p_obj; - obj->_lock_index.ref(); + obj_id = p_obj->get_instance_id(); + p_obj->_lock_index.ref(); } ~_ObjectDebugLock() { - obj->_lock_index.unref(); + Object *obj_ptr = ObjectDB::get_instance(obj_id); + if (likely(obj_ptr)) { + obj_ptr->_lock_index.unref(); + } } }; @@ -2097,7 +2100,11 @@ Object::~Object() { // Disconnect signals that connect to this object. while (connections.size()) { Connection c = connections.front()->get(); - bool disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true); + Object *obj = c.callable.get_object(); + bool disconnected = false; + if (likely(obj)) { + disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true); + } if (unlikely(!disconnected)) { // If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here. connections.pop_front(); diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index 7b643e46378..73f7ec5a544 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -142,6 +142,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_debug_get_current_stack_info); GDVIRTUAL_BIND(_reload_all_scripts); + GDVIRTUAL_BIND(_reload_scripts, "scripts", "soft_reload"); GDVIRTUAL_BIND(_reload_tool_script, "script", "soft_reload"); GDVIRTUAL_BIND(_get_recognized_extensions); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 56b9fa84756..86466395355 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -468,7 +468,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T p_caller_pool_thread->signaled = false; if (IS_WAIT_OVER) { - p_caller_pool_thread->yield_is_over = false; + if (unlikely(p_task == ThreadData::YIELDING)) { + p_caller_pool_thread->yield_is_over = false; + } + if (!exit_threads && was_signaled) { // This thread was awaken for some additional reason, but it's about to exit. // Let's find out what may be pending and forward the requests. diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 658297d805f..5d59d65f92f 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -39,19 +39,10 @@ StaticCString StaticCString::create(const char *p_ptr) { return scs; } -StringName::_Data *StringName::_table[STRING_TABLE_LEN]; - StringName _scs_create(const char *p_chr, bool p_static) { return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName()); } -bool StringName::configured = false; -Mutex StringName::mutex; - -#ifdef DEBUG_ENABLED -bool StringName::debug_stringname = false; -#endif - void StringName::setup() { ERR_FAIL_COND(configured); for (int i = 0; i < STRING_TABLE_LEN; i++) { diff --git a/core/string/string_name.h b/core/string/string_name.h index 89b4c07e0e8..0eb98cf64b5 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -67,7 +67,7 @@ class StringName { _Data() {} }; - static _Data *_table[STRING_TABLE_LEN]; + static inline _Data *_table[STRING_TABLE_LEN]; _Data *_data = nullptr; @@ -75,10 +75,10 @@ class StringName { friend void register_core_types(); friend void unregister_core_types(); friend class Main; - static Mutex mutex; + static inline Mutex mutex; static void setup(); static void cleanup(); - static bool configured; + static inline bool configured = false; #ifdef DEBUG_ENABLED struct DebugSortReferences { bool operator()(const _Data *p_left, const _Data *p_right) const { @@ -86,7 +86,7 @@ class StringName { } }; - static bool debug_stringname; + static inline bool debug_stringname = false; #endif StringName(_Data *p_data) { _data = p_data; } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 3d37e17ef83..07d754daf79 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1537,13 +1537,16 @@ Vector String::split_floats(const String &p_splitter, bool p_allow_empty int from = 0; int len = length(); + String buffer = *this; while (true) { int end = find(p_splitter, from); if (end < 0) { end = len; } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_float(&get_data()[from])); + buffer[end] = 0; + ret.push_back(String::to_float(&buffer.get_data()[from])); + buffer[end] = _cowdata.get(end); } if (end == len) { @@ -1561,6 +1564,7 @@ Vector String::split_floats_mk(const Vector &p_splitters, bool p_ int from = 0; int len = length(); + String buffer = *this; while (true) { int idx; int end = findmk(p_splitters, from, &idx); @@ -1572,7 +1576,9 @@ Vector String::split_floats_mk(const Vector &p_splitters, bool p_ } if (p_allow_empty || (end > from)) { - ret.push_back(String::to_float(&get_data()[from])); + buffer[end] = 0; + ret.push_back(String::to_float(&buffer.get_data()[from])); + buffer[end] = _cowdata.get(end); } if (end == len) { diff --git a/doc/classes/AStar2D.xml b/doc/classes/AStar2D.xml index 2ea6aa15bdb..cfb7d00861f 100644 --- a/doc/classes/AStar2D.xml +++ b/doc/classes/AStar2D.xml @@ -169,7 +169,7 @@ astar.ConnectPoints(2, 3, false); astar.ConnectPoints(4, 3, false); astar.ConnectPoints(1, 4, false); - int[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3] + long[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3] [/csharp] [/codeblocks] If you change the 2nd point's weight to 3, then the result will be [code][1, 4, 3][/code] instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2. @@ -209,7 +209,7 @@ astar.ConnectPoints(1, 2, true); astar.ConnectPoints(1, 3, true); - int[] neighbors = astar.GetPointConnections(1); // Returns [2, 3] + long[] neighbors = astar.GetPointConnections(1); // Returns [2, 3] [/csharp] [/codeblocks] diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml index 281f4edcc1f..4448698c320 100644 --- a/doc/classes/AStar3D.xml +++ b/doc/classes/AStar3D.xml @@ -197,7 +197,7 @@ astar.ConnectPoints(2, 3, false); astar.ConnectPoints(4, 3, false); astar.ConnectPoints(1, 4, false); - int[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3] + long[] res = astar.GetIdPath(1, 3); // Returns [1, 2, 3] [/csharp] [/codeblocks] If you change the 2nd point's weight to 3, then the result will be [code][1, 4, 3][/code] instead, because now even though the distance is longer, it's "easier" to get through point 4 than through point 2. @@ -236,7 +236,7 @@ astar.ConnectPoints(1, 2, true); astar.ConnectPoints(1, 3, true); - int[] neighbors = astar.GetPointConnections(1); // Returns [2, 3] + long[] neighbors = astar.GetPointConnections(1); // Returns [2, 3] [/csharp] [/codeblocks] diff --git a/doc/classes/AnimationNodeBlendSpace2D.xml b/doc/classes/AnimationNodeBlendSpace2D.xml index 596767599c0..da79e15be15 100644 --- a/doc/classes/AnimationNodeBlendSpace2D.xml +++ b/doc/classes/AnimationNodeBlendSpace2D.xml @@ -5,7 +5,7 @@ A resource used by [AnimationNodeBlendTree]. - [AnimationNodeBlendSpace1D] represents a virtual 2D space on which [AnimationRootNode]s are placed. Outputs the linear blend of the three adjacent animations using a [Vector2] weight. Adjacent in this context means the three [AnimationRootNode]s making up the triangle that contains the current value. + [AnimationNodeBlendSpace2D] represents a virtual 2D space on which [AnimationRootNode]s are placed. Outputs the linear blend of the three adjacent animations using a [Vector2] weight. Adjacent in this context means the three [AnimationRootNode]s making up the triangle that contains the current value. You can add vertices to the blend space with [method add_blend_point] and automatically triangulate it by setting [member auto_triangles] to [code]true[/code]. Otherwise, use [method add_triangle] and [method remove_triangle] to triangulate the blend space by hand. @@ -93,7 +93,7 @@ - Updates the position of the point at index [param point] on the blend axis. + Updates the position of the point at index [param point] in the blend space. diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index bd0e05f8e02..79e74c4cc4a 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -243,7 +243,7 @@ var numbers = [1, 2, 3] var extra = [4, 5, 6] numbers.append_array(extra) - print(nums) # Prints [1, 2, 3, 4, 5, 6] + print(numbers) # Prints [1, 2, 3, 4, 5, 6] [/codeblock] @@ -715,7 +715,7 @@ Sorts the array using a custom [Callable]. - [param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]behind[/i] the second one, otherwise it should return [code]false[/code]. + [param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]before[/i] the second one, otherwise it should return [code]false[/code]. [codeblock] func sort_ascending(a, b): if a[1] < b[1]: @@ -728,7 +728,7 @@ print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9]] # Sort descending, using a lambda function. - my_items.sort_custom(func(a, b): return a[0] > b[0]) + my_items.sort_custom(func(a, b): return a[1] > b[1]) print(my_items) # Prints [["Apple", 9], ["Tomato", 5], ["Rice", 4]] [/codeblock] It may also be necessary to use this method to sort strings by natural order, with [method String.naturalnocasecmp_to], as in the following example: diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml index 0e75c65f50a..6ce9616fb61 100644 --- a/doc/classes/Curve2D.xml +++ b/doc/classes/Curve2D.xml @@ -88,7 +88,7 @@ - Returns the position between the vertex [param idx] and the vertex [code]idx + 1[/code], where [param t] controls if the point is the first vertex ([code]t = 0.0[/code]), the last vertex ([code]t = 1.0[/code]), or in between. Values of [param t] outside the range ([code]0.0 >= t <=1[/code]) give strange, but predictable results. + Returns the position between the vertex [param idx] and the vertex [code]idx + 1[/code], where [param t] controls if the point is the first vertex ([code]t = 0.0[/code]), the last vertex ([code]t = 1.0[/code]), or in between. Values of [param t] outside the range ([code]0.0 <= t <= 1.0[/code]) give strange, but predictable results. If [param idx] is out of bounds it is truncated to the first or last vertex, and [param t] is ignored. If the curve has no points, the function sends an error to the console, and returns [code](0, 0)[/code]. diff --git a/doc/classes/NavigationLink2D.xml b/doc/classes/NavigationLink2D.xml index 0892c9ec440..2e1c962dd15 100644 --- a/doc/classes/NavigationLink2D.xml +++ b/doc/classes/NavigationLink2D.xml @@ -29,6 +29,12 @@ Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + + + + Returns the current navigation map [RID] used by this link. + + @@ -57,6 +63,13 @@ Based on [param value], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [param layer_number] between 1 and 32. + + + + + Sets the [RID] of the navigation map this link should use. By default the link will automatically join the [World2D] default navigation map so this function is only required to override the default map. + + diff --git a/doc/classes/NavigationLink3D.xml b/doc/classes/NavigationLink3D.xml index 0fcc106beb7..174228ea5b2 100644 --- a/doc/classes/NavigationLink3D.xml +++ b/doc/classes/NavigationLink3D.xml @@ -29,6 +29,12 @@ Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [param layer_number] between 1 and 32. + + + + Returns the current navigation map [RID] used by this link. + + @@ -57,6 +63,13 @@ Based on [param value], enables or disables the specified layer in the [member navigation_layers] bitmask, given a [param layer_number] between 1 and 32. + + + + + Sets the [RID] of the navigation map this link should use. By default the link will automatically join the [World3D] default navigation map so this function is only required to override the default map. + + diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index ed420f4587d..0cfa3a5d4a0 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -136,7 +136,7 @@ } } - private List<int> _numbers = new(); + private Godot.Collections.Array<int> _numbers = new(); public override Godot.Collections.Array<Godot.Collections.Dictionary> _GetPropertyList() { @@ -173,7 +173,7 @@ if (propertyName.StartsWith("number_")) { int index = int.Parse(propertyName.Substring("number_".Length)); - numbers[index] = value.As<int>(); + _numbers[index] = value.As<int>(); return true; } return false; diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b20b374382e..fef42937920 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1000,7 +1000,7 @@ prime-run %command% [/codeblock] - + Text-based file extensions to include in the script editor's "Find in Files" feature. You can add e.g. [code]tscn[/code] if you wish to also parse your scene files, especially if you use built-in scripts which are serialized in the scene files. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 3c9f0fc7aff..c64d5bf0bd9 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -944,7 +944,8 @@ - A copy of the canvas item will be drawn with a local offset of the mirroring [Vector2]. + A copy of the canvas item will be drawn with a local offset of the [param mirroring]. + [b]Note:[/b] This is equivalent to calling [method canvas_set_item_repeat] like [code]canvas_set_item_repeat(item, mirroring, 1)[/code], with an additional check ensuring [param canvas] is a parent of [param item]. diff --git a/doc/classes/ResourceImporterImageFont.xml b/doc/classes/ResourceImporterImageFont.xml index 6dbfa1d6514..663c9060019 100644 --- a/doc/classes/ResourceImporterImageFont.xml +++ b/doc/classes/ResourceImporterImageFont.xml @@ -40,7 +40,7 @@ Kerning pairs for the font. Kerning pair adjust the spacing between two characters. - Each string consist of three space separated values: "from" string, "to" string and integer offset. Each combination form the two string for a kerning pair, e.g, [code]ab cd -3[/code] will create kerning pairs [code]ac[/code], [code]ad[/code], [code]bc[/code], and [code]bd[/code] with offset [code]-3[/code]. + Each string consist of three space separated values: "from" string, "to" string and integer offset. Each combination form the two string for a kerning pair, e.g, [code]ab cd -3[/code] will create kerning pairs [code]ac[/code], [code]ad[/code], [code]bc[/code], and [code]bd[/code] with offset [code]-3[/code]. [code]\uXXXX[/code] escape sequences can be used to add Unicode characters. Number of rows in the font image. See also [member columns]. diff --git a/doc/classes/ScriptLanguageExtension.xml b/doc/classes/ScriptLanguageExtension.xml index cc47ca274d1..f9d9e4f513e 100644 --- a/doc/classes/ScriptLanguageExtension.xml +++ b/doc/classes/ScriptLanguageExtension.xml @@ -314,6 +314,13 @@ + + + + + + + diff --git a/doc/classes/Theme.xml b/doc/classes/Theme.xml index eb3c1705835..479456ae667 100644 --- a/doc/classes/Theme.xml +++ b/doc/classes/Theme.xml @@ -551,7 +551,7 @@ The default font size of this theme resource. Used as the default value when trying to fetch a font size value that doesn't exist in this theme or is in invalid state. If the default font size is also missing or invalid, the engine fallback value is used (see [member ThemeDB.fallback_font_size]). - Values below [code]0[/code] are invalid and can be used to unset the property. Use [method has_default_font_size] to check if this value is valid. + Values below [code]1[/code] are invalid and can be used to unset the property. Use [method has_default_font_size] to check if this value is valid. diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index a33fc977c6a..af84b847579 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -2143,17 +2143,28 @@ void RenderingDeviceDriverD3D12::command_pipeline_barrier(CommandBufferID p_cmd_ for (uint32_t i = 0; i < p_texture_barriers.size(); i++) { const TextureBarrier &texture_barrier_rd = p_texture_barriers[i]; const TextureInfo *texture_info = (const TextureInfo *)(texture_barrier_rd.texture.id); + if (texture_info->main_texture) { + texture_info = texture_info->main_texture; + } _rd_stages_and_access_to_d3d12(p_src_stages, texture_barrier_rd.prev_layout, texture_barrier_rd.src_access, texture_barrier_d3d12.SyncBefore, texture_barrier_d3d12.AccessBefore); _rd_stages_and_access_to_d3d12(p_dst_stages, texture_barrier_rd.next_layout, texture_barrier_rd.dst_access, texture_barrier_d3d12.SyncAfter, texture_barrier_d3d12.AccessAfter); texture_barrier_d3d12.LayoutBefore = _rd_texture_layout_to_d3d12_barrier_layout(texture_barrier_rd.prev_layout); texture_barrier_d3d12.LayoutAfter = _rd_texture_layout_to_d3d12_barrier_layout(texture_barrier_rd.next_layout); texture_barrier_d3d12.pResource = texture_info->resource; - texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = texture_barrier_rd.subresources.base_mipmap; - texture_barrier_d3d12.Subresources.NumMipLevels = texture_barrier_rd.subresources.mipmap_count; - texture_barrier_d3d12.Subresources.FirstArraySlice = texture_barrier_rd.subresources.base_layer; - texture_barrier_d3d12.Subresources.NumArraySlices = texture_barrier_rd.subresources.layer_count; - texture_barrier_d3d12.Subresources.FirstPlane = _compute_plane_slice(texture_info->format, texture_barrier_rd.subresources.aspect); - texture_barrier_d3d12.Subresources.NumPlanes = format_get_plane_count(texture_info->format); + if (texture_barrier_rd.subresources.mipmap_count == texture_info->mipmaps && texture_barrier_rd.subresources.layer_count == texture_info->layers) { + // So, all resources. Then, let's be explicit about it so D3D12 doesn't think + // we are dealing with a subset of subresources. + texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = 0xffffffff; + texture_barrier_d3d12.Subresources.NumMipLevels = 0; + // Because NumMipLevels == 0, all the other fields are ignored by D3D12. + } else { + texture_barrier_d3d12.Subresources.IndexOrFirstMipLevel = texture_barrier_rd.subresources.base_mipmap; + texture_barrier_d3d12.Subresources.NumMipLevels = texture_barrier_rd.subresources.mipmap_count; + texture_barrier_d3d12.Subresources.FirstArraySlice = texture_barrier_rd.subresources.base_layer; + texture_barrier_d3d12.Subresources.NumArraySlices = texture_barrier_rd.subresources.layer_count; + texture_barrier_d3d12.Subresources.FirstPlane = _compute_plane_slice(texture_info->format, texture_barrier_rd.subresources.aspect); + texture_barrier_d3d12.Subresources.NumPlanes = format_get_plane_count(texture_info->format); + } texture_barrier_d3d12.Flags = (texture_barrier_rd.prev_layout == RDD::TEXTURE_LAYOUT_UNDEFINED) ? D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAG_NONE; texture_barriers.push_back(texture_barrier_d3d12); } diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 941b1a1b280..78fd8a6e478 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -468,7 +468,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ update_skeletons = false; } // Canvas group begins here, render until before this item - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached); item_count = 0; if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) { @@ -499,7 +499,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ mesh_storage->update_mesh_instances(); update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info, material_screen_texture_mipmaps_cached); item_count = 0; if (ci->canvas_group->blur_mipmaps) { @@ -523,7 +523,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ } //render anything pending, including clearing if no items - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached); item_count = 0; texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps); @@ -553,7 +553,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ mesh_storage->update_mesh_instances(); update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info, material_screen_texture_mipmaps_cached); //then reset item_count = 0; } @@ -573,10 +573,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.current_instance_buffer_index = 0; } -void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) { +void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info, bool p_backbuffer_has_mipmaps) { GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); - canvas_begin(p_to_render_target, p_to_backbuffer); + canvas_begin(p_to_render_target, p_to_backbuffer, p_backbuffer_has_mipmaps); if (p_item_count <= 0) { // Nothing to draw, just call canvas_begin() to clear the render target and return. @@ -647,18 +647,17 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, Point2()); } else { Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2); - Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos; - Point2 pos = start_pos; + Point2 offset; - do { - do { - _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, pos); - pos.y += ci->repeat_size.y; - } while (pos.y < end_pos.y); - - pos.x += ci->repeat_size.x; - pos.y = start_pos.y; - } while (pos.x < end_pos.x); + int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0; + int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0; + for (int ry = 0; ry <= repeat_times_y; ry++) { + offset.y = start_pos.y + ry * ci->repeat_size.y; + for (int rx = 0; rx <= repeat_times_x; rx++) { + offset.x = start_pos.x + rx * ci->repeat_size.x; + _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, offset); + } + } } } @@ -809,7 +808,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou state.last_item_index += index; } -void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, const Point2 &p_offset) { +void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, const Point2 &p_repeat_offset) { RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? state.default_filter : p_item->texture_filter; if (texture_filter != state.canvas_instance_batches[state.current_batch_index].filter) { @@ -826,11 +825,11 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend state.canvas_instance_batches[state.current_batch_index].repeat = texture_repeat; } - Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform; - - if (p_offset.x || p_offset.y) { - base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed. + Transform2D base_transform = p_item->final_transform; + if (p_item->repeat_source_item && (p_repeat_offset.x || p_repeat_offset.y)) { + base_transform.columns[2] += p_item->repeat_source_item->final_transform.basis_xform(p_repeat_offset); } + base_transform = p_canvas_transform_inverse * base_transform; Transform2D draw_transform; // Used by transform command @@ -2170,7 +2169,7 @@ bool RasterizerCanvasGLES3::free(RID p_rid) { void RasterizerCanvasGLES3::update() { } -void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer) { +void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); GLES3::Config *config = GLES3::Config::get_singleton(); @@ -2185,6 +2184,7 @@ void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backb glBindFramebuffer(GL_FRAMEBUFFER, render_target->fbo); glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 4); glBindTexture(GL_TEXTURE_2D, render_target->backbuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_backbuffer_has_mipmaps ? render_target->mipmap_count - 1 : 0); } if (render_target->is_transparent || p_to_backbuffer) { diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index 46ed479a3d6..9c0d0abccb0 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -335,7 +335,7 @@ public: typedef void Texture; - void canvas_begin(RID p_to_render_target, bool p_to_backbuffer); + void canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps); //virtual void draw_window_margins(int *black_margin, RID *black_image) override; void draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample); @@ -361,8 +361,8 @@ public: void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size); void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) override; - void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr); - void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_offset); + void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr, bool p_backbuffer_has_mipmaps = false); + void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_repeat_offset); void _render_batch(Light *p_lights, uint32_t p_index, RenderingMethod::RenderInfo *r_render_info = nullptr); bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization); void _new_batch(bool &r_batch_broken); diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 37e7256d764..19ef3d416ce 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -62,6 +62,10 @@ #define _EXT_DEBUG_SEVERITY_LOW_ARB 0x9148 #define _EXT_DEBUG_OUTPUT 0x92E0 +#ifndef GL_FRAMEBUFFER_SRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif + #ifndef GLAPIENTRY #if defined(WINDOWS_ENABLED) #define GLAPIENTRY APIENTRY @@ -345,6 +349,9 @@ RasterizerGLES3::RasterizerGLES3() { } } + // Disable OpenGL linear to sRGB conversion, because Godot will always do this conversion itself. + glDisable(GL_FRAMEBUFFER_SRGB); + // OpenGL needs to be initialized before initializing the Rasterizers config = memnew(GLES3::Config); utilities = memnew(GLES3::Utilities); diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 84b6ab4bd8b..2ee439c6c44 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -777,7 +777,6 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, ERR_FAIL_COND(p_env.is_null()); Sky *sky = sky_owner.get_or_null(environment_get_sky(p_env)); - ERR_FAIL_NULL(sky); GLES3::SkyMaterialData *material_data = nullptr; RID sky_material; @@ -851,6 +850,15 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::SKY_ENERGY_MULTIPLIER, p_sky_energy_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + Color fog_color = environment_get_fog_light_color(p_env).srgb_to_linear() * environment_get_fog_light_energy(p_env); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_ENABLED, environment_get_fog_enabled(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_AERIAL_PERSPECTIVE, environment_get_fog_aerial_perspective(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_LIGHT_COLOR, fog_color, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_SUN_SCATTER, environment_get_fog_sun_scatter(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_DENSITY, environment_get_fog_density(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_SKY_AFFECT, environment_get_fog_sky_affect(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::DIRECTIONAL_LIGHT_COUNT, sky_globals.directional_light_count, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants); + if (p_use_multiview) { glBindBufferBase(GL_UNIFORM_BUFFER, SKY_MULTIVIEW_UNIFORM_LOCATION, scene_state.multiview_buffer); glBindBuffer(GL_UNIFORM_BUFFER, 0); @@ -904,7 +912,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p RS::SkyMode sky_mode = sky->mode; if (sky_mode == RS::SKY_MODE_AUTOMATIC) { - if (shader_data->uses_time || shader_data->uses_position) { + if ((shader_data->uses_time || shader_data->uses_position) && sky->radiance_size == 256) { update_single_frame = true; sky_mode = RS::SKY_MODE_REALTIME; } else if (shader_data->uses_light || shader_data->ubo_size > 0) { @@ -925,7 +933,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p int max_processing_layer = sky->mipmap_count; // Update radiance cubemap - if (sky->reflection_dirty && (sky->processing_layer > max_processing_layer || update_single_frame)) { + if (sky->reflection_dirty && (sky->processing_layer >= max_processing_layer || update_single_frame)) { static const Vector3 view_normals[6] = { Vector3(+1, 0, 0), Vector3(-1, 0, 0), @@ -2587,7 +2595,7 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ scene_state.enable_gl_depth_draw(false); - if (draw_sky) { + if (draw_sky || draw_sky_fog_only) { RENDER_TIMESTAMP("Render Sky"); scene_state.enable_gl_depth_test(true); diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 4a15ed827a0..5a0f394db0b 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -698,7 +698,8 @@ void ShaderGLES3::_clear_version(Version *p_version) { void ShaderGLES3::_initialize_version(Version *p_version) { ERR_FAIL_COND(p_version->variants.size() > 0); - if (shader_cache_dir_valid && _load_from_cache(p_version)) { + bool use_cache = shader_cache_dir_valid && !(feedback_count > 0 && GLES3::Config::get_singleton()->disable_transform_feedback_shader_cache); + if (use_cache && _load_from_cache(p_version)) { return; } p_version->variants.reserve(variant_count); @@ -709,7 +710,7 @@ void ShaderGLES3::_initialize_version(Version *p_version) { _compile_specialization(spec, i, p_version, specialization_default_mask); p_version->variants[i].insert(specialization_default_mask, spec); } - if (shader_cache_dir_valid) { + if (use_cache) { _save_to_cache(p_version); } } diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 6dd04af6b60..14c69d01b84 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -583,6 +583,8 @@ void main() { #define SHADER_IS_SRGB true +#define FLAGS_NON_UNIFORM_SCALE (1 << 4) + /* Varyings */ #if defined(COLOR_USED) @@ -955,6 +957,7 @@ ivec2 multiview_uv(ivec2 uv) { uniform highp mat4 world_transform; uniform mediump float opaque_prepass_threshold; +uniform highp uint model_flags; #if defined(RENDER_MATERIAL) layout(location = 0) out vec4 albedo_output_buffer; @@ -1521,6 +1524,13 @@ void main() { vec3 light_vertex = vertex; #endif //LIGHT_VERTEX_USED + highp mat3 model_normal_matrix; + if (bool(model_flags & uint(FLAGS_NON_UNIFORM_SCALE))) { + model_normal_matrix = transpose(inverse(mat3(model_matrix))); + } else { + model_normal_matrix = mat3(model_matrix); + } + { #CODE : FRAGMENT } @@ -1728,16 +1738,10 @@ void main() { vec3 n = normalize(lightmap_normal_xform * normal); - ambient_light += lm_light_l0 * 0.282095f; - ambient_light += lm_light_l1n1 * 0.32573 * n.y * lightmap_exposure_normalization; - ambient_light += lm_light_l1_0 * 0.32573 * n.z * lightmap_exposure_normalization; - ambient_light += lm_light_l1p1 * 0.32573 * n.x * lightmap_exposure_normalization; - if (metallic > 0.01) { // Since the more direct bounced light is lost, we can kind of fake it with this trick. - vec3 r = reflect(normalize(-vertex), normal); - specular_light += lm_light_l1n1 * 0.32573 * r.y * lightmap_exposure_normalization; - specular_light += lm_light_l1_0 * 0.32573 * r.z * lightmap_exposure_normalization; - specular_light += lm_light_l1p1 * 0.32573 * r.x * lightmap_exposure_normalization; - } + ambient_light += lm_light_l0 * lightmap_exposure_normalization; + ambient_light += lm_light_l1n1 * n.y * lightmap_exposure_normalization; + ambient_light += lm_light_l1_0 * n.z * lightmap_exposure_normalization; + ambient_light += lm_light_l1p1 * n.x * lightmap_exposure_normalization; #else ambient_light += textureLod(lightmap_textures, uvw, 0.0).rgb * lightmap_exposure_normalization; #endif @@ -1871,7 +1875,7 @@ void main() { alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0)); #if defined(ALPHA_SCISSOR_USED) - if (alpha < alpha_scissor) { + if (alpha < alpha_scissor_threshold) { discard; } #endif // !ALPHA_SCISSOR_USED diff --git a/drivers/gles3/shaders/skeleton.glsl b/drivers/gles3/shaders/skeleton.glsl index aad856a5a2f..66befbc3b2c 100644 --- a/drivers/gles3/shaders/skeleton.glsl +++ b/drivers/gles3/shaders/skeleton.glsl @@ -59,7 +59,7 @@ layout(location = 10) in highp uvec4 in_bone_attrib; layout(location = 11) in mediump vec4 in_weight_attrib; #endif -uniform mediump sampler2D skeleton_texture; // texunit:0 +uniform highp sampler2D skeleton_texture; // texunit:0 #endif /* clang-format on */ diff --git a/drivers/gles3/shaders/sky.glsl b/drivers/gles3/shaders/sky.glsl index 9de65ba960d..f734e4b3554 100644 --- a/drivers/gles3/shaders/sky.glsl +++ b/drivers/gles3/shaders/sky.glsl @@ -108,11 +108,11 @@ uniform float sky_energy_multiplier; uniform float luminance_multiplier; uniform float fog_aerial_perspective; -uniform vec3 fog_light_color; +uniform vec4 fog_light_color; uniform float fog_sun_scatter; uniform bool fog_enabled; uniform float fog_density; -uniform float z_far; +uniform float fog_sky_affect; uniform uint directional_light_count; #ifdef USE_MULTIVIEW @@ -135,6 +135,24 @@ vec3 interleaved_gradient_noise(vec2 pos) { } #endif +#if !defined(DISABLE_FOG) +vec4 fog_process(vec3 view, vec3 sky_color) { + vec3 fog_color = mix(fog_light_color.rgb, sky_color, fog_aerial_perspective); + + if (fog_sun_scatter > 0.001) { + vec4 sun_scatter = vec4(0.0); + float sun_total = 0.0; + for (uint i = 0u; i < directional_light_count; i++) { + vec3 light_color = directional_lights.data[i].color_size.xyz * directional_lights.data[i].direction_energy.w; + float light_amount = pow(max(dot(view, directional_lights.data[i].direction_energy.xyz), 0.0), 8.0); + fog_color += light_color * light_amount * fog_sun_scatter; + } + } + + return vec4(fog_color, 1.0); +} +#endif // !DISABLE_FOG + void main() { vec3 cube_normal; #ifdef USE_MULTIVIEW @@ -203,6 +221,21 @@ void main() { // Convert to Linear for tonemapping so color matches scene shader better color = srgb_to_linear(color); + +#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS) + + // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky. + if (fog_enabled) { + vec4 fog = fog_process(cube_normal, color.rgb); + color.rgb = mix(color.rgb, fog.rgb, fog.a * fog_sky_affect); + } + + if (custom_fog.a > 0.0) { + color.rgb = mix(color.rgb, custom_fog.rgb, custom_fog.a); + } + +#endif // DISABLE_FOG + color *= exposure; #ifdef APPLY_TONEMAPPING color = apply_tonemapping(color, white); diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index a28b050bf80..d6472c44c13 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -121,7 +121,7 @@ Config::Config() { #ifdef WEB_ENABLED msaa_supported = (msaa_max_samples > 0); #else - msaa_supported = extensions.has("GL_EXT_framebuffer_multisample"); + msaa_supported = true; #endif #ifndef IOS_ENABLED #ifdef WEB_ENABLED @@ -218,6 +218,8 @@ Config::Config() { //https://github.com/godotengine/godot/issues/92662#issuecomment-2161199477 //disable_particles_workaround = false; } + } else if (rendering_device_name == "PowerVR Rogue GE8320") { + disable_transform_feedback_shader_cache = true; } } diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h index 0c9f9bc275e..ff72fc5b589 100644 --- a/drivers/gles3/storage/config.h +++ b/drivers/gles3/storage/config.h @@ -96,6 +96,9 @@ public: bool disable_particles_workaround = false; // set to 'true' to disable 'GPUParticles' bool flip_xy_workaround = false; + // PowerVR GE 8320 workaround + bool disable_transform_feedback_shader_cache = false; + #ifdef ANDROID_ENABLED PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr; PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr; diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index f9547502f4b..e0daa2dc296 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -1518,6 +1518,11 @@ bool LightStorage::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, int *p_i uint64_t min_pass = 0; // Pass of the existing one, try to use the least recently used one (LRU fashion). for (int j = 0; j < sc; j++) { + if (sarr[j].owner_is_omni != is_omni) { + // Existing light instance type doesn't match new light instance type skip. + continue; + } + LightInstance *sli = light_instance_owner.get_or_null(sarr[j].owner); if (!sli) { // Found a released light instance. diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index d8a5b960b80..b67317c99e5 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -301,7 +301,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) Vector ir = new_surface.index_data; wr = wf_indices.ptrw(); - if (new_surface.vertex_count < (1 << 16)) { + if (new_surface.vertex_count <= 65536) { // Read 16 bit indices. const uint16_t *src_idx = (const uint16_t *)ir.ptr(); for (uint32_t i = 0; i + 5 < wf_index_count; i += 6) { @@ -743,6 +743,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const { } void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) { + ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh."); Mesh *mesh = mesh_owner.get_or_null(p_mesh); ERR_FAIL_NULL(mesh); diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 3b1373c9287..e96c8bb8a8f 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1039,7 +1039,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { data.resize(data_size); ERR_FAIL_COND_V(data.is_empty(), Ref()); - image = Image::create_from_data(texture->width, texture->height, texture->mipmaps > 1, texture->real_format, data); + image = Image::create_from_data(texture->alloc_width, texture->alloc_height, texture->mipmaps > 1, texture->real_format, data); ERR_FAIL_COND_V(image->is_empty(), Ref()); if (texture->format != texture->real_format) { image->convert(texture->format); @@ -1095,7 +1095,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { data.resize(data_size); ERR_FAIL_COND_V(data.is_empty(), Ref()); - image = Image::create_from_data(texture->width, texture->height, false, Image::FORMAT_RGBA8, data); + image = Image::create_from_data(texture->alloc_width, texture->alloc_height, false, Image::FORMAT_RGBA8, data); ERR_FAIL_COND_V(image->is_empty(), Ref()); if (texture->format != Image::FORMAT_RGBA8) { @@ -1497,11 +1497,9 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref &p_image, glPixelStorei(GL_UNPACK_ALIGNMENT, 4); if (texture->target == GL_TEXTURE_2D_ARRAY) { if (p_initialize) { - glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, i, internal_format, w, h, texture->layers, 0, - size * texture->layers, &read[ofs]); - } else { - glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, i, 0, 0, p_layer, w, h, 1, internal_format, size, &read[ofs]); + glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, i, internal_format, w, h, texture->layers, 0, size * texture->layers, nullptr); } + glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, i, 0, 0, p_layer, w, h, 1, internal_format, size, &read[ofs]); } else { glCompressedTexImage2D(blit_target, i, internal_format, w, h, 0, size, &read[ofs]); } diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 210507c2c63..ea8b42b2e4c 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -383,7 +383,7 @@ uint64_t FileAccessUnix::_get_modified_time(const String &p_file) { if (!err) { return status.st_mtime; } else { - print_verbose("Failed to get modified time for: " + p_file + ""); + WARN_PRINT("Failed to get modified time for: " + p_file); return 0; } } diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index 9885d9d7eed..1508a4740b0 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -553,15 +553,40 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) { file = file.substr(0, file.length() - 1); } - struct _stat st; - int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st); + HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); - if (rv == 0) { - return st.st_mtime; - } else { - print_verbose("Failed to get modified time for: " + p_file + ""); - return 0; + if (handle != INVALID_HANDLE_VALUE) { + FILETIME ft_create, ft_write; + + bool status = GetFileTime(handle, &ft_create, nullptr, &ft_write); + + CloseHandle(handle); + + if (status) { + uint64_t ret = 0; + + // If write time is invalid, fallback to creation time. + if (ft_write.dwHighDateTime == 0 && ft_write.dwLowDateTime == 0) { + ret = ft_create.dwHighDateTime; + ret <<= 32; + ret |= ft_create.dwLowDateTime; + } else { + ret = ft_write.dwHighDateTime; + ret <<= 32; + ret |= ft_write.dwLowDateTime; + } + + const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000; + const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL; + + if (ret >= TICKS_TO_UNIX_EPOCH) { + return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND; + } + } } + + print_verbose("Failed to get modified time for: " + p_file); + return 0; } BitField FileAccessWindows::_get_unix_permissions(const String &p_file) { diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 48b9e01fd83..7d9af1df044 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -35,6 +35,7 @@ #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_spin_slider.h" +#include "editor/plugins/animation_player_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/gui/view_panner.h" #include "scene/resources/text_line.h" @@ -868,6 +869,11 @@ void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::Hand undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_out_handle(track_key_pair.first, track_key_pair.second)); undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, p_mode, p_auto ? Animation::HANDLE_SET_MODE_AUTO : Animation::HANDLE_SET_MODE_RESET); } + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); } @@ -1083,7 +1089,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref &p_event) { if (I.key == REMOVE_ICON) { if (!read_only) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action("Remove Bezier Track"); + undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr()); undo_redo->add_do_method(this, "_update_locked_tracks_after", track); undo_redo->add_do_method(this, "_update_hidden_tracks_after", track); @@ -1559,6 +1565,11 @@ void AnimationBezierTrackEdit::gui_input(const Ref &p_event) { undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio); undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio); } + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); moving_handle = 0; queue_redraw(); @@ -1672,6 +1683,11 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) { undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); queue_redraw(); } @@ -1772,6 +1788,11 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_ i++; } + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->add_do_method(this, "queue_redraw"); undo_redo->add_undo_method(this, "queue_redraw"); undo_redo->commit_action(); @@ -1821,6 +1842,15 @@ void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) { undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos, i == 0); i++; } + + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } + undo_redo->add_do_method(this, "queue_redraw"); + undo_redo->add_undo_method(this, "queue_redraw"); + undo_redo->commit_action(); } } @@ -1899,9 +1929,15 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) { i++; } - undo_redo->commit_action(); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } + undo_redo->add_do_method(this, "queue_redraw"); + undo_redo->add_undo_method(this, "queue_redraw"); - queue_redraw(); + undo_redo->commit_action(); } } @@ -1916,6 +1952,11 @@ void AnimationBezierTrackEdit::delete_selection() { } undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); //selection.clear(); @@ -1925,6 +1966,15 @@ void AnimationBezierTrackEdit::delete_selection() { void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) { int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle); p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Animation Bezier Curve Change Call")); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } + undo_redo->commit_action(); } void AnimationBezierTrackEdit::_bind_methods() { diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index c59c9379055..f14b81c1ea3 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -266,6 +266,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); setting = false; @@ -282,6 +287,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); setting = false; @@ -298,6 +308,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); setting = false; @@ -318,6 +333,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle); undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle); undo_redo->add_undo_method(this, "_update_obj", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); setting = false; @@ -7032,7 +7052,10 @@ void AnimationTrackEditor::_update_snap_unit() { if (timeline->is_using_fps()) { snap_unit = 1.0 / step->get_value(); } else { - snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor. + double integer; + double fraction = Math::modf(step->get_value(), &integer); + fraction = 1.0 / Math::round(1.0 / fraction); + snap_unit = integer + fraction; } } diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index d24b1edd709..8664c167b58 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -994,6 +994,9 @@ Ref CodeTextEditor::_get_completion_icon(const ScriptLanguage::CodeCo tex = get_editor_theme_icon(p_option.display); } else { tex = EditorNode::get_singleton()->get_class_icon(p_option.display); + if (!tex.is_valid()) { + tex = get_editor_theme_icon(SNAME("Object")); + } } } break; case ScriptLanguage::CODE_COMPLETION_KIND_ENUM: diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index c83e677b37f..8eaeedd92fc 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -578,32 +578,34 @@ void DependencyRemoveDialog::ok_pressed() { } } + bool project_settings_modified = false; for (int i = 0; i < files_to_delete.size(); ++i) { // If the file we are deleting for e.g. the main scene, default environment, // or audio bus layout, we must clear its definition in Project Settings. if (files_to_delete[i] == String(GLOBAL_GET("application/config/icon"))) { ProjectSettings::get_singleton()->set("application/config/icon", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("application/run/main_scene"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("application/run/main_scene"))) { ProjectSettings::get_singleton()->set("application/run/main_scene", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("application/boot_splash/image"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("application/boot_splash/image"))) { ProjectSettings::get_singleton()->set("application/boot_splash/image", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("rendering/environment/defaults/default_environment"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("rendering/environment/defaults/default_environment"))) { ProjectSettings::get_singleton()->set("rendering/environment/defaults/default_environment", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("display/mouse_cursor/custom_image"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("display/mouse_cursor/custom_image"))) { ProjectSettings::get_singleton()->set("display/mouse_cursor/custom_image", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom"))) { ProjectSettings::get_singleton()->set("gui/theme/custom", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom_font"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom_font"))) { ProjectSettings::get_singleton()->set("gui/theme/custom_font", ""); - } - if (files_to_delete[i] == String(GLOBAL_GET("audio/buses/default_bus_layout"))) { + project_settings_modified = true; + } else if (files_to_delete[i] == String(GLOBAL_GET("audio/buses/default_bus_layout"))) { ProjectSettings::get_singleton()->set("audio/buses/default_bus_layout", ""); + project_settings_modified = true; } String path = OS::get_singleton()->get_resource_dir() + files_to_delete[i].replace_first("res://", "/"); @@ -615,6 +617,9 @@ void DependencyRemoveDialog::ok_pressed() { emit_signal(SNAME("file_removed"), files_to_delete[i]); } } + if (project_settings_modified) { + ProjectSettings::get_singleton()->save(); + } if (dirs_to_delete.size() == 0) { // If we only deleted files we should only need to tell the file system about the files we touched. diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index b62351256e2..32b2133f234 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -457,7 +457,9 @@ void EditorAutoloadSettings::init_autoloads() { for (const AutoloadInfo &info : autoload_cache) { if (info.node && info.in_editor) { - callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED); + // It's important to add the node without deferring because code in plugins or tool scripts + // could use the autoload node when they are enabled. + get_tree()->get_root()->add_child(info.node); } } } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index feca12b4099..f75e438582c 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -386,6 +386,8 @@ void EditorFileSystem::_scan_filesystem() { // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem. if (first_scan) { sd = first_scan_root_dir; + // Will be updated on scan. + ResourceUID::get_singleton()->clear(); } else { Ref d = DirAccess::create(DirAccess::ACCESS_RESOURCES); sd = memnew(ScannedDirectory); @@ -1779,7 +1781,9 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info) { String icon_path; - if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) { + if (file_info->resource_script_class != StringName()) { + icon_path = EditorNode::get_editor_data().script_class_get_icon_path(file_info->resource_script_class); + } else if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) { const String &script_dep = file_info->deps[0]; // Assuming the first dependency is a script. const String &script_path = script_dep.contains("::") ? script_dep.get_slice("::", 2) : script_dep; if (!script_path.is_empty()) { @@ -3059,7 +3063,6 @@ EditorFileSystem::EditorFileSystem() { using_fat32_or_exfat = (da->get_filesystem_type() == "FAT32" || da->get_filesystem_type() == "exFAT"); scan_total = 0; - callable_mp(ResourceUID::get_singleton(), &ResourceUID::clear).call_deferred(); // Will be updated on scan. ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path); } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f24fa344ae5..4cd3924a9cf 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -174,6 +174,32 @@ static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"%s\" directory manually before attempting this operation again."; static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset."; +bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refresh) { + if (Thread::is_main_thread()) { + return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); + } else { + EditorNode::progress_task_step_bg(task, p_step); + return false; + } +} + +EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel) { + if (Thread::is_main_thread()) { + EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel); + } else { + EditorNode::progress_add_task_bg(p_task, p_label, p_amount); + } + task = p_task; +} + +EditorProgress::~EditorProgress() { + if (Thread::is_main_thread()) { + EditorNode::progress_end_task(task); + } else { + EditorNode::progress_end_task_bg(task); + } +} + void EditorNode::disambiguate_filenames(const Vector p_full_paths, Vector &r_filenames) { ERR_FAIL_COND_MSG(p_full_paths.size() != r_filenames.size(), vformat("disambiguate_filenames requires two string vectors of same length (%d != %d).", p_full_paths.size(), r_filenames.size())); @@ -670,7 +696,10 @@ void EditorNode::_notification(int p_what) { callable_mp(this, &EditorNode::_begin_first_scan).call_deferred(); - DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_update_theme).bind(false)); + last_dark_mode_state = DisplayServer::get_singleton()->is_dark_mode(); + last_system_accent_color = DisplayServer::get_singleton()->get_accent_color(); + last_system_base_color = DisplayServer::get_singleton()->get_base_color(); + DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_check_system_theme_changed)); /* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */ } break; @@ -1658,17 +1687,17 @@ void EditorNode::_find_node_types(Node *p_node, int &count_2d, int &count_3d) { } void EditorNode::_save_scene_with_preview(String p_file, int p_idx) { - EditorProgress save("save", TTR("Saving Scene"), 4); + save_scene_progress = memnew(EditorProgress("save", TTR("Saving Scene"), 4)); if (editor_data.get_edited_scene_root() != nullptr) { - save.step(TTR("Analyzing"), 0); + save_scene_progress->step(TTR("Analyzing"), 0); int c2d = 0; int c3d = 0; _find_node_types(editor_data.get_edited_scene_root(), c2d, c3d); - save.step(TTR("Creating Thumbnail"), 1); + save_scene_progress->step(TTR("Creating Thumbnail"), 1); // Current view? Ref img; @@ -1696,8 +1725,7 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) { if (img.is_valid() && img->get_width() > 0 && img->get_height() > 0) { img = img->duplicate(); - save.step(TTR("Creating Thumbnail"), 2); - save.step(TTR("Creating Thumbnail"), 3); + save_scene_progress->step(TTR("Creating Thumbnail"), 3); int preview_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size"); preview_size *= EDSCALE; @@ -1733,12 +1761,19 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) { } } - save.step(TTR("Saving Scene"), 4); + save_scene_progress->step(TTR("Saving Scene"), 4); _save_scene(p_file, p_idx); if (!singleton->cmdline_export_mode) { EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); } + + _close_save_scene_progress(); +} + +void EditorNode::_close_save_scene_progress() { + memdelete_notnull(save_scene_progress); + save_scene_progress = nullptr; } bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_node) { @@ -5132,6 +5167,7 @@ bool EditorNode::is_project_exporting() const { void EditorNode::show_accept(const String &p_text, const String &p_title) { current_menu_option = -1; if (accept) { + _close_save_scene_progress(); accept->set_ok_button_text(p_title); accept->set_text(p_text); EditorInterface::get_singleton()->popup_dialog_centered(accept); @@ -5141,6 +5177,7 @@ void EditorNode::show_accept(const String &p_text, const String &p_title) { void EditorNode::show_save_accept(const String &p_text, const String &p_title) { current_menu_option = -1; if (save_accept) { + _close_save_scene_progress(); save_accept->set_ok_button_text(p_title); save_accept->set_text(p_text); EditorInterface::get_singleton()->popup_dialog_centered(save_accept); @@ -5149,6 +5186,7 @@ void EditorNode::show_save_accept(const String &p_text, const String &p_title) { void EditorNode::show_warning(const String &p_text, const String &p_title) { if (warning) { + _close_save_scene_progress(); warning->set_text(p_text); warning->set_title(p_title); EditorInterface::get_singleton()->popup_dialog_centered(warning); @@ -5296,14 +5334,18 @@ void EditorNode::_load_open_scenes_from_config(Ref p_layout) { PackedStringArray scenes = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes"); for (int i = 0; i < scenes.size(); i++) { - load_scene(scenes[i]); + if (FileAccess::exists(scenes[i])) { + load_scene(scenes[i]); + } } if (p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "current_scene")) { String current_scene = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "current_scene"); - int current_scene_idx = scenes.find(current_scene); - if (current_scene_idx >= 0) { - _set_current_scene(current_scene_idx); + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { + if (editor_data.get_scene_path(i) == current_scene) { + _set_current_scene(i); + break; + } } } @@ -6033,7 +6075,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { base_packed_scene = current_packed_scene; } if (!local_scene_cache.find(path)) { - current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err); + current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); local_scene_cache[path] = current_packed_scene; } else { current_packed_scene = local_scene_cache[path]; diff --git a/editor/editor_node.h b/editor/editor_node.h index 0877d458a7c..222b1cf90c7 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -122,6 +122,14 @@ class SurfaceUpgradeTool; class SurfaceUpgradeDialog; class WindowWrapper; +struct EditorProgress { + String task; + bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true); + + EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false); + ~EditorProgress(); +}; + class EditorNode : public Node { GDCLASS(EditorNode, Node); @@ -470,6 +478,7 @@ private: String external_file; String open_navigate; String saving_scene; + EditorProgress *save_scene_progress = nullptr; DynamicFontImportSettingsDialog *fontdata_import_settings = nullptr; SceneImportSettingsDialog *scene_import_settings = nullptr; @@ -624,6 +633,7 @@ private: void _find_node_types(Node *p_node, int &count_2d, int &count_3d); void _save_scene_with_preview(String p_file, int p_idx = -1); + void _close_save_scene_progress(); bool _find_scene_in_use(Node *p_node, const String &p_path) const; @@ -952,33 +962,6 @@ public: bool ensure_main_scene(bool p_from_native); }; -struct EditorProgress { - String task; - bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true) { - if (Thread::is_main_thread()) { - return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); - } else { - EditorNode::progress_task_step_bg(task, p_step); - return false; - } - } - EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false) { - if (Thread::is_main_thread()) { - EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel); - } else { - EditorNode::progress_add_task_bg(p_task, p_label, p_amount); - } - task = p_task; - } - ~EditorProgress() { - if (Thread::is_main_thread()) { - EditorNode::progress_end_task(task); - } else { - EditorNode::progress_end_task_bg(task); - } - } -}; - class EditorPluginList : public Object { private: Vector plugins_list; diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index fdb4ec170b8..c90222a09f3 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2778,7 +2778,11 @@ void EditorPropertyNodePath::_update_menu() { void EditorPropertyNodePath::_menu_option(int p_idx) { switch (p_idx) { case ACTION_CLEAR: { - emit_changed(get_edited_property(), NodePath()); + if (editing_node) { + emit_changed(get_edited_property(), Variant()); + } else { + emit_changed(get_edited_property(), NodePath()); + } update_property(); } break; diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 4cd44e30200..06efb5ab64d 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -175,6 +175,13 @@ void EditorResourcePicker::_file_quick_selected() { _file_selected(quick_open->get_selected()); } +void EditorResourcePicker::_resource_saved(Object *p_resource) { + if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) { + emit_signal(SNAME("resource_changed"), edited_resource); + _update_resource(); + } +} + void EditorResourcePicker::_update_menu() { _update_menu_items(); @@ -408,6 +415,10 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { if (edited_resource.is_null()) { return; } + Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved); + if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) { + EditorNode::get_singleton()->connect("resource_saved", resource_saved); + } EditorNode::get_singleton()->save_resource_as(edited_resource); } break; @@ -833,6 +844,13 @@ void EditorResourcePicker::_notification(int p_what) { assign_button->queue_redraw(); } } break; + + case NOTIFICATION_EXIT_TREE: { + Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved); + if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) { + EditorNode::get_singleton()->disconnect("resource_saved", resource_saved); + } + } break; } } diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index 28229e6b378..05e392da2c7 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -91,6 +91,8 @@ class EditorResourcePicker : public HBoxContainer { void _file_quick_selected(); void _file_selected(const String &p_path); + void _resource_saved(Object *p_resource); + void _update_menu(); void _update_menu_items(); void _edit_menu_cbk(int p_which); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index d4bd97a3938..535ea553e28 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -685,7 +685,15 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s } Vector FileSystemDock::get_selected_paths() const { - return _tree_get_selected(false); + if (display_mode == DISPLAY_MODE_TREE_ONLY) { + return _tree_get_selected(false); + } else { + Vector selected = _file_list_get_selected(); + if (selected.is_empty()) { + selected.push_back(get_current_directory()); + } + return selected; + } } String FileSystemDock::get_current_path() const { @@ -953,7 +961,8 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_max_columns(1); files->set_max_text_lines(1); files->set_fixed_column_width(0); - files->set_fixed_icon_size(Size2()); + const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); + files->set_fixed_icon_size(Size2(icon_size, icon_size)); } Ref folder_icon = (use_thumbnails) ? folder_thumbnail : get_theme_icon(SNAME("folder"), SNAME("FileDialog")); @@ -2049,6 +2058,15 @@ Vector FileSystemDock::_tree_get_selected(bool remove_self_inclusion, bo return selected_strings; } +Vector FileSystemDock::_file_list_get_selected() const { + Vector selected; + + for (int idx : files->get_selected_items()) { + selected.push_back(files->get_item_metadata(idx)); + } + return selected; +} + Vector FileSystemDock::_remove_self_included_paths(Vector selected_strings) { // Remove paths or files that are included into another. if (selected_strings.size() > 1) { @@ -3392,6 +3410,11 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton current_path = current_path_line_edit->get_text(); + // Favorites isn't a directory so don't show menu. + if (current_path == "Favorites") { + return; + } + file_list_popup->clear(); file_list_popup->reset_size(); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 959ace8eba9..2f54cb91db0 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -359,6 +359,7 @@ private: void _update_display_mode(bool p_force = false); Vector _tree_get_selected(bool remove_self_inclusion = true, bool p_include_unselected_cursor = false) const; + Vector _file_list_get_selected() const; bool _is_file_type_disabled_by_feature_profile(const StringName &p_class); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index afc6d58d631..29f11174aff 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -115,6 +115,8 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector &p_file file_name = ProjectSettings::get_singleton()->localize_path(file_name); } } + selected_options = p_selected_options; + String f = files[0]; if (mode == FILE_MODE_OPEN_FILES) { emit_signal(SNAME("files_selected"), files); @@ -146,7 +148,6 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector &p_file } file->set_text(f); dir->set_text(f.get_base_dir()); - selected_options = p_selected_options; filter->select(p_filter); } diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 4e8d6d63bf1..2ff20e127bf 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -614,13 +614,13 @@ void EditorSpinSlider::_value_focus_exited() { // -> TAB was pressed // -> modal_close was not called // -> need to close/hide manually - if (value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) { + if (!is_visible_in_tree() || value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) { + // Hidden or something else took focus. if (value_input_popup) { value_input_popup->hide(); } - //tab was pressed } else { - //enter, click, esc + // Enter or Esc was pressed. grab_focus(); } @@ -664,6 +664,10 @@ bool EditorSpinSlider::is_grabbing() const { } void EditorSpinSlider::_focus_entered() { + if (is_read_only()) { + return; + } + _ensure_input_popup(); value_input->set_text(get_text_value()); value_input_popup->set_size(get_size()); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index c6cc0e97dde..4a22507e553 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -490,10 +490,14 @@ void SceneTreeEditor::_update_node_tooltip(Node *p_node, TreeItem *p_item) { String tooltip = p_node->get_name(); if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { - p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) { + p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + } tooltip += String("\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path()); } else if (p_node != get_scene_node() && !p_node->get_scene_file_path().is_empty() && can_open_instance) { - p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) { + p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + } tooltip += String("\n" + TTR("Instance:") + " " + p_node->get_scene_file_path()); } @@ -1000,6 +1004,7 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) { TreeItem *item = p_node ? _find(tree->get_root(), p_node->get_path()) : nullptr; if (item) { + selected = p_node; if (auto_expand_selected) { // Make visible when it's collapsed. TreeItem *node = item->get_parent(); @@ -1009,8 +1014,24 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) { } item->select(0); item->set_as_cursor(0); - selected = p_node; tree->ensure_cursor_is_visible(); + } else { + // Ensure the node is selected and visible for the user if the node + // is not collapsed. + bool collapsed = false; + TreeItem *node = item; + while (node && node != tree->get_root()) { + if (node->is_collapsed()) { + collapsed = true; + break; + } + node = node->get_parent(); + } + if (!collapsed) { + item->select(0); + item->set_as_cursor(0); + tree->ensure_cursor_is_visible(); + } } } else { if (!p_node) { diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp index f1b6d3a6ac7..f01381904da 100644 --- a/editor/import/resource_importer_imagefont.cpp +++ b/editor/import/resource_importer_imagefont.cpp @@ -293,18 +293,20 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin } String from_tokens; for (int i = 0; i < kp_tokens[0].length(); i++) { - if (i <= kp_tokens[0].length() - 6 && kp_tokens[0][i] == '\\' && kp_tokens[0][i + 1] == 'u') { + if (i <= kp_tokens[0].length() - 6 && kp_tokens[0][i] == '\\' && kp_tokens[0][i + 1] == 'u' && is_hex_digit(kp_tokens[0][i + 2]) && is_hex_digit(kp_tokens[0][i + 3]) && is_hex_digit(kp_tokens[0][i + 4]) && is_hex_digit(kp_tokens[0][i + 5])) { char32_t charcode = kp_tokens[0].substr(i + 2, 4).hex_to_int(); from_tokens += charcode; + i += 5; } else { from_tokens += kp_tokens[0][i]; } } String to_tokens; for (int i = 0; i < kp_tokens[1].length(); i++) { - if (i <= kp_tokens[1].length() - 6 && kp_tokens[1][i] == '\\' && kp_tokens[1][i + 1] == 'u') { + if (i <= kp_tokens[1].length() - 6 && kp_tokens[1][i] == '\\' && kp_tokens[1][i + 1] == 'u' && is_hex_digit(kp_tokens[1][i + 2]) && is_hex_digit(kp_tokens[1][i + 3]) && is_hex_digit(kp_tokens[1][i + 4]) && is_hex_digit(kp_tokens[1][i + 5])) { char32_t charcode = kp_tokens[1].substr(i + 2, 4).hex_to_int(); to_tokens += charcode; + i += 5; } else { to_tokens += kp_tokens[1][i]; } diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index 6d3d474cee4..9f1724bbd24 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -428,10 +428,10 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s loop_end = p_options["edit/loop_end"]; // Wrap around to max frames, so `-1` can be used to select the end, etc. if (loop_begin < 0) { - loop_begin = CLAMP(loop_begin + frames + 1, 0, frames); + loop_begin = CLAMP(loop_begin + frames, 0, frames - 1); } if (loop_end < 0) { - loop_end = CLAMP(loop_end + frames + 1, 0, frames); + loop_end = CLAMP(loop_end + frames, 0, frames - 1); } } @@ -517,16 +517,19 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s Vector dst_data; if (compression == 2) { dst_format = AudioStreamWAV::FORMAT_QOA; - qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; + qoa_desc desc = {}; uint32_t qoa_len = 0; desc.samplerate = rate; desc.samples = frames; desc.channels = format_channels; - void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len); - dst_data.resize(qoa_len); - memcpy(dst_data.ptrw(), encoded, qoa_len); + void *encoded = qoa_encode((short *)pcm_data.ptr(), &desc, &qoa_len); + if (encoded) { + dst_data.resize(qoa_len); + memcpy(dst_data.ptrw(), encoded, qoa_len); + QOA_FREE(encoded); + } } else { dst_data = pcm_data; } diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index f73c494b250..77ae70781dc 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -751,7 +751,9 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) { } void AbstractPolygon2DEditorPlugin::edit(Object *p_object) { - polygon_editor->edit(Object::cast_to(p_object)); + Node *polygon_node = Object::cast_to(p_object); + polygon_editor->edit(polygon_node); + make_visible(polygon_node != nullptr); } bool AbstractPolygon2DEditorPlugin::handles(Object *p_object) const { diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp index b07db993ba9..38f8b16b341 100644 --- a/editor/plugins/animation_library_editor.cpp +++ b/editor/plugins/animation_library_editor.cpp @@ -561,7 +561,9 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int return; } - anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here. + if (!anim->get_path().is_resource_file()) { + anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here. + } String base_name; if (anim->get_name() != "") { diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 660e4647a19..0161b367096 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -339,7 +339,17 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { track_editor->set_animation(anim, animation_is_readonly); Node *root = player->get_node_or_null(player->get_root_node()); - if (root) { + + // Player shouldn't access parent if it's the scene root. + if (!root || (player == get_tree()->get_edited_scene_root() && player->get_root_node() == SceneStringName(path_pp))) { + NodePath cached_root_path = player->get_path_to(get_cached_root_node()); + if (player->get_node_or_null(cached_root_path) != nullptr) { + player->set_root_node(cached_root_path); + } else { + player->set_root_node(SceneStringName(path_pp)); // No other choice, preventing crash. + } + } else { + cached_root_node_id = root->get_instance_id(); // Caching as `track_editor` can lose track of player's root node. track_editor->set_root(root); } } @@ -1824,6 +1834,10 @@ AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const { return original_node; } +Node *AnimationPlayerEditor::get_cached_root_node() const { + return Object::cast_to(ObjectDB::get_instance(cached_root_node_id)); +} + bool AnimationPlayerEditor::_validate_tracks(const Ref p_anim) { bool is_valid = true; if (!p_anim.is_valid()) { diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 4a3b1f37ab7..544b6d6747a 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -52,6 +52,7 @@ class AnimationPlayerEditor : public VBoxContainer { AnimationPlayerEditorPlugin *plugin = nullptr; AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree. AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy. + ObjectID cached_root_node_id; bool is_dummy = false; enum { @@ -251,6 +252,7 @@ public: AnimationMixer *get_editing_node() const; AnimationPlayer *get_player() const; AnimationMixer *fetch_mixer_for_library() const; + Node *get_cached_root_node() const; static AnimationPlayerEditor *get_singleton() { return singleton; } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 1afe2ddda71..8c91f381825 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -3999,6 +3999,8 @@ void CanvasItemEditor::_project_settings_changed() { void CanvasItemEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { + _update_lock_and_group_button(); + EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true)); EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false)); ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed)); @@ -4108,7 +4110,8 @@ void CanvasItemEditor::_notification(int p_what) { } } break; - case NOTIFICATION_APPLICATION_FOCUS_OUT: { + case NOTIFICATION_APPLICATION_FOCUS_OUT: + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { if (drag_type != DRAG_NONE) { _reset_drag(); viewport->queue_redraw(); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index de56767929a..9c07b664341 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -81,6 +81,8 @@ void EditorNode3DGizmo::redraw() { gizmo_plugin->redraw(this); } + _update_bvh(); + if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) { Node3DEditor::get_singleton()->update_transform_gizmo(); } @@ -244,6 +246,32 @@ void EditorNode3DGizmo::add_mesh(const Ref &p_mesh, const Ref &p instances.push_back(ins); } +void EditorNode3DGizmo::_update_bvh() { + ERR_FAIL_NULL(spatial_node); + + Transform3D transform = spatial_node->get_global_transform(); + + float effective_icon_size = selectable_icon_size > 0.0f ? selectable_icon_size : 0.0f; + Vector3 icon_size_vector3 = Vector3(effective_icon_size, effective_icon_size, effective_icon_size); + AABB aabb(spatial_node->get_position() - icon_size_vector3 * 100.0f, icon_size_vector3 * 200.0f); + + for (const Vector3 &segment_end : collision_segments) { + aabb.expand_to(transform.xform(segment_end)); + } + + if (collision_mesh.is_valid()) { + for (const Face3 &face : collision_mesh->get_faces()) { + aabb.expand_to(transform.xform(face.vertex[0])); + aabb.expand_to(transform.xform(face.vertex[1])); + aabb.expand_to(transform.xform(face.vertex[2])); + } + } + + Node3DEditor::get_singleton()->update_gizmo_bvh_node( + bvh_node_id, + aabb); +} + void EditorNode3DGizmo::add_lines(const Vector &p_lines, const Ref &p_material, bool p_billboard, const Color &p_modulate) { add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate); } @@ -403,12 +431,13 @@ void EditorNode3DGizmo::add_handles(const Vector &p_handles, const Ref< colors.resize(p_handles.size()); Color *w = colors.ptrw(); for (int i = 0; i < p_handles.size(); i++) { + int id = p_ids.is_empty() ? i : p_ids[i]; + Color col(1, 1, 1, 1); - if (is_handle_highlighted(i, p_secondary)) { + if (is_handle_highlighted(id, p_secondary)) { col = Color(0, 0, 1, 0.9); } - int id = p_ids.is_empty() ? i : p_ids[i]; if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { col.a = 0.8; } @@ -765,6 +794,10 @@ void EditorNode3DGizmo::create() { instances.write[i].create_instance(spatial_node, hidden); } + bvh_node_id = Node3DEditor::get_singleton()->insert_gizmo_bvh_node( + spatial_node, + AABB(spatial_node->get_position(), Vector3(0, 0, 0))); + transform(); } @@ -774,6 +807,8 @@ void EditorNode3DGizmo::transform() { for (int i = 0; i < instances.size(); i++) { RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform); } + + _update_bvh(); } void EditorNode3DGizmo::free() { @@ -790,6 +825,9 @@ void EditorNode3DGizmo::free() { clear(); + Node3DEditor::get_singleton()->remove_gizmo_bvh_node(bvh_node_id); + bvh_node_id = DynamicBVH::ID(); + valid = false; } diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index d7c368d5d01..c4b275032ab 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -31,6 +31,7 @@ #ifndef NODE_3D_EDITOR_GIZMOS_H #define NODE_3D_EDITOR_GIZMOS_H +#include "core/math/dynamic_bvh.h" #include "core/templates/hash_map.h" #include "core/templates/local_vector.h" #include "scene/3d/camera_3d.h" @@ -72,8 +73,12 @@ class EditorNode3DGizmo : public Node3DGizmo { Vector instances; Node3D *spatial_node = nullptr; + DynamicBVH::ID bvh_node_id; + void _set_node_3d(Node *p_node) { set_node_3d(Object::cast_to(p_node)); } + void _update_bvh(); + protected: static void _bind_methods(); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 59a4ac8075f..b787563d64b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -770,7 +770,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { } } - if (p_allow_locked || !_is_node_locked(selected)) { + if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) { if (clicked_wants_append) { if (editor_selection->is_selected(selected)) { editor_selection->remove_node(selected); @@ -800,7 +800,6 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const { RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray); } - Vector instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); HashSet> found_gizmos; Node *edited_scene = get_tree()->get_edited_scene_root(); @@ -808,9 +807,9 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const { Node *item = nullptr; float closest_dist = 1e20; - for (int i = 0; i < instances.size(); i++) { - Node3D *spat = Object::cast_to(ObjectDB::get_instance(instances[i])); + Vector nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far()); + for (Node3D *spat : nodes_with_gizmos) { if (!spat) { continue; } @@ -863,12 +862,11 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayRe Vector3 ray = get_ray(p_pos); Vector3 pos = get_ray_pos(p_pos); - Vector instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); + Vector nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far()); + HashSet found_nodes; - for (int i = 0; i < instances.size(); i++) { - Node3D *spat = Object::cast_to(ObjectDB::get_instance(instances[i])); - + for (Node3D *spat : nodes_with_gizmos) { if (!spat) { continue; } @@ -1046,7 +1044,7 @@ void Node3DEditorViewport::_select_region() { _clear_selected(); } - Vector instances = RenderingServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world_3d()->get_scenario()); + Vector nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_frustum_query(frustum); HashSet found_nodes; Vector selected; @@ -1055,8 +1053,7 @@ void Node3DEditorViewport::_select_region() { return; } - for (int i = 0; i < instances.size(); i++) { - Node3D *sp = Object::cast_to(ObjectDB::get_instance(instances[i])); + for (Node3D *sp : nodes_with_gizmos) { if (!sp || _is_node_locked(sp)) { continue; } @@ -1064,21 +1061,23 @@ void Node3DEditorViewport::_select_region() { if (found_nodes.has(sp)) { continue; } - found_nodes.insert(sp); Node *node = Object::cast_to(sp); - if (node != edited_scene) { - node = edited_scene->get_deepest_editable_node(node); - } - // Prevent selection of nodes not owned by the edited scene. - while (node && node != edited_scene->get_parent()) { - Node *node_owner = node->get_owner(); - if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) { - break; + // Selection requires that the node is the edited scene or its descendant, and has an owner. + if (node != edited_scene) { + if (!node->get_owner() || !edited_scene->is_ancestor_of(node)) { + continue; + } + node = edited_scene->get_deepest_editable_node(node); + while (node != edited_scene) { + Node *node_owner = node->get_owner(); + if (node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) { + break; + } + node = node->get_parent(); } - node = node->get_parent(); } // Replace the node by the group if grouped @@ -1952,7 +1951,7 @@ void Node3DEditorViewport::_sinput(const Ref &p_event) { } } - if (_edit.mode != TRANSFORM_NONE) { + if (!_edit.instant && _edit.mode != TRANSFORM_NONE) { Node3D *selected = spatial_editor->get_single_selected_node(); Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data(selected) : nullptr; @@ -2252,6 +2251,11 @@ void Node3DEditorViewport::_sinput(const Ref &p_event) { } if (_edit.mode == TRANSFORM_NONE) { + if (_edit.gizmo.is_null() && is_freelook_active() && k->get_keycode() == Key::ESCAPE) { + set_freelook_active(false); + return; + } + if (_edit.gizmo.is_valid() && (k->get_keycode() == Key::ESCAPE || k->get_keycode() == Key::BACKSPACE)) { // Restore. _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true); @@ -2390,15 +2394,30 @@ void Node3DEditorViewport::_sinput(const Ref &p_event) { if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) { cancel_transform(); } - if (!is_freelook_active()) { - if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) { - begin_transform(TRANSFORM_TRANSLATE, true); + if (!is_freelook_active() && !k->is_echo()) { + if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_TRANSLATE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_TRANSLATE, true); + } } - if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) { - begin_transform(TRANSFORM_ROTATE, true); + if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_ROTATE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_ROTATE, true); + } } - if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) { - begin_transform(TRANSFORM_SCALE, true); + if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_SCALE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_SCALE, true); + } } } @@ -3030,8 +3049,11 @@ void Node3DEditorViewport::_notification(int p_what) { update_preview_node = false; } break; + case NOTIFICATION_APPLICATION_FOCUS_OUT: case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { set_freelook_active(false); + cursor.region_select = false; + surface->queue_redraw(); } break; case NOTIFICATION_ENTER_TREE: { @@ -3107,6 +3129,7 @@ void Node3DEditorViewport::_notification(int p_what) { case NOTIFICATION_DRAG_END: { // Clear preview material when dropped outside applicable object. if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) { + _reset_preview_material(); _remove_preview_material(); } else { _remove_preview_node(); @@ -4001,6 +4024,14 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { _menu_option(VIEW_GIZMOS); } } + if (p_state.has("grid")) { + bool grid = p_state["grid"]; + + int idx = view_menu->get_popup()->get_item_index(VIEW_GRID); + if (view_menu->get_popup()->is_item_checked(idx) != grid) { + _menu_option(VIEW_GRID); + } + } if (p_state.has("information")) { bool information = p_state["information"]; @@ -4079,6 +4110,7 @@ Dictionary Node3DEditorViewport::get_state() const { d["listener"] = viewport->is_audio_listener_3d(); d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER)); d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS)); + d["grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GRID)); d["information"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION)); d["frame_time"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME)); d["half_res"] = subviewport_container->get_stretch_shrink() > 1; @@ -5001,14 +5033,24 @@ void Node3DEditorViewport::update_transform(bool p_shift) { } break; case TRANSFORM_ROTATE: { - Plane plane = Plane(_get_camera_normal(), _edit.center); + Plane plane; + if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { + Vector3 cam_to_obj = _edit.center - _get_camera_position(); + if (!cam_to_obj.is_zero_approx()) { + plane = Plane(cam_to_obj.normalized(), _edit.center); + } else { + plane = Plane(_get_camera_normal(), _edit.center); + } + } else { + plane = Plane(_get_camera_normal(), _edit.center); + } Vector3 local_axis; Vector3 global_axis; switch (_edit.plane) { case TRANSFORM_VIEW: // local_axis unused - global_axis = _get_camera_normal(); + global_axis = plane.normal; break; case TRANSFORM_X_AXIS: local_axis = Vector3(1, 0, 0); @@ -5039,7 +5081,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) { break; } - static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(87.0f)); + static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(85.0f)); bool axis_is_orthogonal = ABS(plane.normal.dot(global_axis)) < orthogonal_threshold; double angle = 0.0f; @@ -7746,6 +7788,9 @@ void Node3DEditor::_sun_environ_settings_pressed() { sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0)); sun_environ_popup->reset_size(); sun_environ_popup->popup(); + // Grabbing the focus is required for Shift modifier checking to be functional + // (when the Add sun/environment buttons are pressed). + sun_environ_popup->grab_focus(); } void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { @@ -9208,6 +9253,49 @@ void Node3DEditor::remove_gizmo_plugin(Ref p_plugin) { _update_gizmos_menu(); } +DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) { + return gizmo_bvh.insert(p_aabb, p_node); +} + +void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) { + gizmo_bvh.update(p_id, p_aabb); + gizmo_bvh.optimize_incremental(1); +} + +void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) { + gizmo_bvh.remove(p_id); +} + +Vector Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) { + struct Result { + Vector nodes; + bool operator()(void *p_data) { + nodes.append((Node3D *)p_data); + return false; + } + } result; + + gizmo_bvh.ray_query(p_ray_start, p_ray_end, result); + + return result.nodes; +} + +Vector Node3DEditor::gizmo_bvh_frustum_query(const Vector &p_frustum) { + Vector points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size()); + + struct Result { + Vector nodes; + bool operator()(void *p_data) { + nodes.append((Node3D *)p_data); + return false; + } + } result; + + gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result); + + return result.nodes; +} + Node3DEditorPlugin::Node3DEditorPlugin() { spatial_editor = memnew(Node3DEditor); spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 5bd14748c08..9e7d46c5e85 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -31,6 +31,7 @@ #ifndef NODE_3D_EDITOR_PLUGIN_H #define NODE_3D_EDITOR_PLUGIN_H +#include "core/math/dynamic_bvh.h" #include "editor/plugins/editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "editor/themes/editor_scale.h" @@ -629,6 +630,8 @@ private: int current_hover_gizmo_handle; bool current_hover_gizmo_handle_secondary; + DynamicBVH gizmo_bvh; + real_t snap_translate_value; real_t snap_rotate_value; real_t snap_scale_value; @@ -933,6 +936,12 @@ public: void add_gizmo_plugin(Ref p_plugin); void remove_gizmo_plugin(Ref p_plugin); + DynamicBVH::ID insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb); + void update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb); + void remove_gizmo_bvh_node(DynamicBVH::ID p_id); + Vector gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end); + Vector gizmo_bvh_frustum_query(const Vector &p_frustum); + void edit(Node3D *p_spatial); void clear(); diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index e9ddaeb3fe6..b38965753e8 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -83,9 +83,8 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, int idx_last = atr_owners.size() - 1; if (idx_last > 0 && !parent_path.begins_with(atr_owners[idx_last].first)) { - // Switch to the previous auto translation owner this was nested in, if that was the case. + // Exit from the current owner nesting into the previous one. atr_owners.remove_at(idx_last); - idx_last -= 1; } if (property == "auto_translate_mode") { @@ -106,7 +105,7 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, // If `auto_translate_mode` wasn't found, that means it is set to its default value (`AUTO_TRANSLATE_MODE_INHERIT`). if (!auto_translate_mode_found) { int idx_last = atr_owners.size() - 1; - if (idx_last > 0 && atr_owners[idx_last].first == parent_path) { + if (idx_last > 0 && parent_path.begins_with(atr_owners[idx_last].first)) { auto_translating = atr_owners[idx_last].second; } else { atr_owners.push_back(Pair(state->get_node_path(i), true)); diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index 5b23cf44d03..86dbfbba952 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -197,12 +197,7 @@ bool Path2DEditor::forward_gui_input(const Ref &p_event) { const Vector2 new_point = xform.affine_inverse().xform(gpoint2); curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1); - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Split Curve")); - undo_redo->add_do_method(curve.ptr(), "add_point", new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1); - undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1); - - action = ACTION_MOVING_NEW_POINT; + action = ACTION_MOVING_NEW_POINT_FROM_SPLIT; action_point = insertion_point + 1; moving_from = curve->get_point_position(action_point); moving_screen_from = gpoint2; @@ -245,6 +240,16 @@ bool Path2DEditor::forward_gui_input(const Ref &p_event) { undo_redo->commit_action(false); } break; + case ACTION_MOVING_NEW_POINT_FROM_SPLIT: { + undo_redo->create_action(TTR("Split Curve")); + undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point); + undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint); + undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(false); + } break; + case ACTION_MOVING_IN: { if (original_mouse_pos != gpoint) { undo_redo->create_action(TTR("Move In-Control in Curve")); @@ -350,7 +355,8 @@ bool Path2DEditor::forward_gui_input(const Ref &p_event) { break; case ACTION_MOVING_POINT: - case ACTION_MOVING_NEW_POINT: { + case ACTION_MOVING_NEW_POINT: + case ACTION_MOVING_NEW_POINT_FROM_SPLIT: { curve->set_point_position(action_point, cpoint); } break; @@ -573,6 +579,10 @@ void Path2DEditor::_cancel_current_action() { curve->remove_point(curve->get_point_count() - 1); } break; + case ACTION_MOVING_NEW_POINT_FROM_SPLIT: { + curve->remove_point(action_point); + } break; + case ACTION_MOVING_IN: { curve->set_point_in(action_point, moving_from); curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length)); diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index a2857fddb79..f45b75a968c 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -81,6 +81,7 @@ class Path2DEditor : public HBoxContainer { ACTION_NONE, ACTION_MOVING_POINT, ACTION_MOVING_NEW_POINT, + ACTION_MOVING_NEW_POINT_FROM_SPLIT, ACTION_MOVING_IN, ACTION_MOVING_OUT, }; diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index e442c37edd5..cd644e08ca6 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -181,7 +181,7 @@ void Polygon2DEditor::_sync_bones() { } if (weights.size() == 0) { //create them - weights.resize(node->get_polygon().size()); + weights.resize(wc); float *w = weights.ptrw(); for (int j = 0; j < wc; j++) { w[j] = 0.0; @@ -850,8 +850,8 @@ void Polygon2DEditor::_uv_input(const Ref &p_input) { if (mm.is_valid()) { if (uv_drag) { Vector2 uv_drag_to = mm->get_position(); - uv_drag_to = snap_point(uv_drag_to); // FIXME: Only works correctly with 'UV_MODE_EDIT_POINT', it's imprecise with the rest. - Vector2 drag = mtx.affine_inverse().xform(uv_drag_to) - mtx.affine_inverse().xform(uv_drag_from); + uv_drag_to = snap_point(uv_drag_to); + Vector2 drag = mtx.affine_inverse().basis_xform(uv_drag_to - uv_drag_from); switch (uv_move_current) { case UV_MODE_CREATE: { @@ -1166,12 +1166,8 @@ void Polygon2DEditor::_uv_draw() { poly_line_color.a *= 0.25; } Color polygon_line_color = Color(0.5, 0.5, 0.9); - Vector polygon_fill_color; - { - Color pf = polygon_line_color; - pf.a *= 0.5; - polygon_fill_color.push_back(pf); - } + Color polygon_fill_color = polygon_line_color; + polygon_fill_color.a *= 0.5; Color prev_color = Color(0.5, 0.5, 0.5); int uv_draw_max = uvs.size(); @@ -1216,7 +1212,7 @@ void Polygon2DEditor::_uv_draw() { uv_edit_draw->draw_line(mtx.xform(uvs[idx]), mtx.xform(uvs[idx_next]), polygon_line_color, Math::round(EDSCALE)); } if (points.size() >= 3) { - uv_edit_draw->draw_polygon(polypoints, polygon_fill_color); + uv_edit_draw->draw_colored_polygon(polypoints, polygon_fill_color); } } @@ -1309,8 +1305,8 @@ void Polygon2DEditor::_bind_methods() { Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const { if (use_snap) { - p_target.x = Math::snap_scalar(snap_offset.x * uv_draw_zoom - uv_draw_ofs.x, snap_step.x * uv_draw_zoom, p_target.x); - p_target.y = Math::snap_scalar(snap_offset.y * uv_draw_zoom - uv_draw_ofs.y, snap_step.y * uv_draw_zoom, p_target.y); + p_target.x = Math::snap_scalar((snap_offset.x - uv_draw_ofs.x) * uv_draw_zoom, snap_step.x * uv_draw_zoom, p_target.x); + p_target.y = Math::snap_scalar((snap_offset.y - uv_draw_ofs.y) * uv_draw_zoom, snap_step.y * uv_draw_zoom, p_target.y); } return p_target; @@ -1486,7 +1482,7 @@ Polygon2DEditor::Polygon2DEditor() { grid_settings = memnew(AcceptDialog); grid_settings->set_title(TTR("Configure Grid:")); - add_child(grid_settings); + uv_edit->add_child(grid_settings); VBoxContainer *grid_settings_vb = memnew(VBoxContainer); grid_settings->add_child(grid_settings_vb); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index c51eb44aee4..f7ced2b1796 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -2711,9 +2711,11 @@ void ScriptEditor::apply_scripts() const { } void ScriptEditor::reload_scripts(bool p_refresh_only) { - if (external_editor_active) { - return; - } + // Call deferred to make sure it runs on the main thread. + callable_mp(this, &ScriptEditor::_reload_scripts).call_deferred(p_refresh_only); +} + +void ScriptEditor::_reload_scripts(bool p_refresh_only) { for (int i = 0; i < tab_container->get_tab_count(); i++) { ScriptEditorBase *se = Object::cast_to(tab_container->get_tab_control(i)); if (!se) { diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 9db1aff76ac..ef88bc107b0 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -436,6 +436,7 @@ class ScriptEditor : public PanelContainer { void _file_removed(const String &p_file); void _autosave_scripts(); void _update_autosave_timer(); + void _reload_scripts(bool p_refresh_only = false); void _update_members_overview_visibility(); void _update_members_overview(); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 3942baed8ab..b806d1e0429 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -372,7 +372,7 @@ void TileAtlasView::_draw_base_tiles_shape_grid() { for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_data(tile_id, 0)->get_texture_origin(); - if (tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, -tile_shape_size / 2) && tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, tile_shape_size / 2 - Vector2(1, 1))) { + if (tile_set_atlas_source->is_rect_in_tile_texture_region(tile_id, 0, Rect2(Vector2(-tile_shape_size) / 2, tile_shape_size))) { for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) { Color color = grid_color; if (frame > 0) { diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index f985bbc6290..03177b7d291 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -165,10 +165,14 @@ void GenericTilePolygonEditor::_base_control_draw() { base_control->draw_set_transform_matrix(xform); // Draw fill rect under texture region. - Rect2 texture_rect(-background_region.size / 2, background_region.size); + Rect2 texture_rect(Vector2(), background_region.size); if (tile_data) { texture_rect.position -= tile_data->get_texture_origin(); + if (tile_data->get_transpose()) { + texture_rect.size = Size2(texture_rect.size.y, texture_rect.size.x); + } } + texture_rect.position -= texture_rect.size / 2; // Half-size offset must be applied after transposing. base_control->draw_rect(texture_rect, Color(1, 1, 1, 0.3)); // Draw the background. @@ -180,18 +184,14 @@ void GenericTilePolygonEditor::_base_control_draw() { if (tile_data->get_flip_v()) { region_size.y = -region_size.y; } - base_control->draw_texture_rect_region(background_atlas_source->get_texture(), Rect2(-background_region.size / 2 - tile_data->get_texture_origin(), region_size), background_region, tile_data->get_modulate(), tile_data->get_transpose()); + // Destination rect position must account for transposing, size must not. + base_control->draw_texture_rect_region(background_atlas_source->get_texture(), Rect2(texture_rect.position, region_size), background_region, tile_data->get_modulate(), tile_data->get_transpose()); } // Compute and draw the grid area. Rect2 grid_area = Rect2(-base_tile_size / 2, base_tile_size); - if (tile_data) { - grid_area.expand_to(-background_region.get_size() / 2 - tile_data->get_texture_origin()); - grid_area.expand_to(background_region.get_size() / 2 - tile_data->get_texture_origin()); - } else { - grid_area.expand_to(-background_region.get_size() / 2); - grid_area.expand_to(background_region.get_size() / 2); - } + grid_area.expand_to(texture_rect.position); + grid_area.expand_to(texture_rect.get_end()); base_control->draw_rect(grid_area, Color(1, 1, 1, 0.3), false); // Draw grid. @@ -1385,10 +1385,8 @@ void TileDataTextureOriginEditor::draw_over_tile(CanvasItem *p_canvas_item, Tran TileSetSource *source = *(tile_set->get_source(p_cell.source_id)); TileSetAtlasSource *atlas_source = Object::cast_to(source); - if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, -tile_set_tile_size / 2) && atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, tile_set_tile_size / 2 - Vector2(1, 1))) { - Transform2D tile_xform; - tile_xform.set_scale(tile_set_tile_size); - tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, color); + if (atlas_source->is_rect_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, Rect2(Vector2(-tile_set_tile_size) / 2, tile_set_tile_size))) { + tile_set->draw_tile_shape(p_canvas_item, p_transform.scaled_local(tile_set_tile_size), color); } if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, Vector2())) { diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index b76e6734140..fbf88cc472a 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -1366,11 +1366,13 @@ void TileMapLayerEditorTilesPlugin::_stop_dragging() { Vector2i coords; HashMap cells_undo; for (int i = 0; i < selection_used_cells.size(); i++) { - coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern); - cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile); coords = tile_set->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); cells_undo[coords] = TileMapCell(edited_layer->get_cell_source_id(coords), edited_layer->get_cell_atlas_coords(coords), edited_layer->get_cell_alternative_tile(coords)); } + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern); + cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile); + } // Build the list of cells to do. HashMap cells_do; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 2da9d66d9a4..b2f09fff476 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -115,8 +115,8 @@ void VSGraphNode::_draw_port(int p_slot_index, Point2i p_pos, bool p_left, const icon_offset = -port_icon->get_size() * 0.5; // Draw "shadow"/outline in the connection rim color. - draw_texture_rect(port_icon, Rect2(p_pos + icon_offset - Size2(2, 2), port_icon->get_size() + Size2(4, 4)), false, p_rim_color); - draw_texture(port_icon, p_pos + icon_offset, p_color); + draw_texture_rect(port_icon, Rect2(p_pos + (icon_offset - Size2(2, 2)) * EDSCALE, (port_icon->get_size() + Size2(4, 4)) * EDSCALE), false, p_rim_color); + draw_texture_rect(port_icon, Rect2(p_pos + icon_offset * EDSCALE, port_icon->get_size() * EDSCALE), false, p_color); } void VSGraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) { @@ -154,7 +154,6 @@ VSRerouteNode::VSRerouteNode() { title_lbl->hide(); const Size2 size = Size2(32, 32) * EDSCALE; - print_line("VSRerouteNode size: " + size); Control *slot_area = memnew(Control); slot_area->set_custom_minimum_size(size); @@ -2888,8 +2887,8 @@ void VisualShaderEditor::_frame_title_popup_show(const Point2 &p_position, int p } frame_title_change_edit->set_text(node->get_title()); frame_title_change_popup->set_meta("id", p_node_id); - frame_title_change_popup->popup(); frame_title_change_popup->set_position(p_position); + frame_title_change_popup->popup(); // Select current text. frame_title_change_edit->grab_focus(); diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index b4aa00ee0a8..afbb223eb5e 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -162,7 +162,7 @@ void ProjectDialog::_validate_path() { } } - if (target_path.is_empty() || target_path.is_relative_path()) { + if (target_path.is_relative_path()) { _set_message(TTR("The path specified is invalid."), MESSAGE_ERROR, target_path_input_type); return; } @@ -352,7 +352,7 @@ void ProjectDialog::_install_path_changed() { void ProjectDialog::_browse_project_path() { String path = project_path->get_text(); - if (path.is_empty()) { + if (path.is_relative_path()) { path = EDITOR_GET("filesystem/directories/default_project_path"); } if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) { @@ -382,12 +382,16 @@ void ProjectDialog::_browse_project_path() { void ProjectDialog::_browse_install_path() { ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT."); + String path = install_path->get_text(); + if (path.is_relative_path() || !DirAccess::dir_exists_absolute(path)) { + path = EDITOR_GET("filesystem/directories/default_project_path"); + } if (create_dir->is_pressed()) { // Select parent directory of install path. - fdialog_install->set_current_dir(install_path->get_text().get_base_dir()); + fdialog_install->set_current_dir(path.get_base_dir()); } else { // Select install path. - fdialog_install->set_current_dir(install_path->get_text()); + fdialog_install->set_current_dir(path); } fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index 092a6a1a185..541ab01e626 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -760,7 +760,7 @@ void ProjectList::_create_project_item_control(int p_index) { hb->set_tags(item.tags, this); hb->set_unsupported_features(item.unsupported_features.duplicate()); hb->set_project_version(item.project_version); - hb->set_last_edited_info(Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true)); + hb->set_last_edited_info(!item.missing ? Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true) : TTR("Missing Date")); hb->set_is_favorite(item.favorite); hb->set_is_missing(item.missing); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 56924fc1fe7..5cd78e8a38e 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1181,7 +1181,16 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { case TOOL_OPEN_DOCUMENTATION: { List selection = editor_selection->get_selected_node_list(); for (const Node *node : selection) { - ScriptEditor::get_singleton()->goto_help("class_name:" + node->get_class()); + String class_name; + Ref