diff options
49 files changed, 843 insertions, 441 deletions
diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 37967cdb48..e7e350687b 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -30,7 +30,7 @@ #include "native_extension.h" #include "core/config/project_settings.h" -#include "core/io/config_file.h" +#include "core/io/dir_access.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" #include "core/os/os.h" @@ -39,6 +39,111 @@ String NativeExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); } +String NativeExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { + // First, check the explicit libraries. + if (p_config->has_section("libraries")) { + List<String> libraries; + p_config->get_section_keys("libraries", &libraries); + + // Iterate the libraries, finding the best matching tags. + String best_library_path; + Vector<String> best_library_tags; + for (const String &E : libraries) { + Vector<String> tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met && tags.size() > best_library_tags.size()) { + best_library_path = p_config->get_value("libraries", E); + best_library_tags = tags; + } + } + + if (!best_library_path.is_empty()) { + if (best_library_path.is_relative_path()) { + best_library_path = p_path.get_base_dir().path_join(best_library_path); + } + if (r_tags != nullptr) { + r_tags->append_array(best_library_tags); + } + return best_library_path; + } + } + + // Second, try to autodetect + String autodetect_library_prefix; + if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { + autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); + } + if (!autodetect_library_prefix.is_empty()) { + String autodetect_path = autodetect_library_prefix; + if (autodetect_path.is_relative_path()) { + autodetect_path = p_path.get_base_dir().path_join(autodetect_path); + } + + // Find the folder and file parts of the prefix. + String folder; + String file_prefix; + if (DirAccess::dir_exists_absolute(autodetect_path)) { + folder = autodetect_path; + } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { + folder = autodetect_path.get_base_dir(); + file_prefix = autodetect_path.get_file(); + } else { + ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + } + + // Open the folder. + Ref<DirAccess> dir = DirAccess::open(folder); + ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + + // Iterate the files and check the prefixes, finding the best matching file. + String best_file; + Vector<String> best_file_tags; + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { + // Check if the files matches all requested feature tags. + String tags_str = file_name.trim_prefix(file_prefix); + tags_str = tags_str.trim_suffix(tags_str.get_extension()); + + Vector<String> tags = tags_str.split(".", false); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + // If all tags are found in the feature list, and we found more tags than before, use this file. + if (all_tags_met && tags.size() > best_file_tags.size()) { + best_file_tags = tags; + best_file = file_name; + } + } + file_name = dir->_get_next(); + } + + if (!best_file.is_empty()) { + String library_path = folder.path_join(best_file); + if (r_tags != nullptr) { + r_tags->append_array(best_file_tags); + } + return library_path; + } + } + return String(); +} + class NativeExtensionMethodBind : public MethodBind { GDNativeExtensionClassMethodCall call_func; GDNativeExtensionClassMethodPtrCall ptrcall_func; @@ -415,28 +520,7 @@ Ref<Resource> NativeExtensionResourceLoader::load(const String &p_path, const St String entry_symbol = config->get_value("configuration", "entry_symbol"); - List<String> libraries; - - config->get_section_keys("libraries", &libraries); - - String library_path; - - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!OS::get_singleton()->has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - library_path = config->get_value("libraries", E); - break; - } - } + String library_path = NativeExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); }); if (library_path.is_empty()) { if (r_error) { diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index ca6200cd1e..77654cf0a8 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -31,7 +31,10 @@ #ifndef NATIVE_EXTENSION_H #define NATIVE_EXTENSION_H +#include <functional> + #include "core/extension/gdnative_interface.h" +#include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" @@ -65,6 +68,7 @@ protected: public: static String get_extension_list_config_file(); + static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); diff --git a/core/os/os.cpp b/core/os/os.cpp index bbb2a94fe7..055385579f 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -353,20 +353,26 @@ bool OS::has_feature(const String &p_feature) { if (p_feature == "debug") { return true; } +#endif // DEBUG_ENABLED + +#ifdef TOOLS_ENABLED + if (p_feature == "editor") { + return true; + } #else - if (p_feature == "release") { + if (p_feature == "template") { return true; } -#endif -#ifdef TOOLS_ENABLED - if (p_feature == "editor") { +#ifdef DEBUG_ENABLED + if (p_feature == "template_debug") { return true; } #else - if (p_feature == "standalone") { + if (p_feature == "template_release" || p_feature == "release") { return true; } -#endif +#endif // DEBUG_ENABLED +#endif // TOOLS_ENABLED if (sizeof(void *) == 8 && p_feature == "64") { return true; diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index 60e20cd97d..fe7756faaf 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -114,7 +114,7 @@ <member name="shadow_enabled" type="bool" setter="set_shadow" getter="has_shadow" default="false"> If [code]true[/code], the light will cast real-time shadows. This has a significant performance cost. Only enable shadow rendering when it makes a noticeable difference in the scene's appearance, and consider using [member distance_fade_enabled] to hide the light when far away from the [Camera3D]. </member> - <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="1.0"> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="2.0"> Offsets the lookup into the shadow map by the object's normal. This can be used to reduce self-shadowing artifacts without using [member shadow_bias]. In practice, this value should be tweaked along with [member shadow_bias] to reduce artifacts as much as possible. </member> <member name="shadow_opacity" type="float" setter="set_param" getter="get_param" default="1.0"> diff --git a/doc/classes/OmniLight3D.xml b/doc/classes/OmniLight3D.xml index 193ae8deeb..f71c81e713 100644 --- a/doc/classes/OmniLight3D.xml +++ b/doc/classes/OmniLight3D.xml @@ -20,7 +20,7 @@ <member name="omni_shadow_mode" type="int" setter="set_shadow_mode" getter="get_shadow_mode" enum="OmniLight3D.ShadowMode" default="1"> See [enum ShadowMode]. </member> - <member name="shadow_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="0.2" /> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="1.0" /> </members> <constants> <constant name="SHADOW_DUAL_PARABOLOID" value="0" enum="ShadowMode"> diff --git a/doc/classes/SpotLight3D.xml b/doc/classes/SpotLight3D.xml index 28e2ef0d95..59d36aefab 100644 --- a/doc/classes/SpotLight3D.xml +++ b/doc/classes/SpotLight3D.xml @@ -12,6 +12,7 @@ </tutorials> <members> <member name="shadow_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="0.03" /> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="1.0" /> <member name="spot_angle" type="float" setter="set_param" getter="get_param" default="45.0"> The spotlight's angle in degrees. [b]Note:[/b] [member spot_angle] is not affected by [member Node3D.scale] (the light's scale or its parent's scale). diff --git a/doc/classes/String.xml b/doc/classes/String.xml index c186952c74..7e76a134ff 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="String" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Built-in string class. + Built-in string Variant type. </brief_description> <description> - This is the built-in string class (and the one used by GDScript). It supports Unicode and provides all necessary means for string handling. Strings are reference-counted and use a copy-on-write approach, so passing them around is cheap in resources. + This is the built-in string Variant type (and the one used by GDScript). Strings may contain any number of Unicode characters, and expose methods useful for manipulating and generating strings. Strings are reference-counted and use a copy-on-write approach (every modification to a string returns a new [String]), so passing them around is cheap in resources. + Some string methods have corresponding variations. Variations suffixed with [code]n[/code] ([method countn], [method findn], [method replacen], etc.) are [b]case-insensitive[/b] (they make no distinction between uppercase and lowercase letters). Method variations prefixed with [code]r[/code] ([method rfind], [method rsplit], etc.) are reversed, and start from the end of the string, instead of the beginning. + [b]Note:[/b] In a boolean context, a string will evaluate to [code]false[/code] if it is empty ([code]""[/code]). Otherwise, a string will always evaluate to [code]true[/code]. </description> <tutorials> <link title="GDScript format strings">$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html</link> @@ -43,30 +45,32 @@ <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if the string begins with the given string. + Returns [code]true[/code] if the string begins with the given [param text]. See also [method ends_with]. </description> </method> <method name="bigrams" qualifiers="const"> <return type="PackedStringArray" /> <description> - Returns an array containing the bigrams (pairs of consecutive letters) of this string. + Returns an array containing the bigrams (pairs of consecutive characters) of this string. [codeblock] - print("Bigrams".bigrams()) # Prints ["Bi", "ig", "gr", "ra", "am", "ms"] + print("Get up!".bigrams()) # Prints ["Ge", "et", "t ", " u", "up", "p!"] [/codeblock] </description> </method> <method name="bin_to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing a binary number into an integer. Binary strings can either be prefixed with [code]0b[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + Converts the string representing a binary number into an [int]. The string may optionally be prefixed with [code]"0b"[/code], and an additional [code]-[/code] prefix for negative numbers. [codeblocks] [gdscript] - print("0b101".bin_to_int()) # Prints "5". - print("101".bin_to_int()) # Prints "5". + print("101".bin_to_int()) # Prints 5 + print("0b101".bin_to_int()) # Prints 5 + print("-0b10".bin_to_int()) # Prints -2 [/gdscript] [csharp] - GD.Print("0b101".BinToInt()); // Prints "5". - GD.Print("101".BinToInt()); // Prints "5". + GD.Print("101".BinToInt()); // Prints 5 + GD.Print("0b101".BinToInt()); // Prints 5 + GD.Print("-0b10".BinToInt()); // Prints -2 [/csharp] [/codeblocks] </description> @@ -87,26 +91,36 @@ <method name="capitalize" qualifiers="const"> <return type="String" /> <description> - Changes the case of some letters. Replaces underscores with spaces, adds spaces before in-word uppercase characters, converts all letters to lowercase, then capitalizes the first letter and every letter following a space character. For [code]capitalize camelCase mixed_with_underscores[/code], it will return [code]Capitalize Camel Case Mixed With Underscores[/code]. + Changes the appearance of the string: replaces underscores ([code]_[/code]) with spaces, adds spaces before uppercase letters in the middle of a word, converts all letters to lowercase, then converts the first one and each one following a space to uppercase. + [codeblocks] + [gdscript] + "move_local_x".capitalize() # Returns "Move Local X" + "sceneFile_path".capitalize() # Returns "Scene File Path" + [/gdscript] + [csharp] + "move_local_x".Capitalize(); // Returns "Move Local X" + "sceneFile_path".Capitalize(); // Returns "Scene File Path" + [/csharp] + [/codeblocks] + [b]Note:[/b] This method not the same as the default appearance of properties in the Inspector dock, as it does not capitalize acronyms ([code]"2D"[/code], [code]"FPS"[/code], [code]"PNG"[/code], etc.) as you may expect. </description> </method> <method name="casecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. + Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" and "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="chr" qualifiers="static"> <return type="String" /> <param index="0" name="char" type="int" /> <description> - Directly converts an decimal integer to a unicode character. Tables of these characters can be found in various locations, for example [url=https://unicodelookup.com/]here.[/url] + Returns a single Unicode character from the decimal [param char]. You may use [url=https://unicodelookup.com/]unicodelookup.com[/url] or [url=https://www.unicode.org/charts/]unicode.org[/url] as points of reference. [codeblock] - print(String.chr(65)) # Prints "A" + print(String.chr(65)) # Prints "A" print(String.chr(129302)) # Prints "🤖" (robot face emoji) [/codeblock] </description> @@ -115,7 +129,19 @@ <return type="bool" /> <param index="0" name="what" type="String" /> <description> - Returns [code]true[/code] if the string contains the given string. + Returns [code]true[/code] if the string contains [param what]. In GDScript, this corresponds to the [code]in[/code] operator. + [codeblocks] + [gdscript] + print("Node".contains("de")) # Prints true + print("team".contains("I")) # Prints false + print("I" in "team") # Prints false + [/gdscript] + [csharp] + GD.Print("Node".Contains("de")); // Prints true + GD.Print("team".Contains("I")); // Prints false + [/csharp] + [/codeblocks] + If you need to know where [param what] is within the string, use [method find]. </description> </method> <method name="count" qualifiers="const"> @@ -124,7 +150,7 @@ <param index="1" name="from" type="int" default="0" /> <param index="2" name="to" type="int" default="0" /> <description> - Returns the number of occurrences of substring [param what] between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + Returns the number of occurrences of the substring [param what] between [param from] and [param to] positions. If [param to] is 0, the search continues until the end of the string. </description> </method> <method name="countn" qualifiers="const"> @@ -133,7 +159,7 @@ <param index="1" name="from" type="int" default="0" /> <param index="2" name="to" type="int" default="0" /> <description> - Returns the number of occurrences of substring [param what] (ignoring case) between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + Returns the number of occurrences of the substring [param what] between [param from] and [param to] positions, [b]ignoring case[/b]. If [param to] is 0, the search continues until the end of the string. </description> </method> <method name="dedent" qualifiers="const"> @@ -146,7 +172,7 @@ <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if the string ends with the given string. + Returns [code]true[/code] if the string ends with the given [param text]. See also [method begins_with]. </description> </method> <method name="find" qualifiers="const"> @@ -154,17 +180,24 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="0" /> <description> - Returns the index of the [b]first[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. - [b]Note:[/b] If you just want to know whether a string contains a substring, use the [code]in[/code] operator as follows: + Returns the index of the [b]first[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the end of the string. [codeblocks] [gdscript] - print("i" in "team") # Will print `false`. + print("Team".find("I")) # Prints -1 + + print("Potato".find("t")) # Prints 2 + print("Potato".find("t", 3)) # Prints 4 + print("Potato".find("t", 5)) # Prints -1 [/gdscript] [csharp] - // C# has no in operator, but we can use `Contains()`. - GD.Print("team".Contains("i")); // Will print `false`. + GD.Print("Team".Find("I")); // Prints -1 + + GD.Print("Potato".Find("t")); // Prints 2 + GD.print("Potato".Find("t", 3)); // Prints 4 + GD.print("Potato".Find("t", 5)); // Prints -1 [/csharp] [/codeblocks] + [b]Note:[/b] If you just want to know whether the string contains [param what], use [method contains]. In GDScript, you may also use the [code]in[/code] operator. </description> </method> <method name="findn" qualifiers="const"> @@ -172,7 +205,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="0" /> <description> - Returns the index of the [b]first[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. + Returns the index of the [b]first[/b] [b]case-insensitive[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The starting search index can be specified with [param from], continuing to the end of the string. </description> </method> <method name="format" qualifiers="const"> @@ -183,53 +216,64 @@ Formats the string by replacing all occurrences of [param placeholder] with the elements of [param values]. [param values] can be a [Dictionary] or an [Array]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. [codeblock] - # Prints: Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it. + # Prints "Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it." var use_array_values = "Waiting for {0} is a play by {1}, and {0} Engine is named after it." print(use_array_values.format(["Godot", "Samuel Beckett"])) - # Prints: User 42 is Godot. + # Prints "User 42 is Godot." print("User {id} is {name}.".format({"id": 42, "name": "Godot"})) [/codeblock] - Some additional handling is performed when [param values] is an array. If [param placeholder] does not contain an underscore, the elements of the array will be used to replace one occurrence of the placeholder in turn; If an array element is another 2-element array, it'll be interpreted as a key-value pair. + Some additional handling is performed when [param values] is an [Array]. If [param placeholder] does not contain an underscore, the elements of the [param values] array will be used to replace one occurrence of the placeholder in order; If an element of [param values] is another 2-element array, it'll be interpreted as a key-value pair. [codeblock] - # Prints: User 42 is Godot. + # Prints "User 42 is Godot." print("User {} is {}.".format([42, "Godot"], "{}")) print("User {id} is {name}.".format([["id", 42], ["name", "Godot"]])) [/codeblock] + See also the [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html]GDScript format string[/url] tutorial. </description> </method> <method name="get_base_dir" qualifiers="const"> <return type="String" /> <description> If the string is a valid file path, returns the base directory name. + [codeblock] + var dir_path = "/path/to/file.txt".get_basename() # dir_path is "/path/to" + [/codeblock] </description> </method> <method name="get_basename" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the full file path without the extension. + If the string is a valid file path, returns the full file path, without the extension. + [codeblock] + var base = "/path/to/file.txt".get_basename() # base is "/path/to/file" + [/codeblock] </description> </method> <method name="get_extension" qualifiers="const"> <return type="String" /> <description> - Returns the extension without the leading period character ([code].[/code]) if the string is a valid file name or path. If the string does not contain an extension, returns an empty string instead. + If the string is a valid file name or path, returns the file extension without the leading period ([code].[/code]). Otherwise, returns an empty string. [codeblock] - print("/path/to/file.txt".get_extension()) # "txt" - print("file.txt".get_extension()) # "txt" - print("file.sample.txt".get_extension()) # "txt" - print(".txt".get_extension()) # "txt" - print("file.txt.".get_extension()) # "" (empty string) - print("file.txt..".get_extension()) # "" (empty string) - print("txt".get_extension()) # "" (empty string) - print("".get_extension()) # "" (empty string) + var a = "/path/to/file.txt".get_extension() # a is "txt" + var b = "cool.txt".get_extension() # b is "txt" + var c = "cool.font.tres".get_extension() # c is "tres" + var d = ".pack1".get_extension() # d is "pack1" + + var e = "file.txt.".get_extension() # e is "" + var f = "file.txt..".get_extension() # f is "" + var g = "txt".get_extension() # g is "" + var h = "".get_extension() # h is "" [/codeblock] </description> </method> <method name="get_file" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the filename. + If the string is a valid file path, returns the file name, including the extension. + [codeblock] + var file = "/path/to/icon.png".get_file() # file is "icon.png" + [/codeblock] </description> </method> <method name="get_slice" qualifiers="const"> @@ -237,11 +281,11 @@ <param index="0" name="delimiter" type="String" /> <param index="1" name="slice" type="int" /> <description> - Splits a string using a [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. - This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + Splits the string using a [param delimiter] and returns the substring at index [param slice]. Returns an empty string if the [param slice] does not exist. + This is faster than [method split], if you only need one substring. [b]Example:[/b] [codeblock] - print("i/am/example/string".get_slice("/", 2)) # Prints 'example'. + print("i/am/example/hi".get_slice("/", 2)) # Prints "example" [/codeblock] </description> </method> @@ -249,7 +293,7 @@ <return type="int" /> <param index="0" name="delimiter" type="String" /> <description> - Splits a string using a [param delimiter] and returns a number of slices. + Returns the total number of slices when the string is split with the given [param delimiter] (see [method split]). </description> </method> <method name="get_slicec" qualifiers="const"> @@ -257,29 +301,29 @@ <param index="0" name="delimiter" type="int" /> <param index="1" name="slice" type="int" /> <description> - Splits a string using a Unicode character with code [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. - This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + Splits the string using a Unicode character with code [param delimiter] and returns the substring at index [param slice]. Returns an empty string if the [param slice] does not exist. + This is faster than [method split], if you only need one substring. </description> </method> <method name="hash" qualifiers="const"> <return type="int" /> <description> Returns the 32-bit hash value representing the string's contents. - [b]Note:[/b] [String]s with equal content will always produce identical hash values. However, the reverse is not true. Returning identical hash values does [i]not[/i] imply the strings are equal, because different strings can have identical hash values due to hash collisions. + [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, strings with different hash values are guaranteed to be different. </description> </method> <method name="hex_to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing a hexadecimal number into an integer. Hexadecimal strings can either be prefixed with [code]0x[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + Converts the string representing a hexadecimal number into an [int]. The string may be optionally prefixed with [code]"0x"[/code], and an additional [code]-[/code] prefix for negative numbers. [codeblocks] [gdscript] - print("0xff".hex_to_int()) # Prints "255". - print("ab".hex_to_int()) # Prints "171". + print("0xff".hex_to_int()) # Prints 255 + print("ab".hex_to_int()) # Prints 171 [/gdscript] [csharp] - GD.Print("0xff".HexToInt()); // Prints "255". - GD.Print("ab".HexToInt()); // Prints "171". + GD.Print("0xff".HexToInt()); // Prints 255 + GD.Print("ab".HexToInt()); // Prints 171 [/csharp] [/codeblocks] </description> @@ -288,17 +332,16 @@ <return type="String" /> <param index="0" name="size" type="int" /> <description> - Converts an integer representing a number of bytes into a human-readable form. - Note that this output is in [url=https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes]IEC prefix format[/url], and includes [code]B[/code], [code]KiB[/code], [code]MiB[/code], [code]GiB[/code], [code]TiB[/code], [code]PiB[/code], and [code]EiB[/code]. + Converts [param size] which represents a number of bytes into a human-readable form. + The result is in [url=https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes]IEC prefix format[/url], which may end in either [code]"B"[/code], [code]"KiB"[/code], [code]"MiB"[/code], [code]"GiB"[/code], [code]"TiB"[/code], [code]"PiB"[/code], or [code]"EiB"[/code]. </description> </method> <method name="indent" qualifiers="const"> <return type="String" /> <param index="0" name="prefix" type="String" /> <description> - Returns a copy of the string with lines indented with [param prefix]. - For example, the string can be indented with two tabs using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. The prefix can be any string so it can also be used to comment out strings with e.g. [code]"# "[/code]. See also [method dedent] to remove indentation. - [b]Note:[/b] Empty lines are kept empty. + Indents every line of the string with the given [param prefix]. Empty lines are not indented. See also [method dedent] to remove indentation. + For example, the string can be indented with two tabulations using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. </description> </method> <method name="insert" qualifiers="const"> @@ -306,57 +349,65 @@ <param index="0" name="position" type="int" /> <param index="1" name="what" type="String" /> <description> - Returns a copy of the string with the substring [param what] inserted at the given [param position]. + Inserts [param what] at the given [param position] in the string. </description> </method> <method name="is_absolute_path" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the string is a path to a file or directory and its starting point is explicitly defined. This includes [code]res://[/code], [code]user://[/code], [code]C:\[/code], [code]/[/code], etc. + Returns [code]true[/code] if the string is a path to a file or directory, and its starting point is explicitly defined. This method is the opposite of [method is_relative_path]. + This includes all paths starting with [code]"res://"[/code], [code]"user://"[/code], [code]"C:\"[/code], [code]"/"[/code], etc. </description> </method> <method name="is_empty" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the length of the string equals [code]0[/code]. + Returns [code]true[/code] if the string's length is [code]0[/code] ([code]""[/code]). See also [method length]. </description> </method> <method name="is_relative_path" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the string is a path to a file or directory and its starting point is implicitly defined within the context it is being used. The starting point may refer to the current directory ([code]./[/code]), or the current [Node]. + Returns [code]true[/code] if the string is a path, and its starting point is dependent on context. The path could begin from the current directory, or the current [Node] (if the string is derived from a [NodePath]), and may sometimes be prefixed with [code]"./"[/code]. This method is the opposite of [method is_absolute_path]. </description> </method> <method name="is_subsequence_of" qualifiers="const"> <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if this string is a subsequence of the given string. + Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order. + [codeblock] + var text = "Wow, incredible!" + + print("inedible".is_subsequence_of(text)) # Prints true + print("Word!".is_subsequence_of(text)) # Prints true + print("Window".is_subsequence_of(text)) # Prints false + print("".is_subsequence_of(text)) # Prints true + [/codeblock] </description> </method> <method name="is_subsequence_ofn" qualifiers="const"> <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if this string is a subsequence of the given string, without considering case. + Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. </description> </method> <method name="is_valid_filename" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string is free from characters that aren't allowed in file names, those being: - [code]: / \ ? * " | % < >[/code] + Returns [code]true[/code] if this string does not contain characters that are not allowed in file names ([code]:[/code] [code]/[/code] [code]\[/code] [code]?[/code] [code]*[/code] [code]"[/code] [code]|[/code] [code]%[/code] [code]<[/code] [code]>[/code]). </description> </method> <method name="is_valid_float" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid float. This is inclusive of integers, and also supports exponents: + Returns [code]true[/code] if this string represents a valid floating-point number. A valid float may contain only digits, one decimal point ([code].[/code]), and the exponent letter ([code]e[/code]). It may also be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. Any valid integer is also a valid float (see [method is_valid_int]). See also [method to_float]. [codeblock] - print("1.7".is_valid_float()) # Prints "true" - print("24".is_valid_float()) # Prints "true" - print("7e3".is_valid_float()) # Prints "true" - print("Hello".is_valid_float()) # Prints "false" + print("1.7".is_valid_float()) # Prints true + print("24".is_valid_float()) # Prints true + print("7e3".is_valid_float()) # Prints true + print("Hello".is_valid_float()) # Prints false [/codeblock] </description> </method> @@ -364,57 +415,73 @@ <return type="bool" /> <param index="0" name="with_prefix" type="bool" default="false" /> <description> - Returns [code]true[/code] if this string contains a valid hexadecimal number. If [param with_prefix] is [code]true[/code], then a validity of the hexadecimal number is determined by the [code]0x[/code] prefix, for example: [code]0xDEADC0DE[/code]. + Returns [code]true[/code] if this string is a valid hexadecimal number. A valid hexadecimal number only contains digits or letters [code]A[/code] to [code]F[/code] (either uppercase or lowercase), and may be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. + If [param with_prefix] is [code]true[/code], the hexadecimal number needs to prefixed by [code]"0x"[/code] to be considered valid. + [codeblock] + print("A08E".is_valid_hex_number()) # Prints true + print("-AbCdEf".is_valid_hex_number()) # Prints true + print("2.5".is_valid_hex_number()) # Prints false + + print("0xDEADC0DE".is_valid_hex_number(true)) # Prints true + [/codeblock] </description> </method> <method name="is_valid_html_color" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid color in hexadecimal HTML notation. Other HTML notations such as named colors or [code]hsl()[/code] colors aren't considered valid by this method and will return [code]false[/code]. + Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. </description> </method> <method name="is_valid_identifier" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]) and the first character may not be a digit. + Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. [codeblock] - print("good_ident_1".is_valid_identifier()) # Prints "true" - print("1st_bad_ident".is_valid_identifier()) # Prints "false" - print("bad_ident_#2".is_valid_identifier()) # Prints "false" + print("node_2d".is_valid_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_identifier()) # Prints true + print("1st_method".is_valid_identifier()) # Prints false + print("MyMethod#2".is_valid_identifier()) # Prints false [/codeblock] </description> </method> <method name="is_valid_int" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid integer. + Returns [code]true[/code] if this string represents a valid integer. A valid integer only contains digits, and may be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. See also [method to_int]. [codeblock] - print("7".is_valid_int()) # Prints "true" - print("14.6".is_valid_int()) # Prints "false" - print("L".is_valid_int()) # Prints "false" - print("+3".is_valid_int()) # Prints "true" - print("-12".is_valid_int()) # Prints "true" + print("7".is_valid_int()) # Prints true + print("1.65".is_valid_int()) # Prints false + print("Hi".is_valid_int()) # Prints false + print("+3".is_valid_int()) # Prints true + print("-12".is_valid_int()) # Prints true [/codeblock] </description> </method> <method name="is_valid_ip_address" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains only a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]0.0.0.0[/code] as valid. + Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. </description> </method> <method name="join" qualifiers="const"> <return type="String" /> <param index="0" name="parts" type="PackedStringArray" /> <description> - Returns a [String] which is the concatenation of the [param parts]. The separator between elements is the string providing this method. + Returns the concatenation of [param parts]' elements, with each element separated by the string calling this method. This method is the opposite of [method split]. [b]Example:[/b] [codeblocks] [gdscript] - print(", ".join(["One", "Two", "Three", "Four"])) + var fruits = ["Apple", "Orange", "Pear", "Kiwi"] + + print(", ".join(fruits)) # Prints "Apple, Orange, Pear, Kiwi" + print("---".join(fruits)) # Prints "Apple---Orange---Pear---Kiwi" [/gdscript] [csharp] - GD.Print(String.Join(",", new string[] {"One", "Two", "Three", "Four"})); + var fruits = new string[] {"Apple", "Orange", "Pear", "Kiwi"}; + + // In C#, this method is static. + GD.Print(string.Join(", ", fruits); // Prints "Apple, Orange, Pear, Kiwi" + GD.Print(string.Join("---", fruits)); // Prints "Apple---Orange---Pear---Kiwi" [/csharp] [/codeblocks] </description> @@ -422,25 +489,24 @@ <method name="json_escape" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string with special characters escaped using the JSON standard. + Returns a copy of the string with special characters escaped using the JSON standard. Because it closely matches the C standard, it is possible to use [method c_unescape] to unescape the string, if necessary. </description> </method> <method name="left" qualifiers="const"> <return type="String" /> <param index="0" name="length" type="int" /> <description> - Returns a number of characters from the left of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. - [b]Example:[/b] + Returns the first [param length] characters from the beginning of the string. If [param length] is negative, strips the last [param length] characters from the string's end. [codeblock] - print("sample text".left(3)) #prints "sam" - print("sample text".left(-3)) #prints "sample t" + print("Hello World!".left(3)) # Prints "Hel" + print("Hello World!".left(-4)) # Prints "Hello Wo" [/codeblock] </description> </method> <method name="length" qualifiers="const"> <return type="int" /> <description> - Returns the number of characters in the string. + Returns the number of characters in the string. Empty strings ([code]""[/code]) always return [code]0[/code]. See also [method is_empty]. </description> </method> <method name="lpad" qualifiers="const"> @@ -448,62 +514,60 @@ <param index="0" name="min_length" type="int" /> <param index="1" name="character" type="String" default="" "" /> <description> - Formats a string to be at least [param min_length] long by adding [param character]s to the left of the string. + Formats the string to be at least [param min_length] long by adding [param character]s to the left of the string, if necessary. See also [method rpad]. </description> </method> <method name="lstrip" qualifiers="const"> <return type="String" /> <param index="0" name="chars" type="String" /> <description> - Returns a copy of the string with characters removed from the left. The [param chars] argument is a string specifying the set of characters to be removed. - [b]Note:[/b] The [param chars] is not a prefix. See [method trim_prefix] method that will remove a single prefix string rather than a set of characters. + Removes a set of characters defined in [param chars] from the string's beginning. See also [method rstrip]. + [b]Note:[/b] [param chars] is not a prefix. Use [method trim_prefix] to remove a single prefix, rather than a set of characters. </description> </method> <method name="match" qualifiers="const"> <return type="bool" /> <param index="0" name="expr" type="String" /> <description> - Does a simple case-sensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + Does a simple expression match, where [code]*[/code] matches zero or more arbitrary characters and [code]?[/code] matches any single character except a period ([code].[/code]). An empty string or empty expression always evaluates to [code]false[/code]. </description> </method> <method name="matchn" qualifiers="const"> <return type="bool" /> <param index="0" name="expr" type="String" /> <description> - Does a simple case-insensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + Does a simple [b]case-insensitive[/b] expression match, where [code]*[/code] matches zero or more arbitrary characters and [code]?[/code] matches any single character except a period ([code].[/code]). An empty string or empty expression always evaluates to [code]false[/code]. </description> </method> <method name="md5_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the MD5 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/MD5]MD5 hash[/url] of the string as a [PackedByteArray]. </description> </method> <method name="md5_text" qualifiers="const"> <return type="String" /> <description> - Returns the MD5 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/MD5]MD5 hash[/url] of the string as another [String]. </description> </method> <method name="naturalnocasecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-insensitive [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. - When used for sorting, natural order comparison will order suites of numbers as expected by most people. If you sort the numbers from 1 to 10 using natural order, you will get [code][1, 2, 3, ...][/code] instead of [code][1, 10, 2, 3, ...][/code]. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. + Performs a [b]case-insensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison. + When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code]. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. </description> </method> <method name="nocasecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-insensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. + Performs a [b]case-insensitive[/b] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="num" qualifiers="static"> @@ -511,20 +575,22 @@ <param index="0" name="number" type="float" /> <param index="1" name="decimals" type="int" default="-1" /> <description> - Converts a [float] to a string representation of a decimal number. - The number of decimal places can be specified with [param decimals]. If [param decimals] is [code]-1[/code] (default), decimal places will be automatically adjusted so that the string representation has 14 significant digits (counting both digits to the left and the right of the decimal point). - Trailing zeros are not included in the string. The last digit will be rounded and not truncated. + Converts a [float] to a string representation of a decimal number, with the number of decimal places specified in [param decimals]. + If [param decimals] is [code]-1[/code] as by default, the string representation may only have up to 14 significant digits, with digits before the decimal point having priority over digits after. + Trailing zeros are not included in the string. The last digit is rounded, not truncated. [b]Example:[/b] [codeblock] - String.num(3.141593) # "3.141593" - String.num(3.141593, 3) # "3.142" - String.num(3.14159300) # "3.141593", no trailing zeros. - # Last digit will be rounded up here, which reduces total digit count since - # trailing zeros are removed: - String.num(42.129999, 5) # "42.13" - # If `decimals` is not specified, the total number of significant digits is 14: - String.num(-0.0000012345432123454321) # "-0.00000123454321" - String.num(-10000.0000012345432123454321) # "-10000.0000012345" + String.num(3.141593) # Returns "3.141593" + String.num(3.141593, 3) # Returns "3.142" + String.num(3.14159300) # Returns "3.141593" + + # Here, the last digit will be rounded up, + # which reduces the total digit count, since trailing zeros are removed: + String.num(42.129999, 5) # Returns "42.13" + + # If `decimals` is not specified, the maximum number of significant digits is 14: + String.num(-0.0000012345432123454321) # Returns "-0.00000123454321" + String.num(-10000.0000012345432123454321) # Returns "-10000.0000012345" [/codeblock] </description> </method> @@ -534,13 +600,31 @@ <param index="1" name="base" type="int" default="10" /> <param index="2" name="capitalize_hex" type="bool" default="false" /> <description> - Converts a signed [int] to a string representation of a number. + Converts the given [param number] to a string representation, with the given [param base]. + By default, [param base] is set to decimal ([code]10[/code]). Other common bases in programming include binary ([code]2[/code]), [url=https://en.wikipedia.org/wiki/Octal]octal[/url] ([code]8[/code]), hexadecimal ([code]16[/code]). + If [param capitalize_hex] is [code]true[/code], digits higher than 9 are represented in uppercase. </description> </method> <method name="num_scientific" qualifiers="static"> <return type="String" /> <param index="0" name="number" type="float" /> <description> + Converts the given [param number] to a string representation, in scientific notation. + [codeblocks] + [gdscript] + var n = -5.2e8 + print(n) # Prints -520000000 + print(String.NumScientific(n)) # Prints -5.2e+08 + [/gdscript] + [csharp] + // This method is not implemented in C#. + // Use `string.ToString()` with "e" to achieve similar results. + var n = -5.2e8f; + GD.Print(n); // Prints -520000000 + GD.Print(n.ToString("e1")); // Prints -5.2e+008 + [/csharp] + [/codeblocks] + [b]Note:[/b] In C#, this method is not implemented. To achieve similar results, see C#'s [url=https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings]Standard numeric format strings[/url] </description> </method> <method name="num_uint64" qualifiers="static"> @@ -549,35 +633,38 @@ <param index="1" name="base" type="int" default="10" /> <param index="2" name="capitalize_hex" type="bool" default="false" /> <description> - Converts a unsigned [int] to a string representation of a number. + Converts the given unsigned [int] to a string representation, with the given [param base]. + By default, [param base] is set to decimal ([code]10[/code]). Other common bases in programming include binary ([code]2[/code]), [url=https://en.wikipedia.org/wiki/Octal]octal[/url] ([code]8[/code]), hexadecimal ([code]16[/code]). + If [param capitalize_hex] is [code]true[/code], digits higher than 9 are represented in uppercase. </description> </method> <method name="pad_decimals" qualifiers="const"> <return type="String" /> <param index="0" name="digits" type="int" /> <description> - Formats a number to have an exact number of [param digits] after the decimal point. + Formats the string representing a number to have an exact number of [param digits] [i]after[/i] the decimal point. </description> </method> <method name="pad_zeros" qualifiers="const"> <return type="String" /> <param index="0" name="digits" type="int" /> <description> - Formats a number to have an exact number of [param digits] before the decimal point. + Formats the string representing a number to have an exact number of [param digits] [i]before[/i] the decimal point. </description> </method> <method name="path_join" qualifiers="const"> <return type="String" /> <param index="0" name="file" type="String" /> <description> - If the string is a path, this concatenates [param file] at the end of the string as a subpath. E.g. [code]"this/is".path_join("path") == "this/is/path"[/code]. + Concatenates [param file] at the end of the string as a subpath, adding [code]/[/code] if necessary. + [b]Example:[/b] [code]"this/is".path_join("path") == "this/is/path"[/code]. </description> </method> <method name="repeat" qualifiers="const"> <return type="String" /> <param index="0" name="count" type="int" /> <description> - Returns original string repeated a number of times. The number of repetitions is given by the argument. + Repeats this string a number of times. [param count] needs to be greater than [code]0[/code]. Otherwise, returns an empty string. </description> </method> <method name="replace" qualifiers="const"> @@ -585,7 +672,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="forwhat" type="String" /> <description> - Replaces occurrences of a case-sensitive substring with the given one inside the string. + Replaces all occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> <method name="replacen" qualifiers="const"> @@ -593,7 +680,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="forwhat" type="String" /> <description> - Replaces occurrences of a case-insensitive substring with the given one inside the string. + Replaces all [b]case-insensitive[/b] occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> <method name="rfind" qualifiers="const"> @@ -601,7 +688,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="-1" /> <description> - Returns the index of the [b]last[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + Returns the index of the [b]last[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the string. This method is the reverse of [method find]. </description> </method> <method name="rfindn" qualifiers="const"> @@ -609,18 +696,17 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="-1" /> <description> - Returns the index of the [b]last[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + Returns the index of the [b]last[/b] [b]case-insensitive[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The starting search index can be specified with [param from], continuing to the beginning of the string. This method is the reverse of [method findn]. </description> </method> <method name="right" qualifiers="const"> <return type="String" /> <param index="0" name="length" type="int" /> <description> - Returns a number of characters from the right of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. - [b]Example:[/b] + Returns the last [param length] characters from the end of the string. If [param length] is negative, strips the first [param length] characters from the string's beginning. [codeblock] - print("sample text".right(3)) #prints "ext" - print("sample text".right(-3)) #prints "ple text" + print("Hello World!".right(3)) # Prints "ld!" + print("Hello World!".right(-4)) # Prints "o World!" [/codeblock] </description> </method> @@ -629,7 +715,7 @@ <param index="0" name="min_length" type="int" /> <param index="1" name="character" type="String" default="" "" /> <description> - Formats a string to be at least [param min_length] long by adding [param character]s to the right of the string. + Formats the string to be at least [param min_length] long, by adding [param character]s to the right of the string, if necessary. See also [method lpad]. </description> </method> <method name="rsplit" qualifiers="const"> @@ -638,21 +724,21 @@ <param index="1" name="allow_empty" type="bool" default="true" /> <param index="2" name="maxsplit" type="int" default="0" /> <description> - Splits the string by a [param delimiter] string and returns an array of the substrings, starting from right. If [param delimiter] is an empty string, each substring will be a single character. - The splits in the returned array are sorted in the same order as the original string, from left to right. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. - If [param maxsplit] is specified, it defines the number of splits to do from the right up to [param maxsplit]. The default value of 0 means that all items are split, thus giving the same result as [method split]. + Splits the string using a [param delimiter] and returns an array of the substrings, starting from the end of the string. The splits in the returned array appear in the same order as the original string. If [param delimiter] is an empty string, each substring will be a single character. + If [param allow_empty] is [code]false[/code], empty strings between adjacent delimiters are excluded from the array. + If [param maxsplit] is greater than [code]0[/code], the number of splits may not exceed [param maxsplit]. By default, the entire string is split, which is mostly identical to [method split]. [b]Example:[/b] [codeblocks] [gdscript] var some_string = "One,Two,Three,Four" var some_array = some_string.rsplit(",", true, 1) + print(some_array.size()) # Prints 2 - print(some_array[0]) # Prints "One,Two,Three" - print(some_array[1]) # Prints "Four" + print(some_array[0]) # Prints "One,Two,Three" + print(some_array[1]) # Prints "Four" [/gdscript] [csharp] - // There is no Rsplit. + // In C#, there is no String.RSplit() method. [/csharp] [/codeblocks] </description> @@ -661,51 +747,55 @@ <return type="String" /> <param index="0" name="chars" type="String" /> <description> - Returns a copy of the string with characters removed from the right. The [param chars] argument is a string specifying the set of characters to be removed. - [b]Note:[/b] The [param chars] is not a suffix. See [method trim_suffix] method that will remove a single suffix string rather than a set of characters. + Removes a set of characters defined in [param chars] from the string's end. See also [method lstrip]. + [b]Note:[/b] [param chars] is not a suffix. Use [method trim_suffix] to remove a single suffix, rather than a set of characters. </description> </method> <method name="sha1_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the SHA-1 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/SHA-1]SHA-1[/url] hash of the string as a [PackedByteArray]. </description> </method> <method name="sha1_text" qualifiers="const"> <return type="String" /> <description> - Returns the SHA-1 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/SHA-1]SHA-1[/url] hash of the string as another [String]. </description> </method> <method name="sha256_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the SHA-256 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/SHA-2]SHA-256[/url] hash of the string as a [PackedByteArray]. </description> </method> <method name="sha256_text" qualifiers="const"> <return type="String" /> <description> - Returns the SHA-256 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/SHA-2]SHA-256[/url] hash of the string as another [String]. </description> </method> <method name="similarity" qualifiers="const"> <return type="float" /> <param index="0" name="text" type="String" /> <description> - Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of 1.0 means totally similar, while 0.0 means totally dissimilar. + Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of [code]1.0[/code] means totally similar, while [code]0.0[/code] means totally dissimilar. [codeblock] - print("ABC123".similarity("ABC123")) # Prints "1" - print("ABC123".similarity("XYZ456")) # Prints "0" - print("ABC123".similarity("123ABC")) # Prints "0.8" - print("ABC123".similarity("abc123")) # Prints "0.4" + print("ABC123".similarity("ABC123")) # Prints 1.0 + print("ABC123".similarity("XYZ456")) # Prints 0.0 + print("ABC123".similarity("123ABC")) # Prints 0.8 + print("ABC123".similarity("abc123")) # Prints 0.4 [/codeblock] </description> </method> <method name="simplify_path" qualifiers="const"> <return type="String" /> <description> - Returns a simplified canonical path. + If the string is a valid file path, converts the string into a canonical path. This is the shortest possible path, without [code]"./"[/code], and all the unnecessary [code]".."[/code] and [code]"/"[/code]. + [codeblock] + var simple_path = "./path/to///../file".simplify_path() + print(simple_path) # Prints "path/file" + [/codeblock] </description> </method> <method name="split" qualifiers="const"> @@ -714,27 +804,29 @@ <param index="1" name="allow_empty" type="bool" default="true" /> <param index="2" name="maxsplit" type="int" default="0" /> <description> - Splits the string by a [param delimiter] string and returns an array of the substrings. The [param delimiter] can be of any length. If [param delimiter] is an empty string, each substring will be a single character. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. - If [param maxsplit] is specified, it defines the number of splits to do from the left up to [param maxsplit]. The default value of [code]0[/code] means that all items are split. - If you need only one element from the array at a specific index, [method get_slice] is a more performant option. + Splits the string using a [param delimiter] and returns an array of the substrings. If [param delimiter] is an empty string, each substring will be a single character. This method is the opposite of [method join]. + If [param allow_empty] is [code]false[/code], empty strings between adjacent delimiters are excluded from the array. + If [param maxsplit] is greater than [code]0[/code], the number of splits may not exceed [param maxsplit]. By default, the entire string is split. [b]Example:[/b] [codeblocks] [gdscript] - var some_string = "One,Two,Three,Four" - var some_array = some_string.split(",", true, 1) - print(some_array.size()) # Prints 2 - print(some_array[0]) # Prints "Four" - print(some_array[1]) # Prints "Three,Two,One" + var some_array = "One,Two,Three,Four".split(",", true, 2) + + print(some_array.size()) # Prints 3 + print(some_array[0]) # Prints "One" + print(some_array[1]) # Prints "Two" + print(some_array[2]) # Prints "Three,Four" [/gdscript] [csharp] - var someString = "One,Two,Three,Four"; - var someArray = someString.Split(",", true); // This is as close as it gets to Godots API. - GD.Print(someArray[0]); // Prints "Four" - GD.Print(someArray[1]); // Prints "Three,Two,One" + // C#'s `Split()` does not support the `maxsplit` parameter. + var someArray = "One,Two,Three".Split(","); + + GD.Print(someArray[0]); // Prints "One" + GD.Print(someArray[1]); // Prints "Two" + GD.Print(someArray[2]); // Prints "Three" [/csharp] [/codeblocks] - If you need to split strings with more complex rules, use the [RegEx] class instead. + [b]Note:[/b] If you only need one substring from the array, consider using [method get_slice] which is faster. If you need to split strings with more complex rules, use the [RegEx] class instead. </description> </method> <method name="split_floats" qualifiers="const"> @@ -742,9 +834,13 @@ <param index="0" name="delimiter" type="String" /> <param index="1" name="allow_empty" type="bool" default="true" /> <description> - Splits the string in floats by using a delimiter string and returns an array of the substrings. - For example, [code]"1,2.5,3"[/code] will return [code][1,2.5,3][/code] if split by [code]","[/code]. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. + Splits the string into floats by using a [param delimiter] and returns a [PackedFloat64Array]. + If [param allow_empty] is [code]false[/code], empty or invalid [float] conversions between adjacent delimiters are excluded. + [codeblock] + var a = "1,2,4.5".split_floats(",") # a is [1.0, 2.0, 4.5] + var c = "1| ||4.5".split_floats("|") # c is [1.0, 0.0, 0.0, 4.5] + var b = "1| ||4.5".split_floats("|", false) # b is [1.0, 4.5] + [/codeblock] </description> </method> <method name="strip_edges" qualifiers="const"> @@ -752,13 +848,14 @@ <param index="0" name="left" type="bool" default="true" /> <param index="1" name="right" type="bool" default="true" /> <description> - Returns a copy of the string stripped of any non-printable character (including tabulations, spaces and line breaks) at the beginning and the end. The optional arguments are used to toggle stripping on the left and right edges respectively. + Strips all non-printable characters from the beginning and the end of the string. These include spaces, tabulations ([code]\t[/code]), and newlines ([code]\n[/code] [code]\r[/code]). + If [param left] is [code]false[/code], ignores the string's beginning. Likewise, if [param right] is [code]false[/code], ignores the string's end. </description> </method> <method name="strip_escapes" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string stripped of any escape character. These include all non-printable control characters of the first page of the ASCII table (< 32), such as tabulation ([code]\t[/code] in C) and newline ([code]\n[/code] and [code]\r[/code]) characters, but not spaces. + Strips all escape characters from the string. These include all non-printable control characters of the first page of the ASCII table (values from 0 to 31), such as tabulation ([code]\t[/code]) and newline ([code]\n[/code], [code]\r[/code]) characters, but [i]not[/i] spaces. </description> </method> <method name="substr" qualifiers="const"> @@ -766,13 +863,13 @@ <param index="0" name="from" type="int" /> <param index="1" name="len" type="int" default="-1" /> <description> - Returns part of the string from the position [param from] with length [param len]. Argument [param len] is optional and using [code]-1[/code] will return remaining characters from given position. + Returns part of the string from the position [param from] with length [param len]. If [param len] is [code]-1[/code] (as by default), returns the rest of the string starting from the given position. </description> </method> <method name="to_ascii_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces. + Converts the string to an [url=https://en.wikipedia.org/wiki/ASCII]ASCII[/url]/Latin-1 encoded [PackedByteArray]. This method is slightly faster than [method to_utf8_buffer], but replaces all unsupported characters with spaces. </description> </method> <method name="to_camel_case" qualifiers="const"> @@ -784,23 +881,25 @@ <method name="to_float" qualifiers="const"> <return type="float" /> <description> - Converts a string containing a decimal number into a [code]float[/code]. The method will stop on the first non-number character except the first [code].[/code] (decimal point), and [code]e[/code] which is used for exponential. + Converts the string representing a decimal number into a [float]. This method stops on the first non-number character, except the first decimal point ([code].[/code]) and the exponent letter ([code]e[/code]). See also [method is_valid_float]. [codeblock] - print("12.3".to_float()) # 12.3 - print("1.2.3".to_float()) # 1.2 - print("12ab3".to_float()) # 12 - print("1e3".to_float()) # 1000 + var a = "12.35".to_float() # a is 12.35 + var b = "1.2.3".to_float() # b is 1.2 + var c = "12xy3".to_float() # c is 12.0 + var d = "1e3".to_float() # d is 1000.0 + var e = "Hello!".to_int() # e is 0.0 [/codeblock] </description> </method> <method name="to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing an integer number into an [code]int[/code]. The method will remove any non-number character and stop if it encounters a [code].[/code]. + Converts the string representing an integer number into an [int]. This method removes any non-number character and stops at the first decimal point ([code].[/code]). See also [method is_valid_int]. [codeblock] - print("123".to_int()) # 123 - print("a1b2c3".to_int()) # 123 - print("1.2.3".to_int()) # 1 + var a = "123".to_int() # a is 123 + var b = "x1y2z3".to_int() # b is 123 + var c = "-1.2.3".to_int() # c is -1 + var d = "Hello!".to_int() # d is 0 [/codeblock] </description> </method> @@ -831,33 +930,33 @@ <method name="to_utf16_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-16 encoded [PackedByteArray] (which is an array of bytes). + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-16]UTF-16[/url] encoded [PackedByteArray]. </description> </method> <method name="to_utf32_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-32 encoded [PackedByteArray] (which is an array of bytes). + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-32]UTF-32[/url] encoded [PackedByteArray]. </description> </method> <method name="to_utf8_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-8 encode [PackedByteArray] (which is an array of bytes). The conversion is a bit slower than [method to_ascii_buffer], but supports all UTF-8 characters. Therefore, you should prefer this function over [method to_ascii_buffer]. + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-8]UTF-8[/url] encoded [PackedByteArray]. This method is slightly slower than [method to_ascii_buffer], but supports all UTF-8 characters. For most cases, prefer using this method. </description> </method> <method name="trim_prefix" qualifiers="const"> <return type="String" /> <param index="0" name="prefix" type="String" /> <description> - Removes a given string from the start if it starts with it or leaves the string unchanged. + Removes the given [param prefix] from the start of the string, or returns the string unchanged. </description> </method> <method name="trim_suffix" qualifiers="const"> <return type="String" /> <param index="0" name="suffix" type="String" /> <description> - Removes a given string from the end if it ends with it or leaves the string unchanged. + Removes the given [param suffix] from the end of the string, or returns the string unchanged. </description> </method> <method name="unicode_at" qualifiers="const"> @@ -870,13 +969,15 @@ <method name="uri_decode" qualifiers="const"> <return type="String" /> <description> - Decodes a string in URL encoded format. This is meant to decode parameters in a URL when receiving an HTTP request. + Decodes the string from its URL-encoded format. This method is meant to properly decode the parameters in a URL when receiving an HTTP request. [codeblocks] [gdscript] - print("https://example.org/?escaped=" + "Godot%20Engine%3A%27docs%27".uri_decode()) + var url = "$DOCS_URL/?highlight=Godot%20Engine%3%docs" + print(url.uri_decode()) # Prints "$DOCS_URL/?hightlight=Godot Engine:docs" [/gdscript] [csharp] - GD.Print("https://example.org/?escaped=" + "Godot%20Engine%3a%27Docs%27".URIDecode()); + var url = "$DOCS_URL/?highlight=Godot%20Engine%3%docs" + GD.Print(url.URIDecode()) // Prints "$DOCS_URL/?hightlight=Godot Engine:docs" [/csharp] [/codeblocks] </description> @@ -884,13 +985,19 @@ <method name="uri_encode" qualifiers="const"> <return type="String" /> <description> - Encodes a string to URL friendly format. This is meant to encode parameters in a URL when sending an HTTP request. + Encodes the string to URL-friendly format. This method is meant to properly encode the parameters in a URL when sending an HTTP request. [codeblocks] [gdscript] - print("https://example.org/?escaped=" + "Godot Engine:'docs'".uri_encode()) + var prefix = "$DOCS_URL/?hightlight=" + var url = prefix + "Godot Engine:docs".uri_encode() + + print(url) # Prints "$DOCS_URL/?highlight=Godot%20Engine%3%docs" [/gdscript] [csharp] - GD.Print("https://example.org/?escaped=" + "Godot Engine:'docs'".URIEncode()); + var prefix = "$DOCS_URL/?hightlight="; + var url = prefix + "Godot Engine:docs".URIEncode(); + + GD.Print(url); // Prints "$DOCS_URL/?highlight=Godot%20Engine%3%docs" [/csharp] [/codeblocks] </description> @@ -898,7 +1005,7 @@ <method name="validate_node_name" qualifiers="const"> <return type="String" /> <description> - Removes any characters from the string that are prohibited in [Node] names ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code]). + Removes all characters that are not allowed in [member Node.name] from the string ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code] [code]%[/code]). </description> </method> <method name="xml_escape" qualifiers="const"> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 8960c66acc..1f71b77cbd 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -1565,7 +1565,7 @@ def format_text_block( escape_post = False # Tag is a reference to a class. - if tag_text in state.classes: + if tag_text in state.classes and not inside_code: if tag_text == state.current_class: # Don't create a link to the same class, format it as strong emphasis. tag_text = f"**{tag_text}**" diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 1b42b55425..7b4131b3a3 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -285,6 +285,15 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display ERR_FAIL_COND(!rt); + // We normally render to the render target upside down, so flip Y when blitting to the screen. + bool flip_y = true; + if (rt->overridden.color.is_valid()) { + // If we've overridden the render target's color texture, that means we + // didn't render upside down, so we don't need to flip it. + // We're probably rendering directly to an XR device. + flip_y = false; + } + GLuint read_fbo = 0; if (rt->view_count > 1) { glGenFramebuffers(1, &read_fbo); @@ -296,10 +305,9 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); - // Flip content upside down to correct for coordinates. Vector2i screen_rect_end = p_screen_rect.get_end(); glBlitFramebuffer(0, 0, rt->size.x, rt->size.y, - p_screen_rect.position.x, screen_rect_end.y, screen_rect_end.x, p_screen_rect.position.y, + p_screen_rect.position.x, flip_y ? screen_rect_end.y : p_screen_rect.position.y, screen_rect_end.x, flip_y ? p_screen_rect.position.y : screen_rect_end.y, GL_COLOR_BUFFER_BIT, GL_NEAREST); if (read_fbo != 0) { diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 1f3056807b..247b89658a 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1902,7 +1902,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glColorMask(0, 0, 0, 0); glClearDepth(1.0f); glClear(GL_DEPTH_BUFFER_BIT); - uint32_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | + uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | SceneShaderGLES3::DISABLE_LIGHT_SPOT; @@ -1943,7 +1943,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glClearBufferfv(GL_COLOR, 0, clear_color.components); } RENDER_TIMESTAMP("Render Opaque Pass"); - uint32_t spec_constant_base_flags = 0; + uint64_t spec_constant_base_flags = 0; { // Specialization Constants that apply for entire rendering pass. @@ -2014,8 +2014,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, GeometryInstanceGLES3 *prev_inst = nullptr; SceneShaderGLES3::ShaderVariant prev_variant = SceneShaderGLES3::ShaderVariant::MODE_COLOR; SceneShaderGLES3::ShaderVariant shader_variant = SceneShaderGLES3::MODE_COLOR; // Assigned to silence wrong -Wmaybe-initialized + uint64_t prev_spec_constants = 0; - uint32_t base_spec_constants = p_params->spec_constant_base_flags; + // Specializations constants used by all instances in the scene. + uint64_t base_spec_constants = p_params->spec_constant_base_flags; if (p_render_data->view_count > 1) { base_spec_constants |= SceneShaderGLES3::USE_MULTIVIEW; @@ -2235,8 +2237,18 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, instance_variant = SceneShaderGLES3::ShaderVariant(1 + int(shader_variant)); } - if (prev_shader != shader || prev_variant != instance_variant) { - bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, base_spec_constants); + uint64_t spec_constants = base_spec_constants; + + if (inst->omni_light_count == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; + } + + if (inst->spot_light_count == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + } + + if (prev_shader != shader || prev_variant != instance_variant || spec_constants != prev_spec_constants) { + bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, spec_constants); if (!success) { continue; } @@ -2248,29 +2260,30 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, opaque_prepass_threshold = 0.1; } - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OPAQUE_PREPASS_THRESHOLD, opaque_prepass_threshold, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OPAQUE_PREPASS_THRESHOLD, opaque_prepass_threshold, shader->version, instance_variant, spec_constants); prev_shader = shader; prev_variant = instance_variant; + prev_spec_constants = spec_constants; } if (prev_inst != inst || prev_shader != shader || prev_variant != instance_variant) { // Rebind the light indices. - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_count, shader->version, instance_variant, base_spec_constants); - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_count, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_count, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_count, shader->version, instance_variant, spec_constants); if (inst->omni_light_count) { - glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, base_spec_constants), inst->omni_light_count, inst->omni_light_gl_cache.ptr()); + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->omni_light_count, inst->omni_light_gl_cache.ptr()); } if (inst->spot_light_count) { - glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, base_spec_constants), inst->spot_light_count, inst->spot_light_gl_cache.ptr()); + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->spot_light_count, inst->spot_light_gl_cache.ptr()); } prev_inst = inst; } - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, spec_constants); if (inst->instance_count > 0) { // Using MultiMesh or Particles. // Bind instance buffers. diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index cd1f44e679..255e62fc33 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -392,10 +392,10 @@ private: GeometryInstanceSurface **elements = nullptr; int element_count = 0; bool reverse_cull = false; - uint32_t spec_constant_base_flags = 0; + uint64_t spec_constant_base_flags = 0; bool force_wireframe = false; - RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint32_t p_spec_constant_base_flags, bool p_force_wireframe = false) { + RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false) { elements = p_elements; element_count = p_element_count; reverse_cull = p_reverse_cull; diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 1dcd17ea0e..69c3ff7c8e 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -36,6 +36,11 @@ #include "core/io/dir_access.h" #include "core/io/file_access.h" +static String _mkid(const String &p_id) { + String id = "m_" + p_id.replace("__", "_dus_"); + return id.replace("__", "_dus_"); //doubleunderscore is reserved in glsl +} + void ShaderGLES3::_add_stage(const char *p_code, StageType p_stage_type) { Vector<String> lines = String(p_code).split("\n"); @@ -425,7 +430,7 @@ void ShaderGLES3::_compile_specialization(Version::Specialization &spec, uint32_ } // textures for (int i = 0; i < p_version->texture_uniforms.size(); i++) { - String native_uniform_name = p_version->texture_uniforms[i]; + String native_uniform_name = _mkid(p_version->texture_uniforms[i]); GLint location = glGetUniformLocation(spec.id, (native_uniform_name).ascii().get_data()); glUniform1i(location, i + base_texture_index); } diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index a61ea1587d..c1c26ed963 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -41,8 +41,6 @@ layout(std140) uniform MaterialUniforms{ //ubo:4 #include "canvas_uniforms_inc.glsl" #include "stdlib_inc.glsl" -uniform sampler2D transforms_texture; //texunit:-1 - out vec2 uv_interp; out vec4 color_interp; out vec2 vertex_interp; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 32557d3314..fa68f0063f 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -779,7 +779,7 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { nd *= nd; // nd^2 return nd * pow(max(distance, 0.0001), -decay); } - +#ifndef DISABLE_LIGHT_OMNI void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, @@ -821,7 +821,9 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f diffuse_light, specular_light); } +#endif // !DISABLE_LIGHT_OMNI +#ifndef DISABLE_LIGHT_SPOT void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, @@ -869,6 +871,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f #endif diffuse_light, specular_light); } +#endif // !DISABLE_LIGHT_SPOT #endif // !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) && !defined(DISABLE_LIGHT_SPOT) #ifndef MODE_RENDER_DEPTH diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 285f32f1a5..5bbbc7b91b 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -334,7 +334,14 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { const AABB &bone = p_surface.bone_aabbs[i]; if (bone.has_volume()) { - mesh->bone_aabbs.write[i].merge_with(bone); + AABB &mesh_bone = mesh->bone_aabbs.write[i]; + if (mesh_bone != AABB()) { + // Already initialized, merge AABBs. + mesh_bone.merge_with(bone); + } else { + // Not yet initialized, copy the bone AABB. + mesh_bone = bone; + } } } mesh->aabb.merge_with(p_surface.aabb); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 926c01b334..7ae0db7b48 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -873,6 +873,10 @@ void CodeTextEditor::_reset_zoom() { } void CodeTextEditor::_line_col_changed() { + if (!code_complete_timer->is_stopped() && code_complete_timer_line != text_editor->get_caret_line()) { + code_complete_timer->stop(); + } + String line = text_editor->get_line(text_editor->get_caret_line()); int positional_column = 0; @@ -902,6 +906,7 @@ void CodeTextEditor::_line_col_changed() { void CodeTextEditor::_text_changed() { if (text_editor->is_insert_text_operation()) { + code_complete_timer_line = text_editor->get_caret_line(); code_complete_timer->start(); } diff --git a/editor/code_editor.h b/editor/code_editor.h index ded7518287..4d7034fbd0 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -158,6 +158,7 @@ class CodeTextEditor : public VBoxContainer { Label *info = nullptr; Timer *idle = nullptr; Timer *code_complete_timer = nullptr; + int code_complete_timer_line = 0; Timer *font_resize_timer = nullptr; int font_resize_val; diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp index b92b0fca59..609afeb40a 100644 --- a/editor/editor_command_palette.cpp +++ b/editor/editor_command_palette.cpp @@ -38,6 +38,9 @@ EditorCommandPalette *EditorCommandPalette::singleton = nullptr; +static Rect2i prev_rect = Rect2i(); +static bool was_showed = false; + float EditorCommandPalette::_score_path(const String &p_search, const String &p_path) { float score = 0.9f + .1f * (p_search.length() / (float)p_path.length()); @@ -145,6 +148,17 @@ void EditorCommandPalette::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_command", "key_name"), &EditorCommandPalette::remove_command); } +void EditorCommandPalette::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + prev_rect = Rect2i(get_position(), get_size()); + was_showed = true; + } + } break; + } +} + void EditorCommandPalette::_sbox_input(const Ref<InputEvent> &p_ie) { Ref<InputEventKey> k = p_ie; if (k.is_valid()) { @@ -171,12 +185,10 @@ void EditorCommandPalette::_confirmed() { } void EditorCommandPalette::open_popup() { - static bool was_showed = false; - if (!was_showed) { - was_showed = true; - popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); + if (was_showed) { + popup(prev_rect); } else { - show(); + popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); } command_search_box->clear(); diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h index 15200552b4..81cf401851 100644 --- a/editor/editor_command_palette.h +++ b/editor/editor_command_palette.h @@ -91,6 +91,7 @@ class EditorCommandPalette : public ConfirmationDialog { protected: static void _bind_methods(); + void _notification(int p_what); public: void open_popup(); diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp index bb533b88d6..6ddccba0e2 100644 --- a/editor/editor_quick_open.cpp +++ b/editor/editor_quick_open.cpp @@ -34,23 +34,24 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" -void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dontclear) { +static Rect2i prev_rect = Rect2i(); +static bool was_showed = false; + +void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dont_clear) { base_type = p_base; allow_multi_select = p_enable_multi; search_options->set_select_mode(allow_multi_select ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE); - static bool was_showed = false; - if (!was_showed) { - was_showed = true; - popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); + if (was_showed) { + popup(prev_rect); } else { - show(); + popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); } EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem(); _build_search_cache(efsd); - if (p_dontclear) { + if (p_dont_clear) { search_box->select_all(); _update_search(); } else { @@ -251,6 +252,13 @@ void EditorQuickOpen::_notification(int p_what) { search_box->set_clear_button_enabled(true); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + prev_rect = Rect2i(get_position(), get_size()); + was_showed = true; + } + } break; + case NOTIFICATION_EXIT_TREE: { disconnect("confirmed", callable_mp(this, &EditorQuickOpen::_confirmed)); } break; diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 11a8fce9c3..9128143619 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -582,6 +582,7 @@ void EditorSpinSlider::_value_focus_exited() { //tab was pressed } else { //enter, click, esc + grab_focus(); } } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 4273a31d62..c01db215da 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -442,10 +442,11 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset> result.insert(E); } + result.insert("template"); if (p_debug) { - result.insert("debug"); + result.insert("template_debug"); } else { - result.insert("release"); + result.insert("template_release"); } if (!p_preset->get_custom_features().is_empty()) { diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index 9de2f94900..5345346c48 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -81,8 +81,8 @@ bool EditorExportPlatformPC::has_valid_export_configuration(const Ref<EditorExpo // Look for export templates (first official, and if defined custom templates). String arch = p_preset->get("binary_format/architecture"); - bool dvalid = exists_export_template(get_template_file_name("debug", arch), &err); - bool rvalid = exists_export_template(get_template_file_name("release", arch), &err); + bool dvalid = exists_export_template(get_template_file_name("template_debug", arch), &err); + bool rvalid = exists_export_template(get_template_file_name("template_release", arch), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 0f67663948..dbd1b12a94 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -376,11 +376,11 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position) { +void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_node_position) { _update_options_menu(p_has_input_ports); use_position_from_popup_menu = true; position_from_popup_menu = p_node_position; - add_node->get_popup()->set_position(p_popup_position); + add_node->get_popup()->set_position(graph->get_screen_position() + graph->get_local_mouse_position()); add_node->get_popup()->reset_size(); add_node->get_popup()->popup(); } @@ -390,7 +390,7 @@ void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { return; } - _popup(false, graph->get_screen_position() + graph->get_local_mouse_position(), p_position); + _popup(false, p_position); } void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { @@ -401,7 +401,7 @@ void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, in Ref<AnimationNode> node = blend_tree->get_node(p_from); if (node.is_valid()) { from_node = p_from; - _popup(true, p_release_position, graph->get_global_mouse_position()); + _popup(true, p_release_position); } } @@ -414,7 +414,7 @@ void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, in if (node.is_valid()) { to_node = p_to; to_slot = p_to_slot; - _popup(false, p_release_position, graph->get_global_mouse_position()); + _popup(false, p_release_position); } } diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index fb19cce147..4b55aa9b3f 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -117,7 +117,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _filter_toggled(); Ref<AnimationNode> _filter_edit; - void _popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position); + void _popup(bool p_has_input_ports, const Vector2 &p_node_position); void _popup_request(const Vector2 &p_position); void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index c5f7d2a047..d62691c76d 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -54,58 +54,36 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p String entry_symbol = config->get_value("configuration", "entry_symbol"); - List<String> libraries; - - config->get_section_keys("libraries", &libraries); - - bool could_export = false; - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_features.has(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - String library_path = config->get_value("libraries", E); - if (!library_path.begins_with("res://")) { - print_line("Skipping export of out-of-project library " + library_path); - continue; - } - add_shared_object(library_path, tags); - - if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { - String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" - "\n" - "extern \"C\" void $ENTRY();\n" - "void $ENTRY_init() {\n" - " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" - "}\n" - "struct $ENTRY_struct {\n" - " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" - " }\n" - "};\n" - "$ENTRY_struct $ENTRY_struct_instance;\n\n"; - additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); - - String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); - } - could_export = true; - break; + PackedStringArray tags; + String library_path = NativeExtension::find_extension_library( + p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); }, &tags); + if (!library_path.is_empty()) { + add_shared_object(library_path, tags); + + if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n" + "\n" + "extern \"C\" void $ENTRY();\n" + "void $ENTRY_init() {\n" + " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" + "}\n" + "struct $ENTRY_struct {\n" + " $ENTRY_struct() {\n" + " add_ios_init_callback($ENTRY_init);\n" + " }\n" + "};\n" + "$ENTRY_struct $ENTRY_struct_instance;\n\n"; + additional_code = additional_code.replace("$ENTRY", entry_symbol); + add_ios_cpp_code(additional_code); + + String linker_flags = "-Wl,-U,_" + entry_symbol; + add_ios_linker_flags(linker_flags); } - } - if (!could_export) { - Vector<String> tags; + } else { + Vector<String> features_vector; for (const String &E : p_features) { - tags.append(E); + features_vector.append(E); } ERR_FAIL_MSG(vformat("No suitable library found. The libraries' tags referred to an invalid feature flag. Possible feature flags for your platform: %s", p_path, String(", ").join(tags))); } @@ -115,11 +93,11 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p config->get_section_keys("dependencies", &dependencies); } - for (const String &E : libraries) { - Vector<String> tags = E.split("."); + for (const String &E : dependencies) { + Vector<String> dependency_tags = E.split("."); bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); if (!p_features.has(tag)) { all_tags_met = false; break; @@ -129,13 +107,12 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p if (all_tags_met) { Dictionary dependency = config->get_value("dependencies", E); for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { - String library_path = *key; + String dependency_path = *key; String target_path = dependency[*key]; - if (!library_path.begins_with("res://")) { - print_line("Skipping export of out-of-project library " + library_path); - continue; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); } - add_shared_object(library_path, tags, target_path); + add_shared_object(dependency_path, dependency_tags, target_path); } break; } diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 5a7b0321b7..63ca78d6c0 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -240,38 +240,63 @@ void Path3DGizmo::redraw() { return; } - Vector<Vector3> v3a = c->tessellate(); - //Vector<Vector3> v3a=c->get_baked_points(); + real_t interval = 0.1; + const real_t length = c->get_baked_length(); - int v3s = v3a.size(); - if (v3s == 0) { - return; - } - Vector<Vector3> v3p; - const Vector3 *r = v3a.ptr(); - - // BUG: the following won't work when v3s, avoid drawing as a temporary workaround. - for (int i = 0; i < v3s - 1; i++) { - v3p.push_back(r[i]); - v3p.push_back(r[i + 1]); - //v3p.push_back(r[i]); - //v3p.push_back(r[i]+Vector3(0,0.2,0)); - } + // 1. Draw curve and bones. + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform3D> frames; + frames.resize(sample_count); + + { + Transform3D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = c->sample_baked_with_rotation(i * interval, true, true); + } + } + + const Transform3D *r = frames.ptr(); + Vector<Vector3> v3p; + for (int i = 0; i < sample_count - 1; i++) { + const Vector3 p1 = r[i].origin; + const Vector3 p2 = r[i + 1].origin; + const Vector3 side = r[i].basis.get_column(0); + const Vector3 up = r[i].basis.get_column(1); + const Vector3 forward = r[i].basis.get_column(2); + + // Curve segment. + v3p.push_back(p1); + v3p.push_back(p2); + + // Fish Bone. + v3p.push_back(p1); + v3p.push_back(p1 + (side - forward) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + (-side - forward) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + up * 0.03); + } - if (v3p.size() > 1) { add_lines(v3p, path_material); add_collision_segments(v3p); } + // 2. Draw handles. if (Path3DEditorPlugin::singleton->get_edited_path() == path) { - v3p.clear(); + Vector<Vector3> v3p; Vector<Vector3> handle_points; Vector<Vector3> sec_handle_points; for (int i = 0; i < c->get_point_count(); i++) { Vector3 p = c->get_point_position(i); handle_points.push_back(p); - // push Out points first so they get selected if the In and Out points are on top of each other. + // Push out points first so they get selected if the In and Out points are on top of each other. if (i < c->get_point_count() - 1) { v3p.push_back(p); v3p.push_back(p + c->get_point_out(i)); diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index 7058b28e68..40557f9b8e 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -36,7 +36,7 @@ #include "editor/editor_undo_redo_manager.h" #include "scene/gui/separator.h" -void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index) { +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list) { if (p_mouse_button_index != MouseButton::RIGHT) { return; } diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h index e2363eb809..09c8068336 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.h +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -61,7 +61,7 @@ private: EditorPropertyInteger *alternative_to_property_editor = nullptr; PopupMenu *popup_menu = nullptr; - void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index); + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list); void _menu_id_pressed(int p_id); void _delete_selected_bindings(); void _update_lists(); diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 86aa897c78..369a59c443 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -988,7 +988,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { metadata_dialog->set_title(TTR("Create Version Control Metadata")); metadata_dialog->set_min_size(Size2(200, 40)); metadata_dialog->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); - version_control_actions->add_child(metadata_dialog); + add_child(metadata_dialog); VBoxContainer *metadata_vb = memnew(VBoxContainer); metadata_dialog->add_child(metadata_vb); @@ -1017,7 +1017,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { set_up_dialog->set_min_size(Size2(600, 100)); set_up_dialog->add_cancel_button("Cancel"); set_up_dialog->set_hide_on_ok(true); - version_control_actions->add_child(set_up_dialog); + add_child(set_up_dialog); Button *set_up_apply_button = set_up_dialog->get_ok_button(); set_up_apply_button->set_text(TTR("Apply")); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 28111bed58..1e917e6b3d 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -274,10 +274,12 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("s3tc"); presets.insert("etc"); presets.insert("etc2"); + presets.insert("editor"); + presets.insert("template_debug"); + presets.insert("template_release"); presets.insert("debug"); presets.insert("release"); - presets.insert("editor"); - presets.insert("standalone"); + presets.insert("template"); presets.insert("32"); presets.insert("64"); presets.insert("movie"); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 24241b712b..ea93e1ebfc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -920,7 +924,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -1131,7 +1135,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 45ca4fe342..cba585e5a5 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -115,6 +115,7 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c02ee99a86..6d5936ad2e 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2512,50 +2512,62 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { - if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && p_context.base != nullptr) { - const GDScriptParser::GetNodeNode *get_node = nullptr; - const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; + + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; - switch (identifier_node->source) { - case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { - if (p_context.current_class != nullptr) { - const StringName &member_name = identifier_node->name; - const GDScriptParser::ClassNode *current_class = p_context.current_class; + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); - if (current_class->has_member(member_name)) { - const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; - if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { - const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); - if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } } } } - } - } break; - case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { - if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); - } - } break; - default: - break; - } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } - if (get_node != nullptr) { - const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); - if (node != nullptr) { - if (r_base != nullptr) { - *r_base = node; - } - r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - r_base_type.kind = GDScriptParser::DataType::NATIVE; - r_base_type.native_type = node->get_class_name(); - r_base_type.builtin_type = Variant::OBJECT; - return true; + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; } } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..e442bf8159 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..f33ba7dffd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 59d3865acd..71e1d9c351 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -796,7 +796,7 @@ bool OpenXRAPI::create_swapchains() { depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; - depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = recommended_size.width; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index a5e3e60775..ec6947e180 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -3947,7 +3947,6 @@ void DisplayServerX11::process_events() { mb->set_window_id(window_id_other); mb->set_position(Vector2(x, y)); mb->set_global_position(mb->get_position()); - Input::get_singleton()->parse_input_event(mb); } break; } diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index b68a8fb031..3876ab128c 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -106,18 +106,57 @@ void Path2D::_notification(int p_what) { #else const real_t line_width = get_tree()->get_debug_paths_width(); #endif - _cached_draw_pts.resize(curve->get_point_count() * 8); - int count = 0; - - for (int i = 0; i < curve->get_point_count(); i++) { - for (int j = 0; j < 8; j++) { - real_t frac = j * (1.0 / 8.0); - Vector2 p = curve->sample(i, frac); - _cached_draw_pts.set(count++, p); + real_t interval = 10; + const real_t length = curve->get_baked_length(); + + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform2D> frames; + frames.resize(sample_count); + + { + Transform2D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = curve->sample_baked_with_rotation(i * interval, true); + } } - } - draw_polyline(_cached_draw_pts, get_tree()->get_debug_paths_color(), line_width, true); + const Transform2D *r = frames.ptr(); + // Draw curve segments + { + PackedVector2Array v2p; + v2p.resize(sample_count); + Vector2 *w = v2p.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = r[i].get_origin(); + } + draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width, false); + } + + // Draw fish bones + { + PackedVector2Array v2p; + v2p.resize(3); + Vector2 *w = v2p.ptrw(); + + for (int i = 0; i < sample_count; i++) { + const Vector2 p = r[i].get_origin(); + const Vector2 side = r[i].columns[0]; + const Vector2 forward = r[i].columns[1]; + + // Fish Bone. + w[0] = p + (side - forward) * 5; + w[1] = p; + w[2] = p + (-side - forward) * 5; + + draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width * 0.5, false); + } + } + } } break; } } diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 5e436fb9f6..935717605a 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -38,7 +38,6 @@ class Path2D : public Node2D { GDCLASS(Path2D, Node2D); Ref<Curve2D> curve; - Vector<Vector2> _cached_draw_pts; void _curve_changed(); diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 198dba7811..3f43e718b8 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -461,7 +461,7 @@ Light3D::Light3D(RenderingServer::LightType p_type) { set_param(PARAM_SHADOW_PANCAKE_SIZE, 20.0); set_param(PARAM_SHADOW_OPACITY, 1.0); set_param(PARAM_SHADOW_BLUR, 1.0); - set_param(PARAM_SHADOW_BIAS, 0.03); + set_param(PARAM_SHADOW_BIAS, 0.1); set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_param(PARAM_TRANSMITTANCE_BIAS, 0.05); set_param(PARAM_SHADOW_FADE_START, 1); @@ -571,8 +571,8 @@ DirectionalLight3D::DirectionalLight3D() : Light3D(RenderingServer::LIGHT_DIRECTIONAL) { set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); - // Increase the default shadow bias to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); + // Increase the default shadow normal bias to better suit most scenes. + set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); set_param(PARAM_INTENSITY, 100000.0); // Specified in Lux, approximate mid-day sun. set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; @@ -614,8 +614,6 @@ void OmniLight3D::_bind_methods() { OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); - // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.2); } PackedStringArray SpotLight3D::get_configuration_warnings() const { @@ -639,3 +637,9 @@ void SpotLight3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.01,degrees"), "set_param", "get_param", PARAM_SPOT_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } + +SpotLight3D::SpotLight3D() : + Light3D(RenderingServer::LIGHT_SPOT) { + // Decrease the default shadow bias to better suit most scenes. + set_param(PARAM_SHADOW_BIAS, 0.03); +} diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index 84d214030b..d2dfa32aac 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -233,8 +233,7 @@ protected: public: PackedStringArray get_configuration_warnings() const override; - SpotLight3D() : - Light3D(RenderingServer::LIGHT_SPOT) {} + SpotLight3D(); }; #endif // LIGHT_3D_H diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 35ba49563c..28eedcbe13 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -220,8 +220,12 @@ void SceneDebugger::_save_node(ObjectID id, const String &p_path) { Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id)); ERR_FAIL_COND(!node); +#ifdef TOOLS_ENABLED HashMap<const Node *, Node *> duplimap; Node *copy = node->duplicate_from_editor(duplimap); +#else + Node *copy = node->duplicate(); +#endif // Handle Unique Nodes. for (int i = 0; i < copy->get_child_count(false); i++) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e7d704a281..a96d85dffa 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1108,8 +1108,9 @@ void TextEdit::_notification(int p_what) { int start = TS->shaped_text_get_range(rid).x; if (!clipped && !search_text.is_empty()) { // Search highhlight int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1125,14 +1126,15 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, search_result_border_color, false); } - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + 1); + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + search_text_len); } } if (!clipped && highlight_all_occurrences && !only_whitespaces_highlighted && !highlighted_text.is_empty()) { // Highlight int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1147,15 +1149,16 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, word_highlighted_color); } - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + 1); + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); } } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { - int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); - while (highlighted_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); + int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + int lookup_symbol_word_len = lookup_symbol_word.length(); + while (lookup_symbol_word_col != -1) { + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1172,7 +1175,7 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, color); } - highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); + lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); } } } diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index e457b2d377..a16d2c2072 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1252,15 +1252,16 @@ void BaseMaterial3D::_update_shader() { } if (distance_fade != DISTANCE_FADE_DISABLED) { + // Use the slightly more expensive circular fade (distance to the object) instead of linear + // (Z distance), so that the fade is always the same regardless of the camera angle. if ((distance_fade == DISTANCE_FADE_OBJECT_DITHER || distance_fade == DISTANCE_FADE_PIXEL_DITHER)) { if (!RenderingServer::get_singleton()->is_low_end()) { code += " {\n"; if (distance_fade == DISTANCE_FADE_OBJECT_DITHER) { - code += " float fade_distance = abs((VIEW_MATRIX * MODEL_MATRIX[3]).z);\n"; - + code += " float fade_distance = length((VIEW_MATRIX * MODEL_MATRIX[3]));\n"; } else { - code += " float fade_distance = -VERTEX.z;\n"; + code += " float fade_distance = length(VERTEX);\n"; } // Use interleaved gradient noise, which is fast but still looks good. code += " const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);"; @@ -1274,7 +1275,7 @@ void BaseMaterial3D::_update_shader() { } } else { - code += " ALPHA*=clamp(smoothstep(distance_fade_min,distance_fade_max,-VERTEX.z),0.0,1.0);\n"; + code += " ALPHA *= clamp(smoothstep(distance_fade_min, distance_fade_max, length(VERTEX)), 0.0, 1.0);\n"; } } diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 075a21b818..33f1f38bf5 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -1075,18 +1075,36 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vector<Point void RendererCanvasCull::canvas_item_add_multiline(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width) { ERR_FAIL_COND(p_points.size() < 2); - Item *canvas_item = canvas_item_owner.get_or_null(p_item); - ERR_FAIL_COND(!canvas_item); - - Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>(); - ERR_FAIL_COND(!pline); - if (true || p_width <= 1) { -#define TODO make thick lines possible + // TODO: `canvas_item_add_line`(`multiline`, `polyline`) share logic, should factor out. + if (p_width <= 1) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + ERR_FAIL_COND(!canvas_item); + Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>(); + ERR_FAIL_COND(!pline); pline->primitive = RS::PRIMITIVE_LINES; pline->polygon.create(Vector<int>(), p_points, p_colors); } else { + if (p_colors.size() == 1) { + Color color = p_colors[0]; + for (int i = 0; i < p_points.size() >> 1; i++) { + Vector2 from = p_points[i * 2 + 0]; + Vector2 to = p_points[i * 2 + 1]; + + canvas_item_add_line(p_item, from, to, color, p_width); + } + } else if (p_colors.size() == p_points.size() >> 1) { + for (int i = 0; i < p_points.size() >> 1; i++) { + Color color = p_colors[i]; + Vector2 from = p_points[i * 2 + 0]; + Vector2 to = p_points[i * 2 + 1]; + + canvas_item_add_line(p_item, from, to, color, p_width); + } + } else { + ERR_FAIL_MSG("Length of p_colors is invalid."); + } } } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 71f4f3ad11..503a25184e 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -445,7 +445,14 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { const AABB &bone = p_surface.bone_aabbs[i]; if (bone.has_volume()) { - mesh->bone_aabbs.write[i].merge_with(bone); + AABB &mesh_bone = mesh->bone_aabbs.write[i]; + if (mesh_bone != AABB()) { + // Already initialized, merge AABBs. + mesh_bone.merge_with(bone); + } else { + // Not yet initialized, copy the bone AABB. + mesh_bone = bone; + } } } mesh->aabb.merge_with(p_surface.aabb); diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h index c46da5e156..086f0b9b0c 100644 --- a/tests/core/os/test_os.h +++ b/tests/core/os/test_os.h @@ -97,14 +97,14 @@ TEST_CASE("[OS] Feature tags") { OS::get_singleton()->has_feature("editor"), "The binary has the \"editor\" feature tag."); CHECK_MESSAGE( - !OS::get_singleton()->has_feature("standalone"), - "The binary does not have the \"standalone\" feature tag."); + !OS::get_singleton()->has_feature("template"), + "The binary does not have the \"template\" feature tag."); CHECK_MESSAGE( - OS::get_singleton()->has_feature("debug"), - "The binary has the \"debug\" feature tag."); + !OS::get_singleton()->has_feature("template_debug"), + "The binary does not have the \"template_debug\" feature tag."); CHECK_MESSAGE( - !OS::get_singleton()->has_feature("release"), - "The binary does not have the \"release\" feature tag."); + !OS::get_singleton()->has_feature("template_release"), + "The binary does not have the \"template_release\" feature tag."); } TEST_CASE("[OS] Process ID") { |