diff options
53 files changed, 1221 insertions, 187 deletions
diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml index 395dfdd7f5..00c79e8ba0 100644 --- a/.github/workflows/javascript_builds.yml +++ b/.github/workflows/javascript_builds.yml @@ -6,7 +6,7 @@ env: # Only used for the cache key. Increment version to force clean build. GODOT_BASE_BRANCH: master SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no - EM_VERSION: 2.0.27 + EM_VERSION: 3.1.10 EM_CACHE_FOLDER: "emsdk-cache" concurrency: diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index db1cbd53b4..5738b42049 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -426,7 +426,7 @@ Ref<Resource> NativeExtensionResourceLoader::load(const String &p_path, const St return Ref<Resource>(); } - if (!library_path.is_resource_file()) { + if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { library_path = p_path.get_base_dir().plus_file(library_path); } diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index fbb4293961..3c854bbbe5 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -635,8 +635,6 @@ Error ResourceLoaderBinary::load() { return error; } - int stage = 0; - for (int i = 0; i < external_resources.size(); i++) { String path = external_resources[i].path; @@ -674,8 +672,6 @@ Error ResourceLoaderBinary::load() { } } } - - stage++; } for (int i = 0; i < internal_resources.size(); i++) { @@ -700,7 +696,6 @@ Error ResourceLoaderBinary::load() { Ref<Resource> cached = ResourceCache::get(path); if (cached.is_valid()) { //already loaded, don't do anything - stage++; error = OK; internal_index_cache[path] = cached; continue; @@ -817,7 +812,6 @@ Error ResourceLoaderBinary::load() { #ifdef TOOLS_ENABLED res->set_edited(false); #endif - stage++; if (progress) { *progress = (i + 1) / float(internal_resources.size()); diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h index 7ad5f76645..f8a10ec87e 100644 --- a/core/math/delaunay_3d.h +++ b/core/math/delaunay_3d.h @@ -323,7 +323,6 @@ public: E = N; } - uint32_t good_triangles = 0; for (uint32_t j = 0; j < triangles.size(); j++) { if (triangles[j].bad) { continue; @@ -360,11 +359,8 @@ public: } } } - - good_triangles++; } - //print_line("at point " + itos(i) + "/" + itos(point_count) + " simplices added " + itos(good_triangles) + "/" + itos(simplex_list.size()) + " - triangles: " + itos(triangles.size())); triangles.clear(); triangles_inserted.clear(); } diff --git a/core/math/quick_hull.cpp b/core/math/quick_hull.cpp index 8e87d44b7f..3614bfadf8 100644 --- a/core/math/quick_hull.cpp +++ b/core/math/quick_hull.cpp @@ -384,7 +384,6 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry3D::MeshData &r_ if (O->get().plane.is_equal_approx(f.plane)) { //merge and delete edge and contiguous face, while repointing edges (uuugh!) int ois = O->get().indices.size(); - int merged = 0; for (int j = 0; j < ois; j++) { //search a @@ -399,7 +398,6 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry3D::MeshData &r_ if (idx != a) { f.indices.insert(i + 1, idx); i++; - merged++; } Edge e2(idx, idxn); diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h index f1ac32928f..e760fc2176 100644 --- a/core/templates/cowdata.h +++ b/core/templates/cowdata.h @@ -183,6 +183,8 @@ public: } int find(const T &p_val, int p_from = 0) const; + int rfind(const T &p_val, int p_from = -1) const; + int count(const T &p_val) const; _FORCE_INLINE_ CowData() {} _FORCE_INLINE_ ~CowData(); @@ -350,6 +352,36 @@ int CowData<T>::find(const T &p_val, int p_from) const { } template <class T> +int CowData<T>::rfind(const T &p_val, int p_from) const { + const int s = size(); + + if (p_from < 0) { + p_from = s + p_from; + } + if (p_from < 0 || p_from >= s) { + p_from = s - 1; + } + + for (int i = p_from; i >= 0; i--) { + if (get(i) == p_val) { + return i; + } + } + return -1; +} + +template <class T> +int CowData<T>::count(const T &p_val) const { + int amount = 0; + for (int i = 0; i < size(); i++) { + if (get(i) == p_val) { + amount++; + } + } + return amount; +} + +template <class T> void CowData<T>::_ref(const CowData *p_from) { _ref(*p_from); } diff --git a/core/templates/vector.h b/core/templates/vector.h index d87e76139b..2ac7c7630a 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -92,6 +92,8 @@ public: _FORCE_INLINE_ const T &operator[](int p_index) const { return _cowdata.get(p_index); } Error insert(int p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); } int find(const T &p_val, int p_from = 0) const { return _cowdata.find(p_val, p_from); } + int rfind(const T &p_val, int p_from = -1) const { return _cowdata.rfind(p_val, p_from); } + int count(const T &p_val) const { return _cowdata.count(p_val); } void append_array(Vector<T> p_other); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index f4b2af5a94..882a89b8ba 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1873,6 +1873,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedByteArray, sort, sarray(), varray()); bind_method(PackedByteArray, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedByteArray, duplicate, sarray(), varray()); + bind_method(PackedByteArray, find, sarray("value", "from"), varray(0)); + bind_method(PackedByteArray, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedByteArray, count, sarray("value"), varray()); bind_function(PackedByteArray, get_string_from_ascii, _VariantCall::func_PackedByteArray_get_string_from_ascii, sarray(), varray()); bind_function(PackedByteArray, get_string_from_utf8, _VariantCall::func_PackedByteArray_get_string_from_utf8, sarray(), varray()); @@ -1935,6 +1938,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedInt32Array, sort, sarray(), varray()); bind_method(PackedInt32Array, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedInt32Array, duplicate, sarray(), varray()); + bind_method(PackedInt32Array, find, sarray("value", "from"), varray(0)); + bind_method(PackedInt32Array, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedInt32Array, count, sarray("value"), varray()); /* Int64 Array */ @@ -1955,6 +1961,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedInt64Array, sort, sarray(), varray()); bind_method(PackedInt64Array, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedInt64Array, duplicate, sarray(), varray()); + bind_method(PackedInt64Array, find, sarray("value", "from"), varray(0)); + bind_method(PackedInt64Array, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedInt64Array, count, sarray("value"), varray()); /* Float32 Array */ @@ -1975,6 +1984,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedFloat32Array, sort, sarray(), varray()); bind_method(PackedFloat32Array, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedFloat32Array, duplicate, sarray(), varray()); + bind_method(PackedFloat32Array, find, sarray("value", "from"), varray(0)); + bind_method(PackedFloat32Array, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedFloat32Array, count, sarray("value"), varray()); /* Float64 Array */ @@ -1995,6 +2007,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedFloat64Array, sort, sarray(), varray()); bind_method(PackedFloat64Array, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedFloat64Array, duplicate, sarray(), varray()); + bind_method(PackedFloat64Array, find, sarray("value", "from"), varray(0)); + bind_method(PackedFloat64Array, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedFloat64Array, count, sarray("value"), varray()); /* String Array */ @@ -2015,6 +2030,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedStringArray, sort, sarray(), varray()); bind_method(PackedStringArray, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedStringArray, duplicate, sarray(), varray()); + bind_method(PackedStringArray, find, sarray("value", "from"), varray(0)); + bind_method(PackedStringArray, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedStringArray, count, sarray("value"), varray()); /* Vector2 Array */ @@ -2035,6 +2053,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedVector2Array, sort, sarray(), varray()); bind_method(PackedVector2Array, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedVector2Array, duplicate, sarray(), varray()); + bind_method(PackedVector2Array, find, sarray("value", "from"), varray(0)); + bind_method(PackedVector2Array, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedVector2Array, count, sarray("value"), varray()); /* Vector3 Array */ @@ -2055,6 +2076,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedVector3Array, sort, sarray(), varray()); bind_method(PackedVector3Array, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedVector3Array, duplicate, sarray(), varray()); + bind_method(PackedVector3Array, find, sarray("value", "from"), varray(0)); + bind_method(PackedVector3Array, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedVector3Array, count, sarray("value"), varray()); /* Color Array */ @@ -2075,6 +2099,9 @@ static void _register_variant_builtin_methods() { bind_method(PackedColorArray, sort, sarray(), varray()); bind_method(PackedColorArray, bsearch, sarray("value", "before"), varray(true)); bind_method(PackedColorArray, duplicate, sarray(), varray()); + bind_method(PackedColorArray, find, sarray("value", "from"), varray(0)); + bind_method(PackedColorArray, rfind, sarray("value", "from"), varray(-1)); + bind_method(PackedColorArray, count, sarray("value"), varray()); /* Register constants */ diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index 61dc1eef00..5d0861bcf3 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -61,6 +61,13 @@ Returns a new [PackedByteArray] with the data compressed. Set the compression mode using one of [enum File.CompressionMode]'s constants. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="decode_double" qualifiers="const"> <return type="float" /> <argument index="0" name="byte_offset" type="int" /> @@ -257,6 +264,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="get_string_from_ascii" qualifiers="const"> <return type="String" /> <description> @@ -352,6 +367,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml index f880771c77..12a553af49 100644 --- a/doc/classes/PackedColorArray.xml +++ b/doc/classes/PackedColorArray.xml @@ -54,6 +54,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Color" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedColorArray" /> <description> @@ -67,6 +74,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Color" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Color" /> @@ -115,6 +130,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Color" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index e2b877ad5f..0a114e6c06 100644 --- a/doc/classes/PackedFloat32Array.xml +++ b/doc/classes/PackedFloat32Array.xml @@ -55,6 +55,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="float" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedFloat32Array" /> <description> @@ -68,6 +75,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="float" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="float" /> @@ -116,6 +131,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="float" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml index be7a52b7f4..0327559f5b 100644 --- a/doc/classes/PackedFloat64Array.xml +++ b/doc/classes/PackedFloat64Array.xml @@ -55,6 +55,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="float" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedFloat64Array" /> <description> @@ -68,6 +75,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="float" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="float" /> @@ -116,6 +131,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="float" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml index 108273d859..f8b606d266 100644 --- a/doc/classes/PackedInt32Array.xml +++ b/doc/classes/PackedInt32Array.xml @@ -55,6 +55,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedInt32Array" /> <description> @@ -68,6 +75,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> @@ -116,6 +131,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml index c34f2fc75e..ea3e304d35 100644 --- a/doc/classes/PackedInt64Array.xml +++ b/doc/classes/PackedInt64Array.xml @@ -55,6 +55,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedInt64Array" /> <description> @@ -68,6 +75,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="int" /> @@ -116,6 +131,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="int" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml index 536f5f02eb..a4653344f0 100644 --- a/doc/classes/PackedStringArray.xml +++ b/doc/classes/PackedStringArray.xml @@ -5,6 +5,12 @@ </brief_description> <description> An array specifically designed to hold [String]s. Packs data tightly, so it saves memory for large array sizes. + If you want to join the strings in the array, use [method String.join]. + [codeblock] + var string_array = PackedStringArray(["hello", "world"]) + var string = " ".join(string_array) + print(string) # "hello world" + [/codeblock] </description> <tutorials> <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> @@ -55,6 +61,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="String" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedStringArray" /> <description> @@ -68,6 +81,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="String" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="String" /> @@ -116,6 +137,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="String" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index 29423d1cde..8f3e5d173d 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -55,6 +55,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Vector2" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedVector2Array" /> <description> @@ -68,6 +75,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Vector2" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Vector2" /> @@ -116,6 +131,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Vector2" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml index 6d460cecab..1207293c32 100644 --- a/doc/classes/PackedVector3Array.xml +++ b/doc/classes/PackedVector3Array.xml @@ -54,6 +54,13 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> + <method name="count" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Vector3" /> + <description> + Returns the number of times an element is in the array. + </description> + </method> <method name="duplicate"> <return type="PackedVector3Array" /> <description> @@ -67,6 +74,14 @@ Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements. </description> </method> + <method name="find" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Vector3" /> + <argument index="1" name="from" type="int" default="0" /> + <description> + Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <argument index="0" name="value" type="Vector3" /> @@ -115,6 +130,14 @@ Reverses the order of the elements in the array. </description> </method> + <method name="rfind" qualifiers="const"> + <return type="int" /> + <argument index="0" name="value" type="Vector3" /> + <argument index="1" name="from" type="int" default="-1" /> + <description> + Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array. + </description> + </method> <method name="set"> <return type="void" /> <argument index="0" name="index" type="int" /> diff --git a/doc/classes/ProgressBar.xml b/doc/classes/ProgressBar.xml index 1e9ab7c375..8a781c51fb 100644 --- a/doc/classes/ProgressBar.xml +++ b/doc/classes/ProgressBar.xml @@ -9,10 +9,27 @@ <tutorials> </tutorials> <members> + <member name="fill_mode" type="int" setter="set_fill_mode" getter="get_fill_mode" default="0"> + The fill direction. See [enum FillMode] for possible values. + </member> <member name="percent_visible" type="bool" setter="set_percent_visible" getter="is_percent_visible" default="true"> If [code]true[/code], the fill percentage is displayed on the bar. </member> </members> + <constants> + <constant name="FILL_BEGIN_TO_END" value="0" enum="FillMode"> + The progress bar fills from begin to end horizontally, according to the language direction. If [method Control.is_layout_rtl] returns [code]false[/code], it fills from left to right, and if it returns [code]true[/code], it fills from right to left. + </constant> + <constant name="FILL_END_TO_BEGIN" value="1" enum="FillMode"> + The progress bar fills from end to begin horizontally, according to the language direction. If [method Control.is_layout_rtl] returns [code]false[/code], it fills from right to left, and if it returns [code]true[/code], it fills from left to right. + </constant> + <constant name="FILL_TOP_TO_BOTTOM" value="2" enum="FillMode"> + The progress fills from top to bottom. + </constant> + <constant name="FILL_BOTTOM_TO_TOP" value="3" enum="FillMode"> + The progress fills from bottom to top. + </constant> + </constants> <theme_items> <theme_item name="font_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> The color of the text. diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 970f5125cd..3cd2ab0903 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -18,9 +18,9 @@ <argument index="0" name="group" type="StringName" /> <argument index="1" name="method" type="StringName" /> <description> - Calls [code]method[/code] on each member of the given group. You can pass arguments to [code]method[/code] by specifying them at the end of the method call. This method is equivalent of calling [method call_group_flags] with [constant GROUP_CALL_DEFAULT] flag. + Calls [code]method[/code] on each member of the given group. You can pass arguments to [code]method[/code] by specifying them at the end of the method call. [b]Note:[/b] Due to design limitations, [method call_group] will fail silently if one of the arguments is [code]null[/code]. - [b]Note:[/b] [method call_group] will always call methods with an one-frame delay, in a way similar to [method Object.call_deferred]. To call methods immediately, use [method call_group_flags] with the [constant GROUP_CALL_REALTIME] flag. + [b]Note:[/b] [method call_group] will call methods immediately on all members at once, which can cause stuttering if an expensive method is called on lots of members. To wait for one frame after [method call_group] was called, use [method call_group_flags] with the [constant GROUP_CALL_DEFERRED] flag. </description> </method> <method name="call_group_flags" qualifiers="vararg"> @@ -30,11 +30,12 @@ <argument index="2" name="method" type="StringName" /> <description> Calls [code]method[/code] on each member of the given group, respecting the given [enum GroupCallFlags]. You can pass arguments to [code]method[/code] by specifying them at the end of the method call. - [b]Note:[/b] Due to design limitations, [method call_group_flags] will fail silently if one of the arguments is [code]null[/code]. [codeblock] - # Call the method immediately and in reverse order. - get_tree().call_group_flags(SceneTree.GROUP_CALL_REALTIME | SceneTree.GROUP_CALL_REVERSE, "bases", "destroy") + # Call the method in a deferred manner and in reverse order. + get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFERRED | SceneTree.GROUP_CALL_REVERSE) [/codeblock] + [b]Note:[/b] Due to design limitations, [method call_group_flags] will fail silently if one of the arguments is [code]null[/code]. + [b]Note:[/b] Group call flags are used to control the method calling behavior. By default, methods will be called immediately in a way similar to [method call_group]. However, if the [constant GROUP_CALL_DEFERRED] flag is present in the [code]flags[/code] argument, methods will be called with a one-frame delay in a way similar to [method Object.set_deferred]. </description> </method> <method name="change_scene"> @@ -139,6 +140,7 @@ <argument index="1" name="notification" type="int" /> <description> Sends the given notification to all members of the [code]group[/code]. + [b]Note:[/b] [method notify_group] will immediately notify all members at once, which can cause stuttering if an expensive method is called as a result of sending the notification lots of members. To wait for one frame, use [method notify_group_flags] with the [constant GROUP_CALL_DEFERRED] flag. </description> </method> <method name="notify_group_flags"> @@ -148,6 +150,7 @@ <argument index="2" name="notification" type="int" /> <description> Sends the given notification to all members of the [code]group[/code], respecting the given [enum GroupCallFlags]. + [b]Note:[/b] Group call flags are used to control the notification sending behavior. By default, notifications will be sent immediately in a way similar to [method notify_group]. However, if the [constant GROUP_CALL_DEFERRED] flag is present in the [code]flags[/code] argument, notifications will be sent with a one-frame delay in a way similar to using [code]Object.call_deferred("notification", ...)[/code]. </description> </method> <method name="queue_delete"> @@ -189,6 +192,7 @@ <argument index="2" name="value" type="Variant" /> <description> Sets the given [code]property[/code] to [code]value[/code] on all members of the given group. + [b]Note:[/b] [method set_group] will set the property immediately on all members at once, which can cause stuttering if a property with an expensive setter is set on lots of members. To wait for one frame, use [method set_group_flags] with the [constant GROUP_CALL_DEFERRED] flag. </description> </method> <method name="set_group_flags"> @@ -199,6 +203,7 @@ <argument index="3" name="value" type="Variant" /> <description> Sets the given [code]property[/code] to [code]value[/code] on all members of the given group, respecting the given [enum GroupCallFlags]. + [b]Note:[/b] Group call flags are used to control the property setting behavior. By default, properties will be set immediately in a way similar to [method set_group]. However, if the [constant GROUP_CALL_DEFERRED] flag is present in the [code]flags[/code] argument, properties will be set with a one-frame delay in a way similar to [method Object.call_deferred]. </description> </method> <method name="set_multiplayer"> @@ -297,8 +302,8 @@ <constant name="GROUP_CALL_REVERSE" value="1" enum="GroupCallFlags"> Call a group in reverse scene order. </constant> - <constant name="GROUP_CALL_REALTIME" value="2" enum="GroupCallFlags"> - Call a group immediately (calls are normally made on idle). + <constant name="GROUP_CALL_DEFERRED" value="2" enum="GroupCallFlags"> + Call a group with a one-frame delay (idle frame, not physics). </constant> <constant name="GROUP_CALL_UNIQUE" value="4" enum="GroupCallFlags"> Call a group only once even if the call is executed many times. diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index 7395611d71..954aa11c0d 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -32,6 +32,7 @@ #include "light_storage.h" #include "config.h" +#include "texture_storage.h" using namespace GLES3; @@ -51,122 +52,284 @@ LightStorage::~LightStorage() { /* Light API */ +void LightStorage::_light_initialize(RID p_light, RS::LightType p_type) { + Light light; + light.type = p_type; + + light.param[RS::LIGHT_PARAM_ENERGY] = 1.0; + light.param[RS::LIGHT_PARAM_INDIRECT_ENERGY] = 1.0; + light.param[RS::LIGHT_PARAM_SPECULAR] = 0.5; + light.param[RS::LIGHT_PARAM_RANGE] = 1.0; + light.param[RS::LIGHT_PARAM_SIZE] = 0.0; + light.param[RS::LIGHT_PARAM_ATTENUATION] = 1.0; + light.param[RS::LIGHT_PARAM_SPOT_ANGLE] = 45; + light.param[RS::LIGHT_PARAM_SPOT_ATTENUATION] = 1.0; + light.param[RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE] = 0; + light.param[RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET] = 0.1; + light.param[RS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET] = 0.3; + light.param[RS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET] = 0.6; + light.param[RS::LIGHT_PARAM_SHADOW_FADE_START] = 0.8; + light.param[RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] = 1.0; + light.param[RS::LIGHT_PARAM_SHADOW_BIAS] = 0.02; + light.param[RS::LIGHT_PARAM_SHADOW_BLUR] = 0; + light.param[RS::LIGHT_PARAM_SHADOW_PANCAKE_SIZE] = 20.0; + light.param[RS::LIGHT_PARAM_SHADOW_VOLUMETRIC_FOG_FADE] = 0.1; + light.param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] = 0.05; + + light_owner.initialize_rid(p_light, light); +} + RID LightStorage::directional_light_allocate() { - return RID(); + return light_owner.allocate_rid(); } void LightStorage::directional_light_initialize(RID p_rid) { + _light_initialize(p_rid, RS::LIGHT_DIRECTIONAL); } RID LightStorage::omni_light_allocate() { - return RID(); + return light_owner.allocate_rid(); } void LightStorage::omni_light_initialize(RID p_rid) { + _light_initialize(p_rid, RS::LIGHT_OMNI); } RID LightStorage::spot_light_allocate() { - return RID(); + return light_owner.allocate_rid(); } void LightStorage::spot_light_initialize(RID p_rid) { + _light_initialize(p_rid, RS::LIGHT_SPOT); } void LightStorage::light_free(RID p_rid) { + light_set_projector(p_rid, RID()); //clear projector + + // delete the texture + Light *light = light_owner.get_or_null(p_rid); + light->dependency.deleted_notify(p_rid); + light_owner.free(p_rid); } void LightStorage::light_set_color(RID p_light, const Color &p_color) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->color = p_color; } void LightStorage::light_set_param(RID p_light, RS::LightParam p_param, float p_value) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + ERR_FAIL_INDEX(p_param, RS::LIGHT_PARAM_MAX); + + if (light->param[p_param] == p_value) { + return; + } + + switch (p_param) { + case RS::LIGHT_PARAM_RANGE: + case RS::LIGHT_PARAM_SPOT_ANGLE: + case RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE: + case RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET: + case RS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET: + case RS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET: + case RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS: + case RS::LIGHT_PARAM_SHADOW_PANCAKE_SIZE: + case RS::LIGHT_PARAM_SHADOW_BIAS: { + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); + } break; + case RS::LIGHT_PARAM_SIZE: { + if ((light->param[p_param] > CMP_EPSILON) != (p_value > CMP_EPSILON)) { + //changing from no size to size and the opposite + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR); + } + } break; + default: { + } + } + + light->param[p_param] = p_value; } void LightStorage::light_set_shadow(RID p_light, bool p_enabled) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + light->shadow = p_enabled; + + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } void LightStorage::light_set_projector(RID p_light, RID p_texture) { + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + if (light->projector == p_texture) { + return; + } + + if (light->type != RS::LIGHT_DIRECTIONAL && light->projector.is_valid()) { + texture_storage->texture_remove_from_decal_atlas(light->projector, light->type == RS::LIGHT_OMNI); + } + + light->projector = p_texture; + + if (light->type != RS::LIGHT_DIRECTIONAL) { + if (light->projector.is_valid()) { + texture_storage->texture_add_to_decal_atlas(light->projector, light->type == RS::LIGHT_OMNI); + } + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR); + } } void LightStorage::light_set_negative(RID p_light, bool p_enable) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->negative = p_enable; } void LightStorage::light_set_cull_mask(RID p_light, uint32_t p_mask) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->cull_mask = p_mask; + + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } void LightStorage::light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->distance_fade = p_enabled; + light->distance_fade_begin = p_begin; + light->distance_fade_shadow = p_shadow; + light->distance_fade_length = p_length; } void LightStorage::light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->reverse_cull = p_enabled; + + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } void LightStorage::light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) { -} + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->bake_mode = p_bake_mode; -void LightStorage::light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) { + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } void LightStorage::light_omni_set_shadow_mode(RID p_light, RS::LightOmniShadowMode p_mode) { -} + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); -void LightStorage::light_directional_set_shadow_mode(RID p_light, RS::LightDirectionalShadowMode p_mode) { -} + light->omni_shadow_mode = p_mode; -void LightStorage::light_directional_set_blend_splits(RID p_light, bool p_enable) { + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } -bool LightStorage::light_directional_get_blend_splits(RID p_light) const { - return false; -} +RS::LightOmniShadowMode LightStorage::light_omni_get_shadow_mode(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_OMNI_SHADOW_CUBE); -void LightStorage::light_directional_set_sky_mode(RID p_light, RS::LightDirectionalSkyMode p_mode) { + return light->omni_shadow_mode; } -RS::LightDirectionalSkyMode LightStorage::light_directional_get_sky_mode(RID p_light) const { - return RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_AND_SKY; -} +void LightStorage::light_directional_set_shadow_mode(RID p_light, RS::LightDirectionalShadowMode p_mode) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); -RS::LightDirectionalShadowMode LightStorage::light_directional_get_shadow_mode(RID p_light) { - return RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL; + light->directional_shadow_mode = p_mode; + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } -RS::LightOmniShadowMode LightStorage::light_omni_get_shadow_mode(RID p_light) { - return RS::LIGHT_OMNI_SHADOW_DUAL_PARABOLOID; -} +void LightStorage::light_directional_set_blend_splits(RID p_light, bool p_enable) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); -bool LightStorage::light_has_shadow(RID p_light) const { - return false; + light->directional_blend_splits = p_enable; + light->version++; + light->dependency.changed_notify(RendererStorage::DEPENDENCY_CHANGED_LIGHT); } -bool LightStorage::light_has_projector(RID p_light) const { - return false; -} +bool LightStorage::light_directional_get_blend_splits(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, false); -RS::LightType LightStorage::light_get_type(RID p_light) const { - return RS::LIGHT_OMNI; + return light->directional_blend_splits; } -AABB LightStorage::light_get_aabb(RID p_light) const { - return AABB(); +void LightStorage::light_directional_set_sky_mode(RID p_light, RS::LightDirectionalSkyMode p_mode) { + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND(!light); + + light->directional_sky_mode = p_mode; } -float LightStorage::light_get_param(RID p_light, RS::LightParam p_param) { - return 0.0; +RS::LightDirectionalSkyMode LightStorage::light_directional_get_sky_mode(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_AND_SKY); + + return light->directional_sky_mode; } -Color LightStorage::light_get_color(RID p_light) { - return Color(); +RS::LightDirectionalShadowMode LightStorage::light_directional_get_shadow_mode(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL); + + return light->directional_shadow_mode; } RS::LightBakeMode LightStorage::light_get_bake_mode(RID p_light) { - return RS::LIGHT_BAKE_DISABLED; -} + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_BAKE_DISABLED); -uint32_t LightStorage::light_get_max_sdfgi_cascade(RID p_light) { - return 0; + return light->bake_mode; } uint64_t LightStorage::light_get_version(RID p_light) const { - return 0; + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, 0); + + return light->version; +} + +AABB LightStorage::light_get_aabb(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, AABB()); + + switch (light->type) { + case RS::LIGHT_SPOT: { + float len = light->param[RS::LIGHT_PARAM_RANGE]; + float size = Math::tan(Math::deg2rad(light->param[RS::LIGHT_PARAM_SPOT_ANGLE])) * len; + return AABB(Vector3(-size, -size, -len), Vector3(size * 2, size * 2, len)); + }; + case RS::LIGHT_OMNI: { + float r = light->param[RS::LIGHT_PARAM_RANGE]; + return AABB(-Vector3(r, r, r), Vector3(r, r, r) * 2); + }; + case RS::LIGHT_DIRECTIONAL: { + return AABB(); + }; + } + + ERR_FAIL_V(AABB()); } /* PROBE API */ diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index 6f24e467bc..5acaf45aa3 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -40,12 +40,100 @@ #include "servers/rendering/renderer_storage.h" #include "servers/rendering/storage/light_storage.h" +#include "platform_config.h" +#ifndef OPENGL_INCLUDE_H +#include <GLES3/gl3.h> +#else +#include OPENGL_INCLUDE_H +#endif + namespace GLES3 { +/* LIGHT */ + +struct Light { + RS::LightType type; + float param[RS::LIGHT_PARAM_MAX]; + Color color = Color(1, 1, 1, 1); + RID projector; + bool shadow = false; + bool negative = false; + bool reverse_cull = false; + RS::LightBakeMode bake_mode = RS::LIGHT_BAKE_DYNAMIC; + uint32_t max_sdfgi_cascade = 2; + uint32_t cull_mask = 0xFFFFFFFF; + bool distance_fade = false; + real_t distance_fade_begin = 40.0; + real_t distance_fade_shadow = 50.0; + real_t distance_fade_length = 10.0; + RS::LightOmniShadowMode omni_shadow_mode = RS::LIGHT_OMNI_SHADOW_DUAL_PARABOLOID; + RS::LightDirectionalShadowMode directional_shadow_mode = RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL; + bool directional_blend_splits = false; + RS::LightDirectionalSkyMode directional_sky_mode = RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_AND_SKY; + uint64_t version = 0; + + RendererStorage::Dependency dependency; +}; + +/* REFLECTION PROBE */ + +struct ReflectionProbe { + RS::ReflectionProbeUpdateMode update_mode = RS::REFLECTION_PROBE_UPDATE_ONCE; + int resolution = 256; + float intensity = 1.0; + RS::ReflectionProbeAmbientMode ambient_mode = RS::REFLECTION_PROBE_AMBIENT_ENVIRONMENT; + Color ambient_color; + float ambient_color_energy = 1.0; + float max_distance = 0; + Vector3 extents = Vector3(1, 1, 1); + Vector3 origin_offset; + bool interior = false; + bool box_projection = false; + bool enable_shadows = false; + uint32_t cull_mask = (1 << 20) - 1; + float mesh_lod_threshold = 0.01; + + RendererStorage::Dependency dependency; +}; + +/* LIGHTMAP */ + +struct Lightmap { + RID light_texture; + bool uses_spherical_harmonics = false; + bool interior = false; + AABB bounds = AABB(Vector3(), Vector3(1, 1, 1)); + int32_t array_index = -1; //unassigned + PackedVector3Array points; + PackedColorArray point_sh; + PackedInt32Array tetrahedra; + PackedInt32Array bsp_tree; + + struct BSP { + static const int32_t EMPTY_LEAF = INT32_MIN; + float plane[4]; + int32_t over = EMPTY_LEAF, under = EMPTY_LEAF; + }; + + RendererStorage::Dependency dependency; +}; + class LightStorage : public RendererLightStorage { private: static LightStorage *singleton; + /* LIGHT */ + mutable RID_Owner<Light, true> light_owner; + + /* REFLECTION PROBE */ + mutable RID_Owner<ReflectionProbe, true> reflection_probe_owner; + + /* LIGHTMAP */ + + Vector<RID> lightmap_textures; + + mutable RID_Owner<Lightmap, true> lightmap_owner; + public: static LightStorage *get_singleton(); @@ -54,6 +142,11 @@ public: /* Light API */ + Light *get_light(RID p_rid) { return light_owner.get_or_null(p_rid); }; + bool owns_light(RID p_rid) { return light_owner.owns(p_rid); }; + + void _light_initialize(RID p_rid, RS::LightType p_type); + virtual RID directional_light_allocate() override; virtual void directional_light_initialize(RID p_rid) override; virtual RID omni_light_allocate() override; @@ -72,7 +165,7 @@ public: virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override; virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override; virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override; - virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override; + virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override {} virtual void light_omni_set_shadow_mode(RID p_light, RS::LightOmniShadowMode p_mode) override; @@ -84,16 +177,99 @@ public: virtual RS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light) override; virtual RS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light) override; + virtual RS::LightType light_get_type(RID p_light) const override { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL); - virtual bool light_has_shadow(RID p_light) const override; - virtual bool light_has_projector(RID p_light) const override; - - virtual RS::LightType light_get_type(RID p_light) const override; + return light->type; + } virtual AABB light_get_aabb(RID p_light) const override; - virtual float light_get_param(RID p_light, RS::LightParam p_param) override; - virtual Color light_get_color(RID p_light) override; + + virtual float light_get_param(RID p_light, RS::LightParam p_param) override { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, 0); + + return light->param[p_param]; + } + + _FORCE_INLINE_ RID light_get_projector(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RID()); + + return light->projector; + } + + virtual Color light_get_color(RID p_light) override { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, Color()); + + return light->color; + } + + _FORCE_INLINE_ uint32_t light_get_cull_mask(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, 0); + + return light->cull_mask; + } + + _FORCE_INLINE_ bool light_is_distance_fade_enabled(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + return light->distance_fade; + } + + _FORCE_INLINE_ float light_get_distance_fade_begin(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + return light->distance_fade_begin; + } + + _FORCE_INLINE_ float light_get_distance_fade_shadow(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + return light->distance_fade_shadow; + } + + _FORCE_INLINE_ float light_get_distance_fade_length(RID p_light) { + const Light *light = light_owner.get_or_null(p_light); + return light->distance_fade_length; + } + + virtual bool light_has_shadow(RID p_light) const override { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL); + + return light->shadow; + } + + virtual bool light_has_projector(RID p_light) const override { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL); + + return light_owner.owns(light->projector); + } + + _FORCE_INLINE_ bool light_is_negative(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL); + + return light->negative; + } + + _FORCE_INLINE_ float light_get_transmittance_bias(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, 0.0); + + return light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS]; + } + + _FORCE_INLINE_ float light_get_shadow_volumetric_fog_fade(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_COND_V(!light, 0.0); + + return light->param[RS::LIGHT_PARAM_SHADOW_VOLUMETRIC_FOG_FADE]; + } + virtual RS::LightBakeMode light_get_bake_mode(RID p_light) override; - virtual uint32_t light_get_max_sdfgi_cascade(RID p_light) override; + virtual uint32_t light_get_max_sdfgi_cascade(RID p_light) override { return 0; } virtual uint64_t light_get_version(RID p_light) const override; /* PROBE API */ diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 84d656fc20..13084e2aab 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -529,7 +529,7 @@ void EditorFileDialog::_multi_selected(int p_item, bool p_selected) { get_ok_button()->set_disabled(_is_open_should_be_disabled()); } -void EditorFileDialog::_items_clear_selection(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) { +void EditorFileDialog::_items_clear_selection(const Vector2 &p_pos, MouseButton p_mouse_button_index) { if (p_mouse_button_index != MouseButton::LEFT) { return; } diff --git a/editor/editor_file_dialog.h b/editor/editor_file_dialog.h index d9a58a5b7f..5f2e29b690 100644 --- a/editor/editor_file_dialog.h +++ b/editor/editor_file_dialog.h @@ -165,7 +165,7 @@ private: void _item_selected(int p_item); void _multi_selected(int p_item, bool p_selected); - void _items_clear_selection(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index); + void _items_clear_selection(const Vector2 &p_pos, MouseButton p_mouse_button_index); void _item_dc_selected(int p_item); void _item_list_item_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index); diff --git a/editor/editor_plugin_settings.cpp b/editor/editor_plugin_settings.cpp index b728ce64c9..85a906ef51 100644 --- a/editor/editor_plugin_settings.cpp +++ b/editor/editor_plugin_settings.cpp @@ -200,12 +200,9 @@ EditorPluginSettings::EditorPluginSettings() { l->set_theme_type_variation("HeaderSmall"); title_hb->add_child(l); title_hb->add_spacer(); - create_plugin = memnew(Button(TTR("Create"))); + Button *create_plugin = memnew(Button(TTR("Create New Plugin"))); create_plugin->connect("pressed", callable_mp(this, &EditorPluginSettings::_create_clicked)); title_hb->add_child(create_plugin); - update_list = memnew(Button(TTR("Update"))); - update_list->connect("pressed", callable_mp(this, &EditorPluginSettings::update_plugins)); - title_hb->add_child(update_list); add_child(title_hb); plugin_list = memnew(Tree); diff --git a/editor/editor_plugin_settings.h b/editor/editor_plugin_settings.h index 826bb8c00f..121534b613 100644 --- a/editor/editor_plugin_settings.h +++ b/editor/editor_plugin_settings.h @@ -45,8 +45,6 @@ class EditorPluginSettings : public VBoxContainer { }; PluginConfigDialog *plugin_config_dialog = nullptr; - Button *create_plugin = nullptr; - Button *update_list = nullptr; Tree *plugin_list = nullptr; bool updating = false; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 47c016803c..aae8cf25b6 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2637,7 +2637,7 @@ void FileSystemDock::_tree_empty_selected() { tree->deselect_all(); } -void FileSystemDock::_file_list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) { +void FileSystemDock::_file_list_item_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) { if (p_mouse_button_index != MouseButton::RIGHT) { return; } @@ -2665,7 +2665,11 @@ void FileSystemDock::_file_list_rmb_clicked(int p_item, const Vector2 &p_pos, Mo } } -void FileSystemDock::_file_list_rmb_pressed(const Vector2 &p_pos) { +void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) { + if (p_mouse_button_index != MouseButton::RIGHT) { + return; + } + // Right click on empty space for file list. if (searched_string.length() > 0) { return; @@ -3132,10 +3136,10 @@ FileSystemDock::FileSystemDock() { files->set_v_size_flags(SIZE_EXPAND_FILL); files->set_select_mode(ItemList::SELECT_MULTI); files->set_drag_forwarding(this); - files->connect("item_clicked", callable_mp(this, &FileSystemDock::_file_list_rmb_clicked)); + files->connect("item_clicked", callable_mp(this, &FileSystemDock::_file_list_item_clicked)); files->connect("gui_input", callable_mp(this, &FileSystemDock::_file_list_gui_input)); files->connect("multi_selected", callable_mp(this, &FileSystemDock::_file_multi_selected)); - files->connect("rmb_clicked", callable_mp(this, &FileSystemDock::_file_list_rmb_pressed)); + files->connect("empty_clicked", callable_mp(this, &FileSystemDock::_file_list_empty_clicked)); files->set_custom_minimum_size(Size2(0, 15 * EDSCALE)); files->set_allow_rmb_select(true); file_list_vb->add_child(files); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index f6c4e271bf..fc24b3e9fd 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -260,8 +260,8 @@ private: void _file_and_folders_fill_popup(PopupMenu *p_popup, Vector<String> p_paths, bool p_display_path_dependent_options = true); void _tree_rmb_select(const Vector2 &p_pos); void _tree_rmb_empty(const Vector2 &p_pos); - void _file_list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index); - void _file_list_rmb_pressed(const Vector2 &p_pos); + void _file_list_item_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index); + void _file_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index); void _tree_empty_selected(); struct FileInfo { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 06a84ff23c..ddde6bf144 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -1531,7 +1531,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/generate_lods"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/create_shadow_meshes"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "meshes/light_baking", PROPERTY_HINT_ENUM, "Disabled,Static (VoxelGI/SDFGI),Static Lightmaps (VoxelGI/SDFGI/LightmapGI),Dynamic (VoxelGI only)", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.1)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "meshes/lightmap_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "skins/use_named_skins"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 30)); diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp index 755bf7ce07..77e4905341 100644 --- a/editor/plugin_config_dialog.cpp +++ b/editor/plugin_config_dialog.cpp @@ -47,7 +47,7 @@ void PluginConfigDialog::_clear_fields() { } void PluginConfigDialog::_on_confirmed() { - String path = "res://addons/" + subfolder_edit->get_text(); + String path = "res://addons/" + _get_subfolder(); if (!_edit_mode) { Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); @@ -56,32 +56,35 @@ void PluginConfigDialog::_on_confirmed() { } } + int lang_idx = script_option_edit->get_selected(); + String ext = ScriptServer::get_language(lang_idx)->get_extension(); + String script_name = script_edit->get_text().is_empty() ? _get_subfolder() : script_edit->get_text(); + if (script_name.get_extension().is_empty()) { + script_name += "." + ext; + } + String script_path = path.plus_file(script_name); + Ref<ConfigFile> cf = memnew(ConfigFile); cf->set_value("plugin", "name", name_edit->get_text()); cf->set_value("plugin", "description", desc_edit->get_text()); cf->set_value("plugin", "author", author_edit->get_text()); cf->set_value("plugin", "version", version_edit->get_text()); - cf->set_value("plugin", "script", script_edit->get_text()); + cf->set_value("plugin", "script", script_name); cf->save(path.plus_file("plugin.cfg")); if (!_edit_mode) { - int lang_idx = script_option_edit->get_selected(); - String lang_name = ScriptServer::get_language(lang_idx)->get_name(); - - Ref<Script> script; - String script_path = path.plus_file(script_edit->get_text()); - String class_name = script_path.get_file().get_basename(); + String class_name = script_name.get_basename(); String template_content = ""; Vector<ScriptLanguage::ScriptTemplate> templates = ScriptServer::get_language(lang_idx)->get_built_in_templates("EditorPlugin"); - if (templates.size() > 0) { - template_content = templates.get(0).content; + if (!templates.is_empty()) { + template_content = templates[0].content; } - script = ScriptServer::get_language(lang_idx)->make_template(template_content, class_name, "EditorPlugin"); + Ref<Script> script = ScriptServer::get_language(lang_idx)->make_template(template_content, class_name, "EditorPlugin"); script->set_path(script_path); ResourceSaver::save(script_path, script); - emit_signal(SNAME("plugin_ready"), script.operator->(), active_edit->is_pressed() ? _to_absolute_plugin_path(subfolder_edit->get_text()) : ""); + emit_signal(SNAME("plugin_ready"), script.ptr(), active_edit->is_pressed() ? _to_absolute_plugin_path(_get_subfolder()) : ""); } else { EditorNode::get_singleton()->get_project_settings()->update_plugins(); } @@ -119,27 +122,18 @@ void PluginConfigDialog::_on_required_text_changed(const String &) { name_validation->set_texture(invalid_icon); name_validation->set_tooltip(TTR("Plugin name cannot be blank.")); } - if (script_edit->get_text().get_extension() != ext) { + if ((!script_edit->get_text().get_extension().is_empty() && script_edit->get_text().get_extension() != ext) || script_edit->get_text().ends_with(".")) { is_valid = false; script_validation->set_texture(invalid_icon); script_validation->set_tooltip(vformat(TTR("Script extension must match chosen language extension (.%s)."), ext)); } - if (script_edit->get_text().get_basename().is_empty()) { + if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) { is_valid = false; - script_validation->set_texture(invalid_icon); - script_validation->set_tooltip(TTR("Script name cannot be blank.")); - } - if (subfolder_edit->get_text().is_empty()) { - is_valid = false; - subfolder_validation->set_texture(invalid_icon); - subfolder_validation->set_tooltip(TTR("Subfolder cannot be blank.")); - } else if (!subfolder_edit->get_text().is_valid_filename()) { subfolder_validation->set_texture(invalid_icon); subfolder_validation->set_tooltip(TTR("Subfolder name is not a valid folder name.")); } else { - Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES); - String path = "res://addons/" + subfolder_edit->get_text(); - if (dir->dir_exists(path) && !_edit_mode) { // Only show this error if in "create" mode. + String path = "res://addons/" + _get_subfolder(); + if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode. is_valid = false; subfolder_validation->set_texture(invalid_icon); subfolder_validation->set_tooltip(TTR("Subfolder cannot be one which already exists.")); @@ -149,6 +143,10 @@ void PluginConfigDialog::_on_required_text_changed(const String &) { get_ok_button()->set_disabled(!is_valid); } +String PluginConfigDialog::_get_subfolder() { + return subfolder_edit->get_text().is_empty() ? name_edit->get_text().replace(" ", "_").to_lower() : subfolder_edit->get_text(); +} + String PluginConfigDialog::_to_absolute_plugin_path(const String &p_plugin_name) { return "res://addons/" + p_plugin_name + "/plugin.cfg"; } @@ -225,6 +223,7 @@ PluginConfigDialog::PluginConfigDialog() { // Plugin Name Label *name_lb = memnew(Label); name_lb->set_text(TTR("Plugin Name:")); + name_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(name_lb); name_validation = memnew(TextureRect); @@ -239,6 +238,7 @@ PluginConfigDialog::PluginConfigDialog() { // Subfolder Label *subfolder_lb = memnew(Label); subfolder_lb->set_text(TTR("Subfolder:")); + subfolder_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(subfolder_lb); subfolder_validation = memnew(TextureRect); @@ -253,6 +253,7 @@ PluginConfigDialog::PluginConfigDialog() { // Description Label *desc_lb = memnew(Label); desc_lb->set_text(TTR("Description:")); + desc_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(desc_lb); Control *desc_spacer = memnew(Control); @@ -266,6 +267,7 @@ PluginConfigDialog::PluginConfigDialog() { // Author Label *author_lb = memnew(Label); author_lb->set_text(TTR("Author:")); + author_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(author_lb); Control *author_spacer = memnew(Control); @@ -278,6 +280,7 @@ PluginConfigDialog::PluginConfigDialog() { // Version Label *version_lb = memnew(Label); version_lb->set_text(TTR("Version:")); + version_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(version_lb); Control *version_spacer = memnew(Control); @@ -290,6 +293,7 @@ PluginConfigDialog::PluginConfigDialog() { // Language dropdown Label *script_option_lb = memnew(Label); script_option_lb->set_text(TTR("Language:")); + script_option_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(script_option_lb); Control *script_opt_spacer = memnew(Control); @@ -311,6 +315,7 @@ PluginConfigDialog::PluginConfigDialog() { // Plugin Script Name Label *script_lb = memnew(Label); script_lb->set_text(TTR("Script Name:")); + script_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(script_lb); script_validation = memnew(TextureRect); @@ -326,6 +331,7 @@ PluginConfigDialog::PluginConfigDialog() { // TODO Make this option work better with languages like C#. Right now, it does not work because the C# project must be compiled first. Label *active_lb = memnew(Label); active_lb->set_text(TTR("Activate now?")); + active_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(active_lb); Control *active_spacer = memnew(Control); diff --git a/editor/plugin_config_dialog.h b/editor/plugin_config_dialog.h index 5c6043da12..6e0cbea378 100644 --- a/editor/plugin_config_dialog.h +++ b/editor/plugin_config_dialog.h @@ -61,6 +61,7 @@ class PluginConfigDialog : public ConfirmationDialog { void _on_cancelled(); void _on_language_changed(const int p_language); void _on_required_text_changed(const String &p_text); + String _get_subfolder(); static String _to_absolute_plugin_path(const String &p_plugin_name); diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index e0cc0f8234..177014e5a7 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -1387,13 +1387,13 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face } // Ensure B has points either side of or in the plane of A. - int in_plane_count = 0, over_count = 0, under_count = 0; + int over_count = 0, under_count = 0; Plane plane_a(vertices_a[0], vertices_a[1], vertices_a[2]); ERR_FAIL_COND_MSG(plane_a.normal == Vector3(), "Couldn't form plane from Brush A face."); for (int i = 0; i < 3; i++) { if (plane_a.has_point(vertices_b[i])) { - in_plane_count++; + // In plane. } else if (plane_a.is_point_over(vertices_b[i])) { over_count++; } else { @@ -1406,7 +1406,6 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face } // Ensure A has points either side of or in the plane of B. - in_plane_count = 0; over_count = 0; under_count = 0; Plane plane_b(vertices_b[0], vertices_b[1], vertices_b[2]); @@ -1414,7 +1413,7 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face for (int i = 0; i < 3; i++) { if (plane_b.has_point(vertices_a[i])) { - in_plane_count++; + // In plane. } else if (plane_b.is_point_over(vertices_a[i])) { over_count++; } else { diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index 7fe2e589b1..dcb1f1d744 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -94,7 +94,7 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin ERR_FAIL_MSG("ImageLoaderSVG can't create image."); } - res = sw_canvas->push(move(picture)); + res = sw_canvas->push(std::move(picture)); if (res != tvg::Result::Success) { memfree(buffer); ERR_FAIL_MSG("ImageLoaderSVG can't create image."); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index ba249aff7a..07421b7275 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -2172,6 +2172,11 @@ void TextServerAdvanced::font_set_scale(const RID &p_font_rid, int64_t p_size, d Vector2i size = _get_size(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + return; // Do not override scale for dynamic fonts, it's calculated automatically. + } +#endif fd->cache[size]->scale = p_scale; } diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 8ae56aa64d..257c569a25 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -1334,6 +1334,11 @@ void TextServerFallback::font_set_scale(const RID &p_font_rid, int64_t p_size, d Vector2i size = _get_size(fd, p_size); ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); +#ifdef MODULE_FREETYPE_ENABLED + if (fd->cache[size]->face) { + return; // Do not override scale for dynamic fonts, it's calculated automatically. + } +#endif fd->cache[size]->scale = p_scale; } diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index 5ff5b2339c..b645a48c88 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -350,7 +350,6 @@ void AudioStreamOGGVorbis::maybe_update_info() { vorbis_info_init(&info); vorbis_comment_init(&comment); - int packet_count = 0; Ref<OGGPacketSequencePlayback> packet_sequence_playback = packet_sequence->instance_playback(); for (int i = 0; i < 3; i++) { @@ -369,8 +368,6 @@ void AudioStreamOGGVorbis::maybe_update_info() { err = vorbis_synthesis_headerin(&info, &comment, packet); ERR_FAIL_COND_MSG(err != 0, "Error parsing header packet " + itos(i) + ": " + itos(err)); - - packet_count++; } packet_sequence->set_sampling_rate(info.rate); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index b548277f95..93cab85441 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1106,6 +1106,10 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); + if (icon.is_valid()) { + set_icon(icon); + } + SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | ((wd.no_focus || wd.is_popup) ? SWP_NOACTIVATE : 0)); if (p_repaint) { @@ -1895,9 +1899,11 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!p_icon.is_valid()); - Ref<Image> icon = p_icon->duplicate(); - if (icon->get_format() != Image::FORMAT_RGBA8) { - icon->convert(Image::FORMAT_RGBA8); + if (icon != p_icon) { + icon = p_icon->duplicate(); + if (icon->get_format() != Image::FORMAT_RGBA8) { + icon->convert(Image::FORMAT_RGBA8); + } } int w = icon->get_width(); int h = icon->get_height(); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index c039b29c54..febc8a2043 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -399,6 +399,7 @@ class DisplayServerWindows : public DisplayServer { HHOOK mouse_monitor = nullptr; List<WindowID> popup_list; uint64_t time_since_popup = 0; + Ref<Image> icon; WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); WindowID window_id_counter = MAIN_WINDOW_ID; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index d43ab47004..68e188bbed 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -387,6 +387,31 @@ String OS_Windows::_quote_command_line_argument(const String &p_text) const { return p_text; } +static void _append_to_pipe(char *p_bytes, int p_size, String *r_pipe, Mutex *p_pipe_mutex) { + // Try to convert from default ANSI code page to Unicode. + LocalVector<wchar_t> wchars; + int total_wchars = MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, nullptr, 0); + if (total_wchars > 0) { + wchars.resize(total_wchars); + if (MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, wchars.ptr(), total_wchars) == 0) { + wchars.clear(); + } + } + + if (p_pipe_mutex) { + p_pipe_mutex->lock(); + } + if (wchars.is_empty()) { + // Let's hope it's compatible with UTF-8. + (*r_pipe) += String::utf8(p_bytes, p_size); + } else { + (*r_pipe) += String(wchars.ptr(), total_wchars); + } + if (p_pipe_mutex) { + p_pipe_mutex->unlock(); + } +} + Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { String path = p_path.replace("/", "\\"); String command = _quote_command_line_argument(path); @@ -435,21 +460,44 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, if (r_pipe) { CloseHandle(pipe[1]); // Close pipe write handle (only child process is writing). - char buf[4096]; + + LocalVector<char> bytes; + int bytes_in_buffer = 0; + + const int CHUNK_SIZE = 4096; DWORD read = 0; for (;;) { // Read StdOut and StdErr from pipe. - bool success = ReadFile(pipe[0], buf, 4096, &read, NULL); + bytes.resize(bytes_in_buffer + CHUNK_SIZE); + const bool success = ReadFile(pipe[0], bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, NULL); if (!success || read == 0) { break; } - if (p_pipe_mutex) { - p_pipe_mutex->lock(); + + // Assume that all possible encodings are ASCII-compatible. + // Break at newline to allow receiving long output in portions. + int newline_index = -1; + for (int i = read - 1; i >= 0; i--) { + if (bytes[bytes_in_buffer + i] == '\n') { + newline_index = i; + break; + } } - (*r_pipe) += String::utf8(buf, read); - if (p_pipe_mutex) { - p_pipe_mutex->unlock(); + if (newline_index == -1) { + bytes_in_buffer += read; + continue; } + + const int bytes_to_convert = bytes_in_buffer + (newline_index + 1); + _append_to_pipe(bytes.ptr(), bytes_to_convert, r_pipe, p_pipe_mutex); + + bytes_in_buffer = read - (newline_index + 1); + memmove(bytes.ptr(), bytes.ptr() + bytes_to_convert, bytes_in_buffer); + } + + if (bytes_in_buffer > 0) { + _append_to_pipe(bytes.ptr(), bytes_in_buffer, r_pipe, p_pipe_mutex); } + CloseHandle(pipe[0]); // Close pipe read handle. } else { WaitForSingleObject(pi.pi.hProcess, INFINITE); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index efde8d8a2b..f61dbc071d 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -57,7 +57,7 @@ void Camera2D::_update_scroll() { Size2 screen_size = _get_camera_screen_size(); Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2()); - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_camera_moved", xform, screen_offset); + get_tree()->call_group(group_name, "_camera_moved", xform, screen_offset); }; } @@ -421,7 +421,7 @@ bool Camera2D::is_current() const { void Camera2D::make_current() { if (is_inside_tree()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", this); + get_tree()->call_group(group_name, "_make_current", this); } else { current = true; } @@ -430,7 +430,7 @@ void Camera2D::make_current() { void Camera2D::clear_current() { if (is_inside_tree()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", (Object *)nullptr); + get_tree()->call_group(group_name, "_make_current", (Object *)nullptr); } else { current = false; } diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 4f05e80377..4c53776bba 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -217,8 +217,6 @@ void Camera3D::make_current() { } get_viewport()->_camera_3d_set(this); - - //get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,camera_group,"_camera_make_current",this); } void Camera3D::clear_current(bool p_enable_next) { diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 88d2c1ad69..5c63bdcf1d 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -318,12 +318,11 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const LocalVector<BSPSimplex> &p_simplices, const Plane &p_plane, uint32_t p_simplex) const { int over = 0; int under = 0; - int coplanar = 0; const BSPSimplex &s = p_simplices[p_simplex]; for (int i = 0; i < 4; i++) { const Vector3 v = p_points[s.vertices[i]]; - if (p_plane.has_point(v)) { //coplanar - coplanar++; + if (p_plane.has_point(v)) { + // Coplanar. } else if (p_plane.is_point_over(v)) { over++; } else { diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index d6ac5ccf30..42a2a68e2d 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -592,7 +592,6 @@ void Voxelizer::_fixup_plot(int p_idx, int p_level) { bake_cells.write[p_idx].albedo[2] = 0; float alpha_average = 0; - int children_found = 0; for (int i = 0; i < 8; i++) { uint32_t child = bake_cells[p_idx].children[i]; @@ -603,8 +602,6 @@ void Voxelizer::_fixup_plot(int p_idx, int p_level) { _fixup_plot(child, p_level + 1); alpha_average += bake_cells[child].alpha; - - children_found++; } bake_cells.write[p_idx].alpha = alpha_average / 8.0; diff --git a/scene/3d/world_environment.cpp b/scene/3d/world_environment.cpp index f638644628..fe9d9ae4dd 100644 --- a/scene/3d/world_environment.cpp +++ b/scene/3d/world_environment.cpp @@ -71,7 +71,7 @@ void WorldEnvironment::_update_current_environment() { } else { get_viewport()->find_world_3d()->set_environment(Ref<Environment>()); } - get_tree()->call_group("_world_environment_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_world_environment_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); } void WorldEnvironment::_update_current_camera_effects() { @@ -82,7 +82,7 @@ void WorldEnvironment::_update_current_camera_effects() { get_viewport()->find_world_3d()->set_camera_effects(Ref<CameraEffects>()); } - get_tree()->call_group("_world_camera_effects_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_world_camera_effects_" + itos(get_viewport()->find_world_3d()->get_scenario().get_id()), "update_configuration_warnings"); } void WorldEnvironment::set_environment(const Ref<Environment> &p_environment) { diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 50ffb3ca67..f36682942f 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -62,15 +62,42 @@ void ProgressBar::_notification(int p_what) { Color font_color = get_theme_color(SNAME("font_color")); draw_style_box(bg, Rect2(Point2(), get_size())); + float r = get_as_ratio(); - int mp = fg->get_minimum_size().width; - int p = r * (get_size().width - mp); - if (p > 0) { - if (is_layout_rtl()) { - draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->get_minimum_size().width, get_size().height))); - } else { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); - } + + switch (mode) { + case FILL_BEGIN_TO_END: + case FILL_END_TO_BEGIN: { + int mp = fg->get_minimum_size().width; + int p = round(r * (get_size().width - mp)); + // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL, + // and left to right otherwise. And likewise for FILL_END_TO_BEGIN. + bool right_to_left = is_layout_rtl() ? (mode == FILL_BEGIN_TO_END) : (mode == FILL_END_TO_BEGIN); + if (p > 0) { + if (right_to_left) { + int p_remaining = round((1.0 - r) * (get_size().width - mp)); + draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + } else { + draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + } + } + } break; + case FILL_TOP_TO_BOTTOM: + case FILL_BOTTOM_TO_TOP: { + int mp = fg->get_minimum_size().height; + int p = round(r * (get_size().height - mp)); + + if (p > 0) { + if (mode == FILL_TOP_TO_BOTTOM) { + draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height))); + } else { + int p_remaining = round((1.0 - r) * (get_size().height - mp)); + draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height))); + } + } + } break; + case FILL_MODE_MAX: + break; } if (percent_visible) { @@ -88,6 +115,16 @@ void ProgressBar::_notification(int p_what) { } } +void ProgressBar::set_fill_mode(int p_fill) { + ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); + mode = (FillMode)p_fill; + update(); +} + +int ProgressBar::get_fill_mode() { + return mode; +} + void ProgressBar::set_percent_visible(bool p_visible) { percent_visible = p_visible; update(); @@ -98,10 +135,18 @@ bool ProgressBar::is_percent_visible() const { } void ProgressBar::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode); + ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode); ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible); ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible"); + + BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END); + BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN); + BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM); + BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP); } ProgressBar::ProgressBar() { diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h index 2d89163f78..5ba21ad7d5 100644 --- a/scene/gui/progress_bar.h +++ b/scene/gui/progress_bar.h @@ -43,11 +43,27 @@ protected: static void _bind_methods(); public: + enum FillMode { + FILL_BEGIN_TO_END, + FILL_END_TO_BEGIN, + FILL_TOP_TO_BOTTOM, + FILL_BOTTOM_TO_TOP, + FILL_MODE_MAX + }; + + void set_fill_mode(int p_fill); + int get_fill_mode(); + void set_percent_visible(bool p_visible); bool is_percent_visible() const; Size2 get_minimum_size() const override; ProgressBar(); + +private: + FillMode mode = FILL_BEGIN_TO_END; }; +VARIANT_ENUM_CAST(ProgressBar::FillMode); + #endif // PROGRESS_BAR_H diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index a151d3cb33..9d80b3cc0f 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -181,7 +181,7 @@ void SceneTree::_flush_ugc() { argptrs[i] = &E->get()[i]; } - call_group_flagsp(GROUP_CALL_REALTIME, E->key().group, E->key().call, argptrs, E->get().size()); + call_group_flagsp(GROUP_CALL_DEFAULT, E->key().group, E->key().call, argptrs, E->get().size()); unique_group_calls.erase(E); } @@ -220,7 +220,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro return; } - if (p_call_flags & GROUP_CALL_UNIQUE && !(p_call_flags & GROUP_CALL_REALTIME)) { + if (p_call_flags & GROUP_CALL_UNIQUE && p_call_flags & GROUP_CALL_DEFERRED) { ERR_FAIL_COND(ugc_locked); UGCall ug; @@ -254,7 +254,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; nodes[i]->callp(p_function, p_args, p_argcount, ce); } else { @@ -268,7 +268,7 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; nodes[i]->callp(p_function, p_args, p_argcount, ce); } else { @@ -307,7 +307,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->notification(p_notification); } else { MessageQueue::get_singleton()->push_notification(nodes[i], p_notification); @@ -320,7 +320,7 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->notification(p_notification); } else { MessageQueue::get_singleton()->push_notification(nodes[i], p_notification); @@ -358,7 +358,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->set(p_name, p_value); } else { MessageQueue::get_singleton()->push_set(nodes[i], p_name, p_value); @@ -371,7 +371,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group continue; } - if (p_call_flags & GROUP_CALL_REALTIME) { + if (!(p_call_flags & GROUP_CALL_DEFERRED)) { nodes[i]->set(p_name, p_value); } else { MessageQueue::get_singleton()->push_set(nodes[i], p_name, p_value); @@ -390,7 +390,7 @@ void SceneTree::notify_group(const StringName &p_group, int p_notification) { } void SceneTree::set_group(const StringName &p_group, const String &p_name, const Variant &p_value) { - set_group_flags(0, p_group, p_name, p_value); + set_group_flags(GROUP_CALL_DEFAULT, p_group, p_name, p_value); } void SceneTree::initialize() { @@ -413,7 +413,7 @@ bool SceneTree::physics_process(double p_time) { emit_signal(SNAME("physics_frame")); _notify_group_pause(SNAME("physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); - call_group_flags(GROUP_CALL_REALTIME, SNAME("_picking_viewports"), SNAME("_process_picking")); + call_group(SNAME("_picking_viewports"), SNAME("_process_picking")); _notify_group_pause(SNAME("physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -944,7 +944,7 @@ void SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::Ca StringName group = *p_args[0]; StringName method = *p_args[1]; - call_group_flagsp(0, group, method, p_args + 2, p_argcount - 2); + call_group_flagsp(GROUP_CALL_DEFAULT, group, method, p_args + 2, p_argcount - 2); } int64_t SceneTree::get_frame() const { @@ -1277,7 +1277,7 @@ void SceneTree::_bind_methods() { BIND_ENUM_CONSTANT(GROUP_CALL_DEFAULT); BIND_ENUM_CONSTANT(GROUP_CALL_REVERSE); - BIND_ENUM_CONSTANT(GROUP_CALL_REALTIME); + BIND_ENUM_CONSTANT(GROUP_CALL_DEFERRED); BIND_ENUM_CONSTANT(GROUP_CALL_UNIQUE); } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 9d7757e0a3..d633fb38d0 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -151,7 +151,6 @@ private: int collision_debug_contacts; void _change_scene(Node *p_to); - //void _call_group(uint32_t p_call_flags,const StringName& p_group,const StringName& p_function,const Variant& p_arg1,const Variant& p_arg2); List<Ref<SceneTreeTimer>> timers; List<Ref<Tween>> tweens; @@ -225,7 +224,7 @@ public: enum GroupCallFlags { GROUP_CALL_DEFAULT = 0, GROUP_CALL_REVERSE = 1, - GROUP_CALL_REALTIME = 2, + GROUP_CALL_DEFERRED = 2, GROUP_CALL_UNIQUE = 4, }; @@ -235,17 +234,20 @@ public: void notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification); void set_group_flags(uint32_t p_call_flags, const StringName &p_group, const String &p_name, const Variant &p_value); + // `notify_group()` is immediate by default since Godot 4.0. void notify_group(const StringName &p_group, int p_notification); + // `set_group()` is immediate by default since Godot 4.0. void set_group(const StringName &p_group, const String &p_name, const Variant &p_value); template <typename... VarArgs> + // `call_group()` is immediate by default since Godot 4.0. void call_group(const StringName &p_group, const StringName &p_function, VarArgs... p_args) { Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. const Variant *argptrs[sizeof...(p_args) + 1]; for (uint32_t i = 0; i < sizeof...(p_args); i++) { argptrs[i] = &args[i]; } - call_group_flagsp(0, p_group, p_function, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); + call_group_flagsp(GROUP_CALL_DEFAULT, p_group, p_function, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } template <typename... VarArgs> diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index 7c689bd436..ed08c45a01 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -267,7 +267,7 @@ void ShaderGlobalsOverride::_notification(int p_what) { remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group_active); remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group); - get_tree()->call_group(SceneStringNames::get_singleton()->shader_overrides_group, "_activate"); //another may want to activate when this is removed + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->shader_overrides_group, "_activate"); //another may want to activate when this is removed active = false; } break; } diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index d7e58ed707..e4037c2843 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2186,7 +2186,7 @@ void Viewport::_gui_control_grab_focus(Control *p_control) { // No need for change. return; } - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, "_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window()); + get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window()); gui.key_focus = p_control; emit_signal(SNAME("gui_focus_changed"), p_control); p_control->notification(Control::NOTIFICATION_FOCUS_ENTER); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 16fce5e08a..27e1590940 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -84,7 +84,7 @@ void Material::inspect_native_shader_code() { SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop()); RID shader = get_shader_rid(); if (st && shader.is_valid()) { - st->call_group("_native_shader_source_visualizer", "_inspect_shader", shader); + st->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_native_shader_source_visualizer", "_inspect_shader", shader); } } diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index 882afdb43d..94e7f46ea5 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -137,7 +137,7 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector Edge ignore_to_edge(-1, -1); if (!_is_point_inside(from)) { - float closest_dist = 1e20; + float closest_dist = 1e20f; Vector2 closest_point; for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { @@ -161,7 +161,7 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector }; if (!_is_point_inside(to)) { - float closest_dist = 1e20; + float closest_dist = 1e20f; Vector2 closest_point; for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { @@ -489,7 +489,7 @@ bool PolygonPathFinder::is_point_inside(const Vector2 &p_point) const { } Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { - float closest_dist = 1e20; + float closest_dist = 1e20f; Vector2 closest_point; for (Set<Edge>::Element *E = edges.front(); E; E = E->next()) { @@ -508,7 +508,7 @@ Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { } } - ERR_FAIL_COND_V(closest_dist == 1e20, Vector2()); + ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2()); return closest_point; } diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 7e18eac6ae..3ea67ae115 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -226,8 +226,6 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { } if (!p_viewport->disable_2d) { - int i = 0; - Map<Viewport::CanvasKey, Viewport::CanvasData *> canvas_map; Rect2 clip_rect(0, 0, p_viewport->size.x, p_viewport->size.y); @@ -238,13 +236,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { RendererCanvasRender::Light *directional_lights_with_shadow = nullptr; if (p_viewport->sdf_active) { - //process SDF + // Process SDF. Rect2 sdf_rect = RSG::texture_storage->render_target_get_sdf_rect(p_viewport->render_target); RendererCanvasRender::LightOccluderInstance *occluders = nullptr; - //make list of occluders + // Make list of occluders. for (KeyValue<RID, Viewport::CanvasData> &E : p_viewport->canvas_map) { RendererCanvasCull::Canvas *canvas = static_cast<RendererCanvasCull::Canvas *>(E.value.canvas); Transform2D xf = _canvas_get_transform(p_viewport, canvas, &E.value, clip_rect.size); @@ -265,14 +263,13 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { RSG::canvas_render->render_sdf(p_viewport->render_target, occluders); RSG::texture_storage->render_target_mark_sdf_enabled(p_viewport->render_target, true); - p_viewport->sdf_active = false; // if used, gets set active again + p_viewport->sdf_active = false; // If used, gets set active again. } else { RSG::texture_storage->render_target_mark_sdf_enabled(p_viewport->render_target, false); } Rect2 shadow_rect; - int light_count = 0; int shadow_count = 0; int directional_light_count = 0; @@ -282,7 +279,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { Transform2D xf = _canvas_get_transform(p_viewport, canvas, &E.value, clip_rect.size); - //find lights in canvas + // Find lights in canvas. for (Set<RendererCanvasRender::Light *>::Element *F = canvas->lights.front(); F; F = F->next()) { RendererCanvasRender::Light *cl = F->get(); @@ -298,12 +295,10 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { if (clip_rect.intersects_transformed(cl->xform_cache, cl->rect_cache)) { cl->filter_next_ptr = lights; lights = cl; - // cl->texture_cache = nullptr; Transform2D scale; scale.scale(cl->rect_cache.size); scale.columns[2] = cl->rect_cache.position; cl->light_shader_xform = cl->xform * scale; - //cl->light_shader_pos = cl->xform_cache[2]; if (cl->use_shadow) { cl->shadows_next_ptr = lights_with_shadow; if (lights_with_shadow == nullptr) { @@ -314,11 +309,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { lights_with_shadow = cl; cl->radius_cache = cl->rect_cache.size.length(); } - - light_count++; } - - //guess this is not needed, but keeping because it may be } } @@ -433,8 +424,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { RENDER_TIMESTAMP("> Render DirectionalLight2D Shadows"); - //make list of occluders - int occ_cullded = 0; + // Make list of occluders. for (KeyValue<RID, Viewport::CanvasData> &E : p_viewport->canvas_map) { RendererCanvasCull::Canvas *canvas = static_cast<RendererCanvasCull::Canvas *>(E.value.canvas); Transform2D xf = _canvas_get_transform(p_viewport, canvas, &E.value, clip_rect.size); @@ -452,7 +442,6 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { if (F->get()->aabb_cache.intersects_filled_polygon(xf_points, point_count)) { F->get()->next = occluders; occluders = F->get(); - occ_cullded++; } } } @@ -504,7 +493,6 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) { if (RSG::canvas->was_sdf_used()) { p_viewport->sdf_active = true; } - i++; if (scenario_draw_canvas_bg && E.key.get_layer() >= scenario_canvas_max_layer) { if (!can_draw_3d) { diff --git a/tests/core/math/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h index 3487e4d7e8..db4e6e2177 100644 --- a/tests/core/math/test_geometry_2d.h +++ b/tests/core/math/test_geometry_2d.h @@ -135,7 +135,7 @@ TEST_CASE("[Geometry2D] Line intersection") { "Parallel lines should not intersect."); } -TEST_CASE("[Geometry2D] Segment intersection.") { +TEST_CASE("[Geometry2D] Segment intersection") { Vector2 r; CHECK(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), &r)); @@ -148,6 +148,10 @@ TEST_CASE("[Geometry2D] Segment intersection.") { Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(2, -1), &r), "Parallel segments should not intersect."); + CHECK_FALSE_MESSAGE( + Geometry2D::segment_intersects_segment(Vector2(1, 2), Vector2(3, 2), Vector2(0, 2), Vector2(-2, 2), &r), + "Non-overlapping collinear segments should not intersect."); + CHECK_MESSAGE( Geometry2D::segment_intersects_segment(Vector2(0, 0), Vector2(0, 1), Vector2(0, 0), Vector2(1, 0), &r), "Touching segments should intersect."); @@ -159,11 +163,114 @@ TEST_CASE("[Geometry2D] Segment intersection.") { CHECK(r.is_equal_approx(Vector2(0, 0))); } +TEST_CASE("[Geometry2D] Segment intersection with circle") { + real_t minus_one = -1.0; + real_t zero = 0.0; + real_t one_quarter = 0.25; + real_t three_quarters = 0.75; + real_t one = 1.0; + + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(4, 0), Vector2(0, 0), 1.0), one_quarter), + "Segment from inside to outside of circle should intersect it."); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(4, 0), Vector2(0, 0), Vector2(0, 0), 1.0), three_quarters), + "Segment from outside to inside of circle should intersect it."); + + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(-2, 0), Vector2(2, 0), Vector2(0, 0), 1.0), one_quarter), + "Segment running through circle should intersect it."); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(-2, 0), Vector2(0, 0), 1.0), one_quarter), + "Segment running through circle should intersect it."); + + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(1, 0), Vector2(0, 0), 1.0), one), + "Segment starting inside the circle and ending on the circle should intersect it"); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(0, 0), Vector2(0, 0), 1.0), zero), + "Segment starting on the circle and going inwards should intersect it"); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(2, 0), Vector2(0, 0), 1.0), zero), + "Segment starting on the circle and going outwards should intersect it"); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(1, 0), Vector2(0, 0), 1.0), one), + "Segment starting outside the circle and ending on the circle intersect it"); + + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0), 2.0), minus_one), + "Segment completely within the circle should not intersect it"); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(-1, 0), Vector2(0, 0), 2.0), minus_one), + "Segment completely within the circle should not intersect it"); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(3, 0), Vector2(0, 0), 1.0), minus_one), + "Segment completely outside the circle should not intersect it"); + CHECK_MESSAGE( + Math::is_equal_approx(Geometry2D::segment_intersects_circle(Vector2(3, 0), Vector2(2, 0), Vector2(0, 0), 1.0), minus_one), + "Segment completely outside the circle should not intersect it"); +} + +TEST_CASE("[Geometry2D] Segment intersection with polygon") { + Vector<Point2> a; + + a.push_back(Point2(-2, 2)); + a.push_back(Point2(3, 4)); + a.push_back(Point2(1, 1)); + a.push_back(Point2(2, -2)); + a.push_back(Point2(-1, -1)); + + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(0, 2), Vector2(2, 2), a), + "Segment from inside to outside of polygon should intersect it."); + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(0, 2), a), + "Segment from outside to inside of polygon should intersect it."); + + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(2, 4), Vector2(3, 3), a), + "Segment running through polygon should intersect it."); + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(3, 3), Vector2(2, 4), a), + "Segment running through polygon should intersect it."); + + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(0, 0), Vector2(1, 1), a), + "Segment starting inside the polygon and ending on the polygon should intersect it"); + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(1, 1), Vector2(0, 0), a), + "Segment starting on the polygon and going inwards should intersect it"); + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 2), Vector2(-2, -1), a), + "Segment starting on the polygon and going outwards should intersect it"); + CHECK_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 1), Vector2(-2, 2), a), + "Segment starting outside the polygon and ending on the polygon intersect it"); + + CHECK_FALSE_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(-1, 2), Vector2(1, -1), a), + "Segment completely within the polygon should not intersect it"); + CHECK_FALSE_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(1, -1), Vector2(-1, 2), a), + "Segment completely within the polygon should not intersect it"); + CHECK_FALSE_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(2, -1), a), + "Segment completely outside the polygon should not intersect it"); + CHECK_FALSE_MESSAGE( + Geometry2D::is_segment_intersecting_polygon(Vector2(2, -1), Vector2(2, 2), a), + "Segment completely outside the polygon should not intersect it"); +} + TEST_CASE("[Geometry2D] Closest point to segment") { Vector2 s[] = { Vector2(-4, -4), Vector2(4, 4) }; CHECK(Geometry2D::get_closest_point_to_segment(Vector2(4.1, 4.1), s).is_equal_approx(Vector2(4, 4))); CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-4.1, -4.1), s).is_equal_approx(Vector2(-4, -4))); CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-1, 1), s).is_equal_approx(Vector2(0, 0))); + + Vector2 t[] = { Vector2(1, -2), Vector2(1, -2) }; + CHECK_MESSAGE( + Geometry2D::get_closest_point_to_segment(Vector2(-3, 4), t).is_equal_approx(Vector2(1, -2)), + "Line segment is only a single point. This point should be the closest."); } TEST_CASE("[Geometry2D] Closest point to uncapped segment") { @@ -186,6 +293,30 @@ TEST_CASE("[Geometry2D] Closest points between segments") { Geometry2D::get_closest_points_between_segments(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), c1, c2); CHECK(c1.is_equal_approx(Vector2(0, 0))); CHECK(c2.is_equal_approx(Vector2(0, 0))); + + Geometry2D::get_closest_points_between_segments(Vector2(-3, 4), Vector2(-3, 4), Vector2(-4, 3), Vector2(-2, 3), c1, c2); + CHECK_MESSAGE( + c1.is_equal_approx(Vector2(-3, 4)), + "1st line segment is only a point, this point should be the closest point to the 2nd line segment."); + CHECK_MESSAGE( + c2.is_equal_approx(Vector2(-3, 3)), + "1st line segment is only a point, this should not matter when determining the closest point on the 2nd line segment."); + + Geometry2D::get_closest_points_between_segments(Vector2(-4, 3), Vector2(-2, 3), Vector2(-3, 4), Vector2(-3, 4), c1, c2); + CHECK_MESSAGE( + c1.is_equal_approx(Vector2(-3, 3)), + "2nd line segment is only a point, this should not matter when determining the closest point on the 1st line segment."); + CHECK_MESSAGE( + c2.is_equal_approx(Vector2(-3, 4)), + "2nd line segment is only a point, this point should be the closest point to the 1st line segment."); + + Geometry2D::get_closest_points_between_segments(Vector2(5, -4), Vector2(5, -4), Vector2(-2, 1), Vector2(-2, 1), c1, c2); + CHECK_MESSAGE( + c1.is_equal_approx(Vector2(5, -4)), + "Both line segments are only a point. On the 1st line segment, that point should be the closest point to the 2nd line segment."); + CHECK_MESSAGE( + c2.is_equal_approx(Vector2(-2, 1)), + "Both line segments are only a point. On the 2nd line segment, that point should be the closest point to the 1st line segment."); } TEST_CASE("[Geometry2D] Make atlas") { @@ -562,6 +693,174 @@ TEST_CASE("[Geometry2D] Clip polyline with polygon") { CHECK(r[1][1].is_equal_approx(Vector2(55, 70))); } } + +TEST_CASE("[Geometry2D] Convex hull") { + Vector<Point2> a; + Vector<Point2> r; + + a.push_back(Point2(-4, -8)); + a.push_back(Point2(-10, -4)); + a.push_back(Point2(8, 2)); + a.push_back(Point2(-6, 10)); + a.push_back(Point2(-12, 4)); + a.push_back(Point2(10, -8)); + a.push_back(Point2(4, 8)); + + SUBCASE("[Geometry2D] No points") { + r = Geometry2D::convex_hull(Vector<Vector2>()); + + CHECK_MESSAGE(r.is_empty(), "The convex hull should be empty if there are no input points."); + } + + SUBCASE("[Geometry2D] Single point") { + Vector<Point2> b; + b.push_back(Point2(4, -3)); + + r = Geometry2D::convex_hull(b); + REQUIRE_MESSAGE(r.size() == 1, "Convex hull should contain 1 point."); + CHECK(r[0].is_equal_approx(b[0])); + } + + SUBCASE("[Geometry2D] All points form the convex hull") { + r = Geometry2D::convex_hull(a); + REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points."); + CHECK(r[0].is_equal_approx(Point2(-12, 4))); + CHECK(r[1].is_equal_approx(Point2(-10, -4))); + CHECK(r[2].is_equal_approx(Point2(-4, -8))); + CHECK(r[3].is_equal_approx(Point2(10, -8))); + CHECK(r[4].is_equal_approx(Point2(8, 2))); + CHECK(r[5].is_equal_approx(Point2(4, 8))); + CHECK(r[6].is_equal_approx(Point2(-6, 10))); + CHECK(r[7].is_equal_approx(Point2(-12, 4))); + } + + SUBCASE("[Geometry2D] Add extra points inside original convex hull") { + a.push_back(Point2(-4, -8)); + a.push_back(Point2(0, 0)); + a.push_back(Point2(0, 8)); + a.push_back(Point2(-10, -3)); + a.push_back(Point2(9, -4)); + a.push_back(Point2(6, 4)); + + r = Geometry2D::convex_hull(a); + REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points."); + CHECK(r[0].is_equal_approx(Point2(-12, 4))); + CHECK(r[1].is_equal_approx(Point2(-10, -4))); + CHECK(r[2].is_equal_approx(Point2(-4, -8))); + CHECK(r[3].is_equal_approx(Point2(10, -8))); + CHECK(r[4].is_equal_approx(Point2(8, 2))); + CHECK(r[5].is_equal_approx(Point2(4, 8))); + CHECK(r[6].is_equal_approx(Point2(-6, 10))); + CHECK(r[7].is_equal_approx(Point2(-12, 4))); + } + + SUBCASE("[Geometry2D] Add extra points on border of original convex hull") { + a.push_back(Point2(9, -3)); + a.push_back(Point2(-2, -8)); + + r = Geometry2D::convex_hull(a); + REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points."); + CHECK(r[0].is_equal_approx(Point2(-12, 4))); + CHECK(r[1].is_equal_approx(Point2(-10, -4))); + CHECK(r[2].is_equal_approx(Point2(-4, -8))); + CHECK(r[3].is_equal_approx(Point2(10, -8))); + CHECK(r[4].is_equal_approx(Point2(8, 2))); + CHECK(r[5].is_equal_approx(Point2(4, 8))); + CHECK(r[6].is_equal_approx(Point2(-6, 10))); + CHECK(r[7].is_equal_approx(Point2(-12, 4))); + } + + SUBCASE("[Geometry2D] Add extra points outside border of original convex hull") { + a.push_back(Point2(-11, -1)); + a.push_back(Point2(7, 6)); + + r = Geometry2D::convex_hull(a); + REQUIRE_MESSAGE(r.size() == 10, "Convex hull should contain 10 points."); + CHECK(r[0].is_equal_approx(Point2(-12, 4))); + CHECK(r[1].is_equal_approx(Point2(-11, -1))); + CHECK(r[2].is_equal_approx(Point2(-10, -4))); + CHECK(r[3].is_equal_approx(Point2(-4, -8))); + CHECK(r[4].is_equal_approx(Point2(10, -8))); + CHECK(r[5].is_equal_approx(Point2(8, 2))); + CHECK(r[6].is_equal_approx(Point2(7, 6))); + CHECK(r[7].is_equal_approx(Point2(4, 8))); + CHECK(r[8].is_equal_approx(Point2(-6, 10))); + CHECK(r[9].is_equal_approx(Point2(-12, 4))); + } +} + +TEST_CASE("[Geometry2D] Bresenham line") { + Vector<Vector2i> r; + + SUBCASE("[Geometry2D] Single point") { + r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(0, 0)); + + REQUIRE_MESSAGE(r.size() == 1, "The Bresenham line should contain exactly one point."); + CHECK(r[0] == Vector2i(0, 0)); + } + + SUBCASE("[Geometry2D] Line parallel to x-axis") { + r = Geometry2D::bresenham_line(Point2i(1, 2), Point2i(5, 2)); + + REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points."); + CHECK(r[0] == Vector2i(1, 2)); + CHECK(r[1] == Vector2i(2, 2)); + CHECK(r[2] == Vector2i(3, 2)); + CHECK(r[3] == Vector2i(4, 2)); + CHECK(r[4] == Vector2i(5, 2)); + } + + SUBCASE("[Geometry2D] 45 degree line from the origin") { + r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 4)); + + REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points."); + CHECK(r[0] == Vector2i(0, 0)); + CHECK(r[1] == Vector2i(1, 1)); + CHECK(r[2] == Vector2i(2, 2)); + CHECK(r[3] == Vector2i(3, 3)); + CHECK(r[4] == Vector2i(4, 4)); + } + + SUBCASE("[Geometry2D] Sloped line going up one unit") { + r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 1)); + + REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points."); + CHECK(r[0] == Vector2i(0, 0)); + CHECK(r[1] == Vector2i(1, 0)); + CHECK(r[2] == Vector2i(2, 0)); + CHECK(r[3] == Vector2i(3, 1)); + CHECK(r[4] == Vector2i(4, 1)); + } + + SUBCASE("[Geometry2D] Sloped line going up two units") { + r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 2)); + + REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points."); + CHECK(r[0] == Vector2i(0, 0)); + CHECK(r[1] == Vector2i(1, 0)); + CHECK(r[2] == Vector2i(2, 1)); + CHECK(r[3] == Vector2i(3, 1)); + CHECK(r[4] == Vector2i(4, 2)); + } + + SUBCASE("[Geometry2D] Long sloped line") { + r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(11, 5)); + + REQUIRE_MESSAGE(r.size() == 12, "The Bresenham line should contain exactly twelve points."); + CHECK(r[0] == Vector2i(0, 0)); + CHECK(r[1] == Vector2i(1, 0)); + CHECK(r[2] == Vector2i(2, 1)); + CHECK(r[3] == Vector2i(3, 1)); + CHECK(r[4] == Vector2i(4, 2)); + CHECK(r[5] == Vector2i(5, 2)); + CHECK(r[6] == Vector2i(6, 3)); + CHECK(r[7] == Vector2i(7, 3)); + CHECK(r[8] == Vector2i(8, 4)); + CHECK(r[9] == Vector2i(9, 4)); + CHECK(r[10] == Vector2i(10, 5)); + CHECK(r[11] == Vector2i(11, 5)); + } +} } // namespace TestGeometry2D #endif // TEST_GEOMETRY_2D_H |