diff options
126 files changed, 1262 insertions, 630 deletions
diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index f684b838e1..39f7b7657a 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Emscripten latest - uses: mymindstorm/setup-emsdk@v11 + uses: mymindstorm/setup-emsdk@v12 with: version: ${{env.EM_VERSION}} actions-cache-folder: ${{env.EM_CACHE_FOLDER}} diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 933b9891cc..f3c0bc2153 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1331,6 +1331,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/occlusion_culling/bvh_build_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), 2); GLOBAL_DEF(PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1"), 60); // No negative and limit to 500 due to crashes. GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_RANGE, "Based on Locale,Left-to-Right,Right-to-Left"), 0); GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000); diff --git a/core/io/ip.cpp b/core/io/ip.cpp index 28b7037120..65728f34f6 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -75,7 +75,7 @@ struct _IP_ResolverPrivate { Semaphore sem; Thread thread; - bool thread_abort = false; + SafeFlag thread_abort; void resolve_queues() { for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) { @@ -111,7 +111,7 @@ struct _IP_ResolverPrivate { static void _thread_function(void *self) { _IP_ResolverPrivate *ipr = static_cast<_IP_ResolverPrivate *>(self); - while (!ipr->thread_abort) { + while (!ipr->thread_abort.is_set()) { ipr->sem.wait(); ipr->resolve_queues(); } @@ -343,12 +343,12 @@ IP::IP() { singleton = this; resolver = memnew(_IP_ResolverPrivate); - resolver->thread_abort = false; + resolver->thread_abort.clear(); resolver->thread.start(_IP_ResolverPrivate::_thread_function, resolver); } IP::~IP() { - resolver->thread_abort = true; + resolver->thread_abort.set(); resolver->sem.post(); resolver->thread.wait_to_finish(); diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index c47f297a72..d0448e86fc 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -492,13 +492,21 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e print_lt("GET: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); } + bool still_valid = true; + bool was_thread = load_task.thread; do { load_task.cond_var->wait(thread_load_lock); + if (!thread_load_tasks.has(local_path)) { //may have been erased during unlock and this was always an invalid call + still_valid = false; + break; + } } while (load_task.cond_var); // In case of spurious wakeup. - thread_suspended_count--; + if (was_thread) { + thread_suspended_count--; + } - if (!thread_load_tasks.has(local_path)) { //may have been erased during unlock and this was always an invalid call + if (!still_valid) { if (r_error) { *r_error = ERR_INVALID_PARAMETER; } diff --git a/core/math/random_pcg.h b/core/math/random_pcg.h index fe40e7f76a..cc22b23b70 100644 --- a/core/math/random_pcg.h +++ b/core/math/random_pcg.h @@ -126,10 +126,18 @@ public: } _FORCE_INLINE_ double randfn(double p_mean, double p_deviation) { - return p_mean + p_deviation * (cos(Math_TAU * randd()) * sqrt(-2.0 * log(randd()))); // Box-Muller transform + double temp = randd(); + if (temp < CMP_EPSILON) { + temp += CMP_EPSILON; // To prevent generating of INF value in log function, resulting to return NaN value from this function. + } + return p_mean + p_deviation * (cos(Math_TAU * randd()) * sqrt(-2.0 * log(temp))); // Box-Muller transform. } _FORCE_INLINE_ float randfn(float p_mean, float p_deviation) { - return p_mean + p_deviation * (cos((float)Math_TAU * randf()) * sqrt(-2.0 * log(randf()))); // Box-Muller transform + float temp = randf(); + if (temp < CMP_EPSILON) { + temp += CMP_EPSILON; // To prevent generating of INF value in log function, resulting to return NaN value from this function. + } + return p_mean + p_deviation * (cos((float)Math_TAU * randf()) * sqrt(-2.0 * log(temp))); // Box-Muller transform. } double random(double p_from, double p_to); diff --git a/core/os/thread.cpp b/core/os/thread.cpp index 9d16392b2a..92865576f3 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -50,8 +50,8 @@ void Thread::_set_platform_functions(const PlatformFunctions &p_functions) { platform_functions = p_functions; } -void Thread::callback(Thread *p_self, const Settings &p_settings, Callback p_callback, void *p_userdata) { - Thread::caller_id = _thread_id_hash(p_self->thread.get_id()); +void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_callback, void *p_userdata) { + Thread::caller_id = p_caller_id; if (platform_functions.set_priority) { platform_functions.set_priority(p_settings.priority); } @@ -79,7 +79,7 @@ void Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_ std::thread empty_thread; thread.swap(empty_thread); } - std::thread new_thread(&Thread::callback, this, p_settings, p_callback, p_user); + std::thread new_thread(&Thread::callback, _thread_id_hash(thread.get_id()), p_settings, p_callback, p_user); thread.swap(new_thread); id = _thread_id_hash(thread.get_id()); } diff --git a/core/os/thread.h b/core/os/thread.h index 3d4c48a760..6eb21fba65 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -82,7 +82,7 @@ private: static thread_local ID caller_id; std::thread thread; - static void callback(Thread *p_self, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata); + static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata); static PlatformFunctions platform_functions; diff --git a/core/string/translation.cpp b/core/string/translation.cpp index b9d5d3b538..160bad14ab 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -712,7 +712,25 @@ String TranslationServer::get_tool_locale() { #else { #endif - return get_locale(); + // Look for best matching loaded translation. + String best_locale = "en"; + int best_score = 0; + + for (const Ref<Translation> &E : translations) { + const Ref<Translation> &t = E; + ERR_FAIL_COND_V(t.is_null(), best_locale); + String l = t->get_locale(); + + int score = compare_locales(locale, l); + if (score > 0 && score >= best_score) { + best_locale = l; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } + } + } + return best_locale; } } diff --git a/core/typedefs.h b/core/typedefs.h index cd1367611a..1dcba58188 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -305,9 +305,9 @@ struct BuildIndexSequence<0, Is...> : IndexSequence<Is...> {}; #endif // Macro GD_IS_DEFINED() allows to check if a macro is defined. It needs to be defined to anything (say 1) to work. -#define __GDARG_PLACEHOLDER_1 0, +#define __GDARG_PLACEHOLDER_1 false, #define __gd_take_second_arg(__ignored, val, ...) val -#define ____gd_is_defined(arg1_or_junk) __gd_take_second_arg(arg1_or_junk 1, 0) +#define ____gd_is_defined(arg1_or_junk) __gd_take_second_arg(arg1_or_junk true, false) #define ___gd_is_defined(val) ____gd_is_defined(__GDARG_PLACEHOLDER_##val) #define GD_IS_DEFINED(x) ___gd_is_defined(x) diff --git a/core/variant/array.cpp b/core/variant/array.cpp index d156c35343..5215142dd3 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -466,7 +466,7 @@ Array Array::slice(int p_begin, int p_end, int p_step, bool p_deep) const { ERR_FAIL_COND_V_MSG(p_step > 0 && begin > end, result, "Slice is positive, but bounds is decreasing."); ERR_FAIL_COND_V_MSG(p_step < 0 && begin < end, result, "Slice is negative, but bounds is increasing."); - int result_size = (end - begin) / p_step; + int result_size = (end - begin) / p_step + (((end - begin) % p_step != 0) ? 1 : 0); result.resize(result_size); for (int src_idx = begin, dest_idx = 0; dest_idx < result_size; ++dest_idx) { diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 2da9559873..fa3bb78913 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -2941,7 +2941,7 @@ uint32_t Variant::recursive_hash(int recursion_count) const { return hash_one_uint64((uint64_t)_data._int); } break; case FLOAT: { - return hash_murmur3_one_float(_data._float); + return hash_murmur3_one_double(_data._float); } break; case STRING: { return reinterpret_cast<const String *>(_data._mem)->hash(); @@ -3158,7 +3158,7 @@ uint32_t Variant::recursive_hash(int recursion_count) const { } return hash_fmix32(h); } else { - return hash_murmur3_one_float(0.0); + return hash_murmur3_one_double(0.0); } } break; diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 8f3ae65b9c..a6363039ba 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -542,7 +542,8 @@ struct VariantUtilityFunctions { } Variant base = *p_args[0]; Variant ret; - for (int i = 1; i < p_argcount; i++) { + + for (int i = 0; i < p_argcount; i++) { Variant::Type arg_type = p_args[i]->get_type(); if (arg_type != Variant::INT && arg_type != Variant::FLOAT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; @@ -550,6 +551,9 @@ struct VariantUtilityFunctions { r_error.argument = i; return Variant(); } + if (i == 0) { + continue; + } bool valid; Variant::evaluate(Variant::OP_LESS, base, *p_args[i], ret, valid); if (!valid) { @@ -582,7 +586,8 @@ struct VariantUtilityFunctions { } Variant base = *p_args[0]; Variant ret; - for (int i = 1; i < p_argcount; i++) { + + for (int i = 0; i < p_argcount; i++) { Variant::Type arg_type = p_args[i]->get_type(); if (arg_type != Variant::INT && arg_type != Variant::FLOAT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; @@ -590,6 +595,9 @@ struct VariantUtilityFunctions { r_error.argument = i; return Variant(); } + if (i == 0) { + continue; + } bool valid; Variant::evaluate(Variant::OP_GREATER, base, *p_args[i], ret, valid); if (!valid) { diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 3bda3a3896..8db668af0a 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -622,7 +622,7 @@ <description> Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clampf] on the result of this function. [codeblock] - lerp(0, 4, 0.75) # Returns 3.0 + lerpf(0, 4, 0.75) # Returns 3.0 [/codeblock] See also [method inverse_lerp] which performs the reverse of this operation. To perform eased interpolation with [method lerp], combine it with [method ease] or [method smoothstep]. </description> @@ -645,8 +645,8 @@ <return type="float" /> <param index="0" name="x" type="float" /> <description> - Returns the natural logarithm of [param x]. This is the amount of time needed to reach a certain level of continuous growth. - [b]Note:[/b] This is not the same as the "log" function on most calculators, which uses a base 10 logarithm. + Returns the [url=https://en.wikipedia.org/wiki/Natural_logarithm]natural logarithm[/url] of [param x] (base [url=https://en.wikipedia.org/wiki/E_(mathematical_constant)][i]e[/i][/url], with [i]e[/i] being approximately 2.71828). This is the amount of time needed to reach a certain level of continuous growth. + [b]Note:[/b] This is not the same as the "log" function on most calculators, which uses a base 10 logarithm. To use base 10 logarithm, use [code]log(x) / log(10)[/code]. [codeblock] log(10) # Returns 2.302585 [/codeblock] @@ -1142,9 +1142,9 @@ <description> Returns [code]-1.0[/code] if [param x] is negative, [code]1.0[/code] if [param x] is positive, and [code]0.0[/code] if [param x] is zero. [codeblock] - sign(-6.5) # Returns -1.0 - sign(0.0) # Returns 0.0 - sign(6.5) # Returns 1.0 + signf(-6.5) # Returns -1.0 + signf(0.0) # Returns 0.0 + signf(6.5) # Returns 1.0 [/codeblock] </description> </method> @@ -1154,9 +1154,9 @@ <description> Returns [code]-1[/code] if [param x] is negative, [code]1[/code] if [param x] is positive, and [code]0[/code] if if [param x] is zero. [codeblock] - sign(-6) # Returns -1 - sign(0) # Returns 0 - sign(6) # Returns 1 + signi(-6) # Returns -1 + signi(0) # Returns 0 + signi(6) # Returns 1 [/codeblock] </description> </method> @@ -1226,8 +1226,8 @@ Returns the multiple of [param step] that is the closest to [param x]. This can also be used to round a floating point number to an arbitrary number of decimals. A type-safe version of [method snapped], returning a [float]. [codeblock] - snapped(32.0, 2.5) # Returns 32.5 - snapped(3.14159, 0.01) # Returns 3.14 + snappedf(32.0, 2.5) # Returns 32.5 + snappedf(3.14159, 0.01) # Returns 3.14 [/codeblock] </description> </method> @@ -1239,8 +1239,8 @@ Returns the multiple of [param step] that is the closest to [param x]. A type-safe version of [method snapped], returning an [int]. [codeblock] - snapped(53, 16) # Returns 48 - snapped(4096, 100) # Returns 4100 + snappedi(53, 16) # Returns 48 + snappedi(4096, 100) # Returns 4100 [/codeblock] </description> </method> diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml index 6cd8c44269..1d95152103 100644 --- a/doc/classes/BaseMaterial3D.xml +++ b/doc/classes/BaseMaterial3D.xml @@ -390,7 +390,7 @@ Repeat flags for the texture. See [enum TextureFilter] for options. </member> <member name="transparency" type="int" setter="set_transparency" getter="get_transparency" enum="BaseMaterial3D.Transparency" default="0"> - If [code]true[/code], transparency is enabled on the body. Some transparency modes will disable shadow casting. Any transparency mode other than Disabled has a greater performance impact compared to opaque rendering. See also [member blend_mode]. + The material's transparency mode. Some transparency modes will disable shadow casting. Any transparency mode other than [constant TRANSPARENCY_DISABLED] has a greater performance impact compared to opaque rendering. See also [member blend_mode]. </member> <member name="use_particle_trails" type="bool" setter="set_flag" getter="get_flag" default="false"> If [code]true[/code], enables parts of the shader required for [GPUParticles3D] trails to function. This also requires using a mesh with appropriate skinning, such as [RibbonTrailMesh] or [TubeTrailMesh]. Enabling this feature outside of materials used in [GPUParticles3D] meshes will break material rendering. diff --git a/doc/classes/CollisionPolygon2D.xml b/doc/classes/CollisionPolygon2D.xml index f290fc9801..64a2e89b4b 100644 --- a/doc/classes/CollisionPolygon2D.xml +++ b/doc/classes/CollisionPolygon2D.xml @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CollisionPolygon2D" inherits="Node2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Defines a 2D collision polygon. + Node that represents a 2D collision polygon. </brief_description> <description> - Provides a concave or convex 2D collision polygon to a [CollisionObject2D] parent. Polygons can be drawn in the editor or specified by a list of vertices. See also [ConvexPolygonShape2D]. + Provides a 2D collision polygon to a [CollisionObject2D] parent. Polygons can be drawn in the editor or specified by a list of vertices. + Depending on the build mode, this node effectively provides several convex shapes (by convex decomposition of the polygon) or a single concave shape made of the polygon's segments. In the editor, a [CollisionPolygon2D] can be generated from a [Sprite2D]'s outline by selecting a [Sprite2D] node, going to the [b]Sprite2D[/b] menu at the top of the 2D editor viewport then choosing [b]Create CollisionPolygon2D Sibling[/b]. </description> <tutorials> @@ -24,15 +25,16 @@ The margin used for one-way collision (in pixels). Higher values will make the shape thicker, and work better for colliders that enter the polygon at a high velocity. </member> <member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array()"> - The polygon's list of vertices. The final point will be connected to the first. The returned value is a clone of the [PackedVector2Array], not a reference. + The polygon's list of vertices. Each point will be connected to the next, and the final point will be connected to the first. + [b]Warning:[/b] The returned value is a clone of the [PackedVector2Array], not a reference. </member> </members> <constants> <constant name="BUILD_SOLIDS" value="0" enum="BuildMode"> - Collisions will include the polygon and its contained area. + Collisions will include the polygon and its contained area. In this mode the node has the same effect as several [ConvexPolygonShape2D] nodes, one for each convex shape in the convex decomposition of the polygon (but without the overhead of multiple nodes). </constant> <constant name="BUILD_SEGMENTS" value="1" enum="BuildMode"> - Collisions will only include the polygon edges. + Collisions will only include the polygon edges. In this mode the node has the same effect as a single [ConcavePolygonShape2D] made of segments, with the restriction that each segment (after the first one) starts where the previous one ends, and the last one ends where the first one starts (forming a closed but hollow polygon). </constant> </constants> </class> diff --git a/doc/classes/CollisionPolygon3D.xml b/doc/classes/CollisionPolygon3D.xml index 29e55367a8..39f77bde39 100644 --- a/doc/classes/CollisionPolygon3D.xml +++ b/doc/classes/CollisionPolygon3D.xml @@ -1,18 +1,18 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CollisionPolygon3D" inherits="Node3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Editor-only node for defining a collision polygon in 3D space. + Node that represents a 3D collision polygon, given by the thickening of a 2D polygon in the local XY plane along the local Z axis. </brief_description> <description> - Allows editing a concave or convex collision polygon's vertices on a selected plane. Can also set a depth perpendicular to that plane. This class is only available in the editor. It will not appear in the scene tree at run-time. Creates several [ConvexPolygonShape3D]s at run-time to represent the original polygon using convex decomposition. - [b]Note:[/b] Since this is an editor-only helper, properties modified during gameplay will have no effect. + Provides a 3D collision polygon to a [CollisionObject3D] parent, by thickening a 2D (convex or concave) polygon in the local XY plane along the local Z axis. The 2D polygon in the local XY plane can be drawn in the editor or specified by a list of vertices. That 2D polygon is thickened evenly in the local Z and -Z directions. + This node has the same effect as several [ConvexPolygonShape3D] nodes, created by thickening the 2D convex polygons in the convex decomposition of the given 2D polygon (but without the overhead of multiple nodes). [b]Warning:[/b] A non-uniformly scaled CollisionPolygon3D node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change its [member polygon]'s vertices instead. </description> <tutorials> </tutorials> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0"> - Length that the resulting collision extends in either direction perpendicular to its polygon. + Length that the resulting collision extends in either direction perpendicular to its 2D polygon. </member> <member name="disabled" type="bool" setter="set_disabled" getter="is_disabled" default="false"> If [code]true[/code], no collision will be produced. @@ -21,7 +21,7 @@ The collision margin for the generated [Shape3D]. See [member Shape3D.margin] for more details. </member> <member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array()"> - Array of vertices which define the polygon. + Array of vertices which define the 2D polygon in the local XY plane. [b]Note:[/b] The returned value is a copy of the original. Methods which mutate the size or properties of the return value will not impact the original polygon. To change properties of the polygon, assign it to a temporary variable and make changes before reassigning the [code]polygon[/code] member. </member> </members> diff --git a/doc/classes/CollisionShape2D.xml b/doc/classes/CollisionShape2D.xml index 75530370bc..67807ccd06 100644 --- a/doc/classes/CollisionShape2D.xml +++ b/doc/classes/CollisionShape2D.xml @@ -4,7 +4,7 @@ Node that represents collision shape data in 2D space. </brief_description> <description> - Editor facility for creating and editing collision shapes in 2D space. Set the [member shape] property to configure the shape. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method CollisionObject2D.shape_owner_get_shape] to get the actual shape. + Editor facility for creating and editing collision shapes in 2D space. Set the [member shape] property to configure the shape. You can use this node to represent all sorts of collision shapes, for example, add this to an [Area2D] to give it a detection shape, or add it to a [PhysicsBody2D] to create a solid object. </description> <tutorials> diff --git a/doc/classes/CollisionShape3D.xml b/doc/classes/CollisionShape3D.xml index c5d05670e9..1d5d640f8c 100644 --- a/doc/classes/CollisionShape3D.xml +++ b/doc/classes/CollisionShape3D.xml @@ -4,7 +4,7 @@ Node that represents collision shape data in 3D space. </brief_description> <description> - Editor facility for creating and editing collision shapes in 3D space. Set the [member shape] property to configure the shape. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method CollisionObject3D.shape_owner_get_shape] to get the actual shape. + Editor facility for creating and editing collision shapes in 3D space. Set the [member shape] property to configure the shape. You can use this node to represent all sorts of collision shapes, for example, add this to an [Area3D] to give it a detection shape, or add it to a [PhysicsBody3D] to create a solid object. [b]Warning:[/b] A non-uniformly scaled CollisionShape3D node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size of its [member shape] resource instead. </description> diff --git a/doc/classes/ConcavePolygonShape2D.xml b/doc/classes/ConcavePolygonShape2D.xml index ee3e4f6de4..afd2d0a8e0 100644 --- a/doc/classes/ConcavePolygonShape2D.xml +++ b/doc/classes/ConcavePolygonShape2D.xml @@ -4,16 +4,19 @@ Concave polygon shape resource for 2D physics. </brief_description> <description> - 2D concave polygon shape to be added as a [i]direct[/i] child of a [PhysicsBody2D] or [Area2D] using a [CollisionShape2D] node. It is made out of segments and is optimal for complex polygonal concave collisions. However, it is not advised to use for [RigidBody2D] nodes. A CollisionPolygon2D in convex decomposition mode (solids) or several convex objects are advised for that instead. Otherwise, a concave polygon 2D shape is better for static collisions. - The main difference between a [ConvexPolygonShape2D] and a [ConcavePolygonShape2D] is that a concave polygon assumes it is concave and uses a more complex method of collision detection, and a convex one forces itself to be convex to speed up collision detection. - [b]Performance:[/b] Due to its complexity, [ConcavePolygonShape2D] is the slowest collision shape to check collisions against. Its use should generally be limited to level geometry. For convex geometry, using [ConvexPolygonShape2D] will perform better. For dynamic physics bodies that need concave collision, several [ConvexPolygonShape2D]s can be used to represent its collision by using convex decomposition; see [ConvexPolygonShape2D]'s documentation for instructions. However, consider using primitive collision shapes such as [CircleShape2D] or [RectangleShape2D] first. + 2D concave polygon shape to be added as a [i]direct[/i] child of a [PhysicsBody2D] or [Area2D] using a [CollisionShape2D] node. + The shape consists of a collection of line segments, and as such it does not include any "inside" that the segments might be enclosing. If the segments do enclose anything, then the shape is [i]hollow[/i], as opposed to a [ConvexPolygonShape2D] which is solid. See also [CollisionPolygon2D]. + Being made out of line segments, this shape is the most freely configurable single 2D shape. It can be used to form (hollow) polygons of any nature, convex or concave. + [b]Note:[/b] When used for collision, [b]ConcavePolygonShape2D[/b] is intended to work with static [PhysicsBody2D] nodes like [StaticBody2D] and is not recommended to use with [RigidBody2D] nodes in a mode other than Static. A [CollisionPolygon2D] in convex decomposition mode (solids) or several convex objects are advised for that instead. Otherwise, a concave polygon 2D shape is better suited for static bodies. + [b]Warning:[/b] The nature of this shape makes it extra prone to being tunneled through by (small) fast physics bodies. For example, consider a (small) rigid body [i]Ball[/i] traveling toward a static body [i]Box[/i] at high speed. If the box uses a [b]ConcavePolygonShape2D[/b] consisting of four segments, then the ball might end up inside the box or tunnel all the way through the box, if it goes fast enough. This is (partly) because the ball can only collide against the individual segments of the hollow box. In interactions with rigid bodies tunneling can be avoided by enabling continuous collision detection on the rigid body. [b]Warning:[/b] Using this shape for an [Area2D] (via a [CollisionShape2D] node) may give unexpected results: the area will only detect collisions with the segments in the [ConcavePolygonShape2D] (and not with any "inside" of the shape, for example). + [b]Performance:[/b] Due to its complexity, [ConcavePolygonShape2D] is the slowest collision shape to check collisions against. Its use should generally be limited to level geometry. For convex geometry, using [ConvexPolygonShape2D] will perform better. For dynamic physics bodies that need concave collision, several [ConvexPolygonShape2D]s can be used to represent its collision by using convex decomposition; see [ConvexPolygonShape2D]'s documentation for instructions. However, consider using primitive collision shapes such as [CircleShape2D] or [RectangleShape2D] first. </description> <tutorials> </tutorials> <members> <member name="segments" type="PackedVector2Array" setter="set_segments" getter="get_segments" default="PackedVector2Array()"> - The array of points that make up the [ConcavePolygonShape2D]'s line segments. + The array of points that make up the [ConcavePolygonShape2D]'s line segments. The array (of length divisible by two) is naturally divided into pairs (one pair for each segment); each pair consists of the starting point of a segment and the endpoint of a segment. </member> </members> </class> diff --git a/doc/classes/ConcavePolygonShape3D.xml b/doc/classes/ConcavePolygonShape3D.xml index f01ca8efaf..70634242b7 100644 --- a/doc/classes/ConcavePolygonShape3D.xml +++ b/doc/classes/ConcavePolygonShape3D.xml @@ -4,10 +4,13 @@ Concave polygon shape resource (also called "trimesh") for 3D physics. </brief_description> <description> - 3D concave polygon shape resource (also called "trimesh") to be added as a [i]direct[/i] child of a [PhysicsBody3D] or [Area3D] using a [CollisionShape3D] node. This shape is created by feeding a list of triangles. Despite its name, [ConcavePolygonShape3D] can also store convex polygon shapes. However, unlike [ConvexPolygonShape3D], [ConcavePolygonShape3D] is [i]not[/i] limited to storing convex shapes exclusively. - [b]Note:[/b] When used for collision, [ConcavePolygonShape3D] is intended to work with static [PhysicsBody3D] nodes like [StaticBody3D] and will not work with [CharacterBody3D] or [RigidBody3D] with a mode other than Static. - [b]Performance:[/b] Due to its complexity, [ConcavePolygonShape3D] is the slowest collision shape to check collisions against. Its use should generally be limited to level geometry. For convex geometry, using [ConvexPolygonShape3D] will perform better. For dynamic physics bodies that need concave collision, several [ConvexPolygonShape3D]s can be used to represent its collision by using convex decomposition; see [ConvexPolygonShape3D]'s documentation for instructions. However, consider using primitive collision shapes such as [SphereShape3D] or [BoxShape3D] first. + 3D concave polygon shape resource (also called "trimesh") to be added as a [i]direct[/i] child of a [PhysicsBody3D] or [Area3D] using a [CollisionShape3D] node. + The shape consists of a collection of triangle faces, and as such it does not include any "inside" that the faces might be enclosing. If the faces enclose anything, then the shape is [i]hollow[/i], as opposed to a [ConvexPolygonShape3D] which is solid. See also [CollisionPolygon3D]. + Being made out of triangle faces, this shape is the most freely configurable single 3D shape. Despite its name, it can be used to form (hollow) polyhedra of any nature, convex or concave. + [b]Note:[/b] When used for collision, [b]ConcavePolygonShape3D[/b] is intended to work with static [PhysicsBody3D] nodes like [StaticBody3D] and will not work with [CharacterBody3D] or [RigidBody3D] in a mode other than Static. + [b]Warning:[/b] The nature of this shape makes it extra prone to being tunneled through by (small) fast physics bodies. For example, consider a (small) rigid body [i]Ball[/i] traveling toward a static body [i]Box[/i] at high speed. If the box uses a [b]ConcavePolygonShape3D[/b] consisting of twelve triangle faces (two triangle faces for each of the six sides of the box), then the ball might end up inside the box or tunnel all the way through the box, if it goes fast enough. This is (partly) because the ball can only collide against the individual faces of the hollow box. In interactions with rigid bodies tunneling can be avoided by enabling continuous collision detection on the rigid body. [b]Warning:[/b] Using this shape for an [Area3D] (via a [CollisionShape3D] node, created e.g. by using the [i]Create Trimesh Collision Sibling[/i] option in the [i]Mesh[/i] menu that appears when selecting a [MeshInstance3D] node) may give unexpected results: the area will only detect collisions with the triangle faces in the [ConcavePolygonShape3D] (and not with any "inside" of the shape, for example); moreover it will only detect all such collisions if [member backface_collision] is [code]true[/code]. + [b]Performance:[/b] Due to its complexity, [ConcavePolygonShape3D] is the slowest collision shape to check collisions against. Its use should generally be limited to level geometry. For convex geometry, using [ConvexPolygonShape3D] will perform better. For dynamic physics bodies that need concave collision, several [ConvexPolygonShape3D]s can be used to represent its collision by using convex decomposition; see [ConvexPolygonShape3D]'s documentation for instructions. However, consider using primitive collision shapes such as [SphereShape3D] or [BoxShape3D] first. </description> <tutorials> <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> @@ -16,14 +19,14 @@ <method name="get_faces" qualifiers="const"> <return type="PackedVector3Array" /> <description> - Returns the faces (an array of triangles). + Returns the faces of the trimesh shape as an array of vertices. The array (of length divisible by three) is naturally divided into triples; each triple of vertices defines a triangle. </description> </method> <method name="set_faces"> <return type="void" /> <param index="0" name="faces" type="PackedVector3Array" /> <description> - Sets the faces (an array of triangles). + Sets the faces of the trimesh shape from an array of vertices. The [param faces] array should be composed of triples such that each triple of vertices defines a triangle. </description> </method> </methods> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index f2ab6cb07e..83797f793b 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -399,7 +399,7 @@ <return type="float" /> <param index="0" name="offset" type="int" enum="Side" /> <description> - Returns the anchor for the specified [enum Side]. A getter method for [member offset_bottom], [member offset_left], [member offset_right] and [member offset_top]. + Returns the offset for the specified [enum Side]. A getter method for [member offset_bottom], [member offset_left], [member offset_right] and [member offset_top]. </description> </method> <method name="get_parent_area_size" qualifiers="const"> @@ -840,7 +840,7 @@ <param index="0" name="side" type="int" enum="Side" /> <param index="1" name="neighbor" type="NodePath" /> <description> - Sets the anchor for the specified [enum Side] to the [Control] at [param neighbor] node path. A setter method for [member focus_neighbor_bottom], [member focus_neighbor_left], [member focus_neighbor_right] and [member focus_neighbor_top]. + Sets the focus neighbor for the specified [enum Side] to the [Control] at [param neighbor] node path. A setter method for [member focus_neighbor_bottom], [member focus_neighbor_left], [member focus_neighbor_right] and [member focus_neighbor_top]. </description> </method> <method name="set_global_position"> diff --git a/doc/classes/ConvexPolygonShape2D.xml b/doc/classes/ConvexPolygonShape2D.xml index 862626d9b6..146dac3e74 100644 --- a/doc/classes/ConvexPolygonShape2D.xml +++ b/doc/classes/ConvexPolygonShape2D.xml @@ -4,9 +4,11 @@ Convex polygon shape resource for 2D physics. </brief_description> <description> - 2D convex polygon shape to be added as a [i]direct[/i] child of a [PhysicsBody2D] or [Area2D] using a [CollisionShape2D] node. A convex polygon, whatever its shape, is internally decomposed into as many convex polygons as needed to ensure all collision checks against it are always done on convex polygons (which are faster to check). See also [CollisionPolygon2D]. - The main difference between a [ConvexPolygonShape2D] and a [ConcavePolygonShape2D] is that a concave polygon assumes it is concave and uses a more complex method of collision detection, and a convex one forces itself to be convex to speed up collision detection. - [b]Performance:[/b] [ConvexPolygonShape2D] is faster to check collisions against compared to [ConcavePolygonShape2D], but it is slower than primitive collision shapes such as [CircleShape2D] or [RectangleShape2D]. Its use should generally be limited to medium-sized objects that cannot have their collision accurately represented by a primitive shape. + 2D convex polygon shape to be added as a [i]direct[/i] child of a [PhysicsBody2D] or [Area2D] using a [CollisionShape2D] node. + The shape is a [i]solid[/i] that includes all the points that it encloses, as opposed to [ConcavePolygonShape2D] which is hollow if it encloses anything. See also [CollisionPolygon2D]. + The solid nature of the shape makes it well-suited for both detection and physics; in physics body interactions this allows depenetrating even those shapes which end up (e.g. due to high speed) fully inside the convex shape (similarly to primitive shapes, but unlike [ConcavePolygonShape2D]). The convexity limits the possible geometric shape of a single [ConvexPolygonShape2D]: it cannot be concave. + [b]Convex decomposition:[/b] Concave objects' collisions can be represented accurately using [i]several[/i] convex shapes. This allows dynamic physics bodies to have complex concave collisions (at a performance cost). It can be achieved using several [ConvexPolygonShape2D] nodes or by using the [CollisionPolygon2D] node in Solids build mode. To generate a collision polygon from a sprite, select the [Sprite2D] node, go to the [b]Sprite2D[/b] menu that appears above the viewport, and choose [b]Create Polygon2D Sibling[/b]. + [b]Performance:[/b] [ConvexPolygonShape2D] is faster to check collisions against compared to [ConcavePolygonShape2D], but it is slower than primitive collision shapes such as [CircleShape2D] or [RectangleShape2D]. Its use should generally be limited to medium-sized objects that cannot have their collision accurately represented by primitive shapes. </description> <tutorials> </tutorials> @@ -15,13 +17,14 @@ <return type="void" /> <param index="0" name="point_cloud" type="PackedVector2Array" /> <description> - Based on the set of points provided, this creates and assigns the [member points] property using the convex hull algorithm. Removing all unneeded points. See [method Geometry2D.convex_hull] for details. + Based on the set of points provided, this assigns the [member points] property using the convex hull algorithm, removing all unneeded points. See [method Geometry2D.convex_hull] for details. </description> </method> </methods> <members> <member name="points" type="PackedVector2Array" setter="set_points" getter="get_points" default="PackedVector2Array()"> - The polygon's list of vertices. Can be in either clockwise or counterclockwise order. Only set this property with convex hull points, use [method set_point_cloud] to generate a convex hull shape from concave shape points. + The polygon's list of vertices that form a convex hull. Can be in either clockwise or counterclockwise order. + [b]Warning:[/b] Only set this property to a list of points that actually form a convex hull. Use [method set_point_cloud] to generate the convex hull of an arbitrary set of points. </member> </members> </class> diff --git a/doc/classes/ConvexPolygonShape3D.xml b/doc/classes/ConvexPolygonShape3D.xml index 66d2280280..e97cad4497 100644 --- a/doc/classes/ConvexPolygonShape3D.xml +++ b/doc/classes/ConvexPolygonShape3D.xml @@ -4,9 +4,11 @@ Convex polygon shape resource for 3D physics. </brief_description> <description> - 3D convex polygon shape resource to be added as a [i]direct[/i] child of a [PhysicsBody3D] or [Area3D] using a [CollisionShape3D] node. Unlike [ConcavePolygonShape3D], [ConvexPolygonShape3D] cannot store concave polygon shapes. [ConvexPolygonShape3D]s can be manually drawn in the editor using the [CollisionPolygon3D] node. - [b]Convex decomposition:[/b] Concave objects' collisions can be represented accurately using [i]several[/i] [ConvexPolygonShape3D]s. This allows dynamic physics bodies to have complex concave collisions (at a performance cost). This is available in the editor by selecting the [MeshInstance3D], going to the [b]Mesh[/b] menu and choosing [b]Create Multiple Convex Collision Siblings[/b]. Alternatively, [method MeshInstance3D.create_multiple_convex_collisions] can be called in a script to perform this decomposition at run-time. - [b]Performance:[/b] [ConvexPolygonShape3D] is faster to check collisions against compared to [ConcavePolygonShape3D], but it is slower than primitive collision shapes such as [SphereShape3D] or [BoxShape3D]. Its use should generally be limited to medium-sized objects that cannot have their collision accurately represented by a primitive shape. + 3D convex polygon shape resource to be added as a [i]direct[/i] child of a [PhysicsBody3D] or [Area3D] using a [CollisionShape3D] node. + The shape is a [i]solid[/i] that includes all the points that it encloses, as opposed to [ConcavePolygonShape3D] which is hollow if it encloses anything. See also [CollisionPolygon3D]. + The solid nature of the shape makes it well-suited for both detection and physics; in physics body interactions this allows depenetrating even those shapes which end up (e.g. due to high speed) fully inside the convex shape (similarly to primitive shapes, but unlike [ConcavePolygonShape3D] and [HeightMapShape3D]). The convexity restricts the possible geometric shape of a single [ConvexPolygonShape3D]: it cannot be concave. + [b]Convex decomposition:[/b] Concave objects' collisions can be represented accurately using [i]several[/i] convex shapes. This allows dynamic physics bodies to have complex concave collisions (at a performance cost). It can be achieved by using several [ConvexPolygonShape3D] nodes or by using the [CollisionPolygon3D] node. To generate a collision polygon from a mesh, select the [MeshInstance3D] node, go to the [b]Mesh[/b] menu that appears above the viewport and choose [b]Create Multiple Convex Collision Siblings[/b]. Alternatively, [method MeshInstance3D.create_multiple_convex_collisions] can be called in a script to perform this decomposition at run-time. + [b]Performance:[/b] [ConvexPolygonShape3D] is faster to check collisions against compared to [ConcavePolygonShape3D], but it is slower than primitive collision shapes such as [SphereShape3D] or [BoxShape3D]. Its use should generally be limited to medium-sized objects that cannot have their collision accurately represented by primitive shapes. </description> <tutorials> <link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link> diff --git a/doc/classes/EditorPaths.xml b/doc/classes/EditorPaths.xml index 6733212208..0d02685f98 100644 --- a/doc/classes/EditorPaths.xml +++ b/doc/classes/EditorPaths.xml @@ -64,7 +64,9 @@ <return type="bool" /> <description> Returns [code]true[/code] if the editor is marked as self-contained, [code]false[/code] otherwise. When self-contained mode is enabled, user configuration, data and cache files are saved in an [code]editor_data/[/code] folder next to the editor binary. This makes portable usage easier and ensures the Godot editor minimizes file writes outside its own folder. Self-contained mode is not available for exported projects. - Self-contained mode can be enabled by creating a file named [code]._sc_[/code] or [code]_sc_[/code] in the same folder as the editor binary while the editor is not running. See also [method get_self_contained_file]. + Self-contained mode can be enabled by creating a file named [code]._sc_[/code] or [code]_sc_[/code] in the same folder as the editor binary or macOS .app bundle while the editor is not running. See also [method get_self_contained_file]. + [b]Note:[/b] On macOS, quarantine flag should be manually removed before using self-contained mode, see [url=https://docs.godotengine.org/en/stable/tutorials/export/running_on_macos.html]Running on macOS[/url]. + [b]Note:[/b] On macOS, placing [code]_sc_[/code] or any other file inside .app bundle will break digital signature and make it non-portable, consider placing it in the same folder as the .app bundle instead. [b]Note:[/b] The Steam release of Godot uses self-contained mode by default. </description> </method> diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index 06ce32597f..4433e73547 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -109,7 +109,7 @@ </methods> <members> <member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="false"> - If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer2D]. When [method NavigationAgent2D.set_velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector2 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. + If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer2D]. When [method set_velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector2 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. </member> <member name="debug_enabled" type="bool" setter="set_debug_enabled" getter="get_debug_enabled" default="false"> If [code]true[/code] shows debug visuals for this agent. @@ -193,7 +193,7 @@ <signal name="velocity_computed"> <param index="0" name="safe_velocity" type="Vector2" /> <description> - Notifies when the collision avoidance velocity is calculated. Emitted by [method set_velocity]. Only emitted when [member avoidance_enabled] is true. + Notifies when the collision avoidance velocity is calculated. Emitted at the end of the physics frame in which [method set_velocity] is called. Only emitted when [member avoidance_enabled] is true. </description> </signal> <signal name="waypoint_reached"> diff --git a/doc/classes/NavigationAgent3D.xml b/doc/classes/NavigationAgent3D.xml index d2151773da..962573ea5a 100644 --- a/doc/classes/NavigationAgent3D.xml +++ b/doc/classes/NavigationAgent3D.xml @@ -112,7 +112,7 @@ The NavigationAgent height offset is subtracted from the y-axis value of any vector path position for this NavigationAgent. The NavigationAgent height offset does not change or influence the navigation mesh or pathfinding query result. Additional navigation maps that use regions with navigation meshes that the developer baked with appropriate agent radius or height values are required to support different-sized agents. </member> <member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="false"> - If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer3D]. When [method NavigationAgent3D.set_velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector3 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. + If [code]true[/code] the agent is registered for an RVO avoidance callback on the [NavigationServer3D]. When [method set_velocity] is used and the processing is completed a [code]safe_velocity[/code] Vector3 is received with a signal connection to [signal velocity_computed]. Avoidance processing with many registered agents has a significant performance cost and should only be enabled on agents that currently require it. </member> <member name="debug_enabled" type="bool" setter="set_debug_enabled" getter="get_debug_enabled" default="false"> If [code]true[/code] shows debug visuals for this agent. @@ -136,7 +136,7 @@ The maximum speed that an agent can move. </member> <member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1"> - A bitfield determining what navigation layers of navigation regions this NavigationAgent will use to calculate path. Changing it runtime will clear current navigation path and generate new one, according to new navigation layers. + A bitfield determining what navigation layers of navigation regions this agent will use to calculate path. Changing it runtime will clear current navigation path and generate new one, according to new navigation layers. </member> <member name="neighbor_distance" type="float" setter="set_neighbor_distance" getter="get_neighbor_distance" default="50.0"> The distance to search for other agents. @@ -196,7 +196,7 @@ <signal name="velocity_computed"> <param index="0" name="safe_velocity" type="Vector3" /> <description> - Notifies when the collision avoidance velocity is calculated. Emitted by [method set_velocity]. Only emitted when [member avoidance_enabled] is true. + Notifies when the collision avoidance velocity is calculated. Emitted at the end of the physics frame in which [method set_velocity] is called. Only emitted when [member avoidance_enabled] is true. </description> </signal> <signal name="waypoint_reached"> diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml index c82ed5f143..a670301268 100644 --- a/doc/classes/NodePath.xml +++ b/doc/classes/NodePath.xml @@ -141,7 +141,7 @@ <return type="StringName" /> <param index="0" name="idx" type="int" /> <description> - Gets the resource or property name indicated by [param idx] (0 to [method get_subname_count]). + Gets the resource or property name indicated by [param idx] (0 to [method get_subname_count] - 1). [codeblocks] [gdscript] var node_path = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path") diff --git a/doc/classes/PhysicsDirectSpaceState2D.xml b/doc/classes/PhysicsDirectSpaceState2D.xml index d4cb073d15..588887d491 100644 --- a/doc/classes/PhysicsDirectSpaceState2D.xml +++ b/doc/classes/PhysicsDirectSpaceState2D.xml @@ -21,7 +21,7 @@ </description> </method> <method name="collide_shape"> - <return type="PackedVector2Array[]" /> + <return type="Vector2[]" /> <param index="0" name="parameters" type="PhysicsShapeQueryParameters2D" /> <param index="1" name="max_results" type="int" default="32" /> <description> diff --git a/doc/classes/PhysicsDirectSpaceState3D.xml b/doc/classes/PhysicsDirectSpaceState3D.xml index f7bc74d9ad..b9f2a12245 100644 --- a/doc/classes/PhysicsDirectSpaceState3D.xml +++ b/doc/classes/PhysicsDirectSpaceState3D.xml @@ -21,7 +21,7 @@ </description> </method> <method name="collide_shape"> - <return type="PackedVector3Array[]" /> + <return type="Vector3[]" /> <param index="0" name="parameters" type="PhysicsShapeQueryParameters3D" /> <param index="1" name="max_results" type="int" default="32" /> <description> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 82464d2f99..46ffc3eabe 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -119,6 +119,13 @@ <param index="0" name="area" type="RID" /> <param index="1" name="callback" type="Callable" /> <description> + Sets the area's area monitor callback. This callback will be called when any other (shape of an) area enters or exits (a shape of) the given area, and must take the following five parameters: + 1. an integer [code]status[/code]: either [constant AREA_BODY_ADDED] or [constant AREA_BODY_REMOVED] depending on whether the other area's shape entered or exited the area, + 2. an [RID] [code]area_rid[/code]: the [RID] of the other area that entered or exited the area, + 3. an integer [code]instance_id[/code]: the [code]ObjectID[/code] attached to the other area, + 4. an integer [code]area_shape_idx[/code]: the index of the shape of the other area that entered or exited the area, + 5. an integer [code]self_shape_idx[/code]: the index of the shape of the area where the other area entered or exited. + By counting (or keeping track of) the shapes that enter and exit, it can be determined if an area (with all its shapes) is entering for the first time or exiting for the last time. </description> </method> <method name="area_set_collision_layer"> @@ -142,12 +149,13 @@ <param index="0" name="area" type="RID" /> <param index="1" name="callback" type="Callable" /> <description> - Sets the function to call when any body/area enters or exits the area. This callback will be called for any object interacting with the area, and takes five parameters: - 1: [constant AREA_BODY_ADDED] or [constant AREA_BODY_REMOVED], depending on whether the object entered or exited the area. - 2: [RID] of the object that entered/exited the area. - 3: Instance ID of the object that entered/exited the area. - 4: The shape index of the object that entered/exited the area. - 5: The shape index of the area where the object entered/exited. + Sets the area's body monitor callback. This callback will be called when any other (shape of a) body enters or exits (a shape of) the given area, and must take the following five parameters: + 1. an integer [code]status[/code]: either [constant AREA_BODY_ADDED] or [constant AREA_BODY_REMOVED] depending on whether the other body shape entered or exited the area, + 2. an [RID] [code]body_rid[/code]: the [RID] of the body that entered or exited the area, + 3. an integer [code]instance_id[/code]: the [code]ObjectID[/code] attached to the body, + 4. an integer [code]body_shape_idx[/code]: the index of the shape of the body that entered or exited the area, + 5. an integer [code]self_shape_idx[/code]: the index of the shape of the area where the body entered or exited. + By counting (or keeping track of) the shapes that enter and exit, it can be determined if a body (with all its shapes) is entering for the first time or exiting for the last time. </description> </method> <method name="area_set_monitorable"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 27bec07cba..ed227047e5 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1199,7 +1199,10 @@ [b]Note:[/b] This property is only read when the project starts. To toggle pseudolocalization at run-time, use [member TranslationServer.pseudolocalization_enabled] instead. </member> <member name="internationalization/rendering/force_right_to_left_layout_direction" type="bool" setter="" getter="" default="false"> - Force layout direction and text writing direction to RTL for all locales. + Force layout direction and text writing direction to RTL for all controls. + </member> + <member name="internationalization/rendering/root_node_layout_direction" type="int" setter="" getter="" default="0"> + Root node default layout direction. </member> <member name="internationalization/rendering/text_driver" type="String" setter="" getter="" default=""""> Specifies the [TextServer] to use. If left empty, the default will be used. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index e5ab3271dc..2ba8e40ce2 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -587,7 +587,7 @@ <method name="canvas_light_occluder_create"> <return type="RID" /> <description> - Creates a light occluder and adds it to the RenderingServer. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_light_ocluder_*[/code] RenderingServer functions. + Creates a light occluder and adds it to the RenderingServer. It can be accessed with the RID that is returned. This RID will be used in all [code]canvas_light_occluder_*[/code] RenderingServer functions. Once finished with your RID, you will want to free the RID using the RenderingServer's [method free_rid] static method. </description> </method> diff --git a/doc/classes/Shape3D.xml b/doc/classes/Shape3D.xml index 129810943e..02c8ca26f0 100644 --- a/doc/classes/Shape3D.xml +++ b/doc/classes/Shape3D.xml @@ -23,7 +23,7 @@ When set to [code]0[/code], the default value from [member ProjectSettings.physics/3d/solver/default_contact_bias] is used. </member> <member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.04"> - The collision margin for the shape. Used in Bullet Physics only. + The collision margin for the shape. This is not used in Godot Physics. Collision margins allow collision detection to be more efficient by adding an extra shell around shapes. Collision algorithms are more expensive when objects overlap by more than their margin, so a higher value for margins is better for performance, at the cost of accuracy around edges as it makes them less sharp. </member> </members> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index ad37cf5c86..6d71578488 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1154,7 +1154,7 @@ <param index="1" name="width" type="float" /> <param index="2" name="jst_flags" type="int" enum="TextServer.JustificationFlag" default="3" /> <description> - Adjusts text with to fit to specified width, returns new text width. + Adjusts text width to fit to specified width, returns new text width. </description> </method> <method name="shaped_text_get_ascent" qualifiers="const"> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 8b48bd4c88..fe85ad7df6 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -226,7 +226,7 @@ <method name="is_normalized" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the vector is normalized, [code]false[/code] otherwise. + Returns [code]true[/code] if the vector is normalized, i.e. its length is approximately equal to 1. </description> </method> <method name="is_zero_approx" qualifiers="const"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 91553c3acd..2f55e6a2c6 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -194,7 +194,7 @@ <method name="is_normalized" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the vector is [method normalized], [code]false[/code] otherwise. + Returns [code]true[/code] if the vector is normalized, i.e. its length is approximately equal to 1. </description> </method> <method name="is_zero_approx" qualifiers="const"> diff --git a/doc/classes/Vector4.xml b/doc/classes/Vector4.xml index 880e6e4e19..7f71dc23ec 100644 --- a/doc/classes/Vector4.xml +++ b/doc/classes/Vector4.xml @@ -145,7 +145,7 @@ <method name="is_normalized" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the vector is normalized, i.e. its length is equal to 1. + Returns [code]true[/code] if the vector is normalized, i.e. its length is approximately equal to 1. </description> </method> <method name="is_zero_approx" qualifiers="const"> diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index d940ea41ac..c7ba94b1b4 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -108,6 +108,10 @@ <member name="primary_interface" type="XRInterface" setter="set_primary_interface" getter="get_primary_interface"> The primary [XRInterface] currently bound to the [XRServer]. </member> + <member name="world_origin" type="Transform3D" setter="set_world_origin" getter="get_world_origin" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)"> + The current origin of our tracking space in the virtual world. This is used by the renderer to properly position the camera with new tracking data. + [b]Note:[/b] This property is managed by the current [XROrigin3D] node. It is exposed for access from GDExtensions. + </member> <member name="world_scale" type="float" setter="set_world_scale" getter="get_world_scale" default="1.0"> Allows you to adjust the scale to your game's units. Most AR/VR platforms assume a scale of 1 game world unit = 1 real world meter. </member> diff --git a/doc/classes/int.xml b/doc/classes/int.xml index 93fc8386c6..d5cb60e58d 100644 --- a/doc/classes/int.xml +++ b/doc/classes/int.xml @@ -1,40 +1,38 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="int" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Integer built-in type. + Built-in integer Variant type. </brief_description> <description> - Signed 64-bit integer type. - It can take values in the interval [code][-2^63, 2^63 - 1][/code], i.e. [code][-9223372036854775808, 9223372036854775807][/code]. Exceeding those bounds will wrap around. - [int] is a [Variant] type, and will thus be used when assigning an integer value to a [Variant]. It can also be enforced with the [code]: int[/code] type hint. + Signed 64-bit integer type. This means that it can take values from [code]-2^63[/code] to [code]2^63 - 1[/code], i.e. from [code]-9223372036854775808[/code] to [code]9223372036854775807[/code]. When it exceeds these bounds, it will wrap around. + [int]s can be automatically converted to [float]s when necessary, for example when passing them as arguments in functions. The [float] will be as close to the original integer as possible. + Likewise, [float]s can be automatically converted into [int]s. This will truncate the [float], discarding anything after the floating point. + [b]Note:[/b] In a boolean context, an [int] will evaluate to [code]false[/code] if it equals [code]0[/code], and to [code]true[/code] otherwise. [codeblocks] [gdscript] - var my_variant = 0 # int, value 0. - my_variant += 4.2 # float, value 4.2. - var my_int: int = 1 # int, value 1. - my_int = 4.2 # int, value 4, the right value is implicitly cast to int. - my_int = int("6.7") # int, value 6, the String is explicitly cast with int. - var max_int = 9223372036854775807 - print(max_int) # 9223372036854775807, OK. - max_int += 1 - print(max_int) # -9223372036854775808, we overflowed and wrapped around. + var x: int = 1 # x is 1 + x = 4.2 # x is 4, because 4.2 gets truncated + var max_int = 9223372036854775807 # Biggest value an int can store + max_int += 1 # max_int is -9223372036854775808, because it wrapped around [/gdscript] [csharp] - int myInt = (int)"6.7".ToFloat(); // int, value 6, the String is explicitly cast with int. - // We have to use `long` here, because GDSript's `int` - // is 64 bits long while C#'s `int` is only 32 bits. - long maxInt = 9223372036854775807; - GD.Print(maxInt); // 9223372036854775807, OK. - maxInt++; - GD.Print(maxInt); // -9223372036854775808, we overflowed and wrapped around. + int x = 1; // x is 1 + x = 4.2; // x is 4, because 4.2 gets truncated + // We use long below, because GDScript's int is 64-bit while C#'s int is 32-bit. + long maxLong = 9223372036854775807; // Biggest value a long can store + maxLong++; // maxLong is now -9223372036854775808, because it wrapped around. - // Alternatively, if we used C#'s 32-bit `int` type, the maximum value is much smaller: - int halfInt = 2147483647; - GD.Print(halfInt); // 2147483647, OK. - halfInt++; - GD.Print(halfInt); // -2147483648, we overflowed and wrapped around. + // Alternatively with C#'s 32-bit int type, which has a smaller maximum value. + int maxInt = 2147483647; // Biggest value an int can store + maxInt++; // maxInt is now -2147483648, because it wrapped around [/csharp] [/codeblocks] + In GDScript, you can use the [code]0b[/code] literal for binary representation, the [code]0x[/code] literal for hexadecimal representation, and the [code]_[/code] symbol to separate long numbers and improve readability. + [codeblock] + var x = 0b1001 # x is 9 + var y = 0xF5 # y is 245 + var z = 10_000_000 # z is 10000000 + [/codeblock] </description> <tutorials> </tutorials> @@ -42,7 +40,7 @@ <constructor name="int"> <return type="int" /> <description> - Constructs a default-initialized [int] set to [code]0[/code]. + Constructs an [int] set to [code]0[/code]. </description> </constructor> <constructor name="int"> @@ -56,21 +54,21 @@ <return type="int" /> <param index="0" name="from" type="String" /> <description> - Converts a [String] to an [int], following the same rules as [method String.to_int]. + Constructs a new [int] from a [String], following the same rules as [method String.to_int]. </description> </constructor> <constructor name="int"> <return type="int" /> <param index="0" name="from" type="bool" /> <description> - Cast a [bool] value to an integer value, [code]int(true)[/code] will be equals to 1 and [code]int(false)[/code] will be equals to 0. + Constructs a new [int] from a [bool]. [code]true[/code] is converted to [code]1[/code] and [code]false[/code] is converted to [code]0[/code]. </description> </constructor> <constructor name="int"> <return type="int" /> <param index="0" name="from" type="float" /> <description> - Cast a float value to an integer value, this method simply removes the number fractions (i.e. rounds [param from] towards zero), so for example [code]int(2.7)[/code] will be equals to 2, [code]int(0.1)[/code] will be equals to 0 and [code]int(-2.7)[/code] will be equals to -2. This operation is also called truncation. + Constructs a new [int] from a [float]. This will truncate the [float], discarding anything after the floating point. </description> </constructor> </constructors> @@ -79,25 +77,25 @@ <return type="bool" /> <param index="0" name="right" type="float" /> <description> - Returns [code]true[/code] if this [int] is not equivalent to the given [float]. + Returns [code]true[/code] if the [int] is not equivalent to the [float]. </description> </operator> <operator name="operator !="> <return type="bool" /> <param index="0" name="right" type="int" /> <description> - Returns [code]true[/code] if the integers are not equal. + Returns [code]true[/code] if the [int]s are not equal. </description> </operator> <operator name="operator %"> <return type="int" /> <param index="0" name="right" type="int" /> <description> - Returns the remainder after dividing two integers. This operation uses truncated division, which is often not desired as it does not work well with negative numbers. Consider using [method @GlobalScope.posmod] instead if you want to handle negative numbers. + Returns the remainder after dividing two [int]s. Uses truncated division, which returns a negative number if the dividend is negative. If this is not desired, consider using [method @GlobalScope.posmod]. [codeblock] - print(5 % 2) # 1 - print(12 % 4) # 0 - print(-5 % 3) # -2 + print(6 % 2) # Prints 0 + print(11 % 4) # Prints 3 + print(-5 % 3) # Prints -2 [/codeblock] </description> </operator> @@ -105,17 +103,16 @@ <return type="int" /> <param index="0" name="right" type="int" /> <description> - Returns the result of bitwise [code]AND[/code] operation for two integers. + Performs the bitwise [code]AND[/code] operation. [codeblock] - print(3 & 1) # 1 - print(11 & 3) # 3 + print(0b1100 & 0b1010) # Prints 8 (binary 1000) [/codeblock] - It's useful to retrieve binary flags from a variable. + This is useful for retrieving binary flags from a variable. [codeblock] - var flags = 5 - # Do something if the first bit is enabled. - if flags & 1: - do_stuff() + var flags = 0b101 + # Check if the first or second bit are enabled. + if flags & 0b011: + do_stuff() # This line will run. [/codeblock] </description> </operator> @@ -123,23 +120,23 @@ <return type="Color" /> <param index="0" name="right" type="Color" /> <description> - Multiplies each component of the [Color] by the given [int]. + Multiplies each component of the [Color] by the [int]. </description> </operator> <operator name="operator *"> <return type="Quaternion" /> <param index="0" name="right" type="Quaternion" /> <description> - Multiplies each component of the [Quaternion] by the given [int]. This operation is not meaningful on its own, but it can be used as a part of a larger expression. + Multiplies each component of the [Quaternion] by the [int]. This operation is not meaningful on its own, but it can be used as a part of a larger expression. </description> </operator> <operator name="operator *"> <return type="Vector2" /> <param index="0" name="right" type="Vector2" /> <description> - Multiplies each component of the [Vector2] by the given [int]. + Multiplies each component of the [Vector2] by the [int]. [codeblock] - print(2 * Vector2(1, 1)) # Vector2(2, 2) + print(2 * Vector2(1, 4)) # Prints (2, 8) [/codeblock] </description> </operator> @@ -147,49 +144,49 @@ <return type="Vector2i" /> <param index="0" name="right" type="Vector2i" /> <description> - Multiplies each component of the [Vector2i] by the given [int]. + Multiplies each component of the [Vector2i] by the [int]. </description> </operator> <operator name="operator *"> <return type="Vector3" /> <param index="0" name="right" type="Vector3" /> <description> - Multiplies each component of the [Vector3] by the given [int]. + Multiplies each component of the [Vector3] by the [int]. </description> </operator> <operator name="operator *"> <return type="Vector3i" /> <param index="0" name="right" type="Vector3i" /> <description> - Multiplies each component of the [Vector3i] by the given [int]. + Multiplies each component of the [Vector3i] by the [int]. </description> </operator> <operator name="operator *"> <return type="Vector4" /> <param index="0" name="right" type="Vector4" /> <description> - Multiplies each component of the [Vector4] by the given [int]. + Multiplies each component of the [Vector4] by the [int]. </description> </operator> <operator name="operator *"> <return type="Vector4i" /> <param index="0" name="right" type="Vector4i" /> <description> - Multiplies each component of the [Vector4i] by the given [int]. + Multiplies each component of the [Vector4i] by the [int]. </description> </operator> <operator name="operator *"> <return type="float" /> <param index="0" name="right" type="float" /> <description> - Multiplies an [int] and a [float]. The result is a [float]. + Multiplies the [float] by the [int]. The result is a [float]. </description> </operator> <operator name="operator *"> <return type="int" /> <param index="0" name="right" type="int" /> <description> - Multiplies two [int]s. + Multiplies the two [int]s. </description> </operator> <operator name="operator **"> @@ -198,7 +195,7 @@ <description> Raises an [int] to a power of a [float]. The result is a [float]. [codeblock] - print(8**0.25) # 1.68179283050743 + print(2 ** 0.5) # Prints 1.4142135623731 [/codeblock] </description> </operator> @@ -206,9 +203,9 @@ <return type="int" /> <param index="0" name="right" type="int" /> <description> - Raises an [int] to a power of a [int]. + Raises the left [int] to a power of the right [int]. [codeblock] - print(5**5) # 3125 + print(3 ** 4) # Prints 81 [/codeblock] </description> </operator> @@ -216,37 +213,37 @@ <return type="float" /> <param index="0" name="right" type="float" /> <description> - Adds an [int] and a [float]. The result is a [float]. + Adds the [int] and the [float]. The result is a [float]. </description> </operator> <operator name="operator +"> <return type="int" /> <param index="0" name="right" type="int" /> <description> - Adds two integers. + Adds the two [int]s. </description> </operator> <operator name="operator -"> <return type="float" /> <param index="0" name="right" type="float" /> <description> - Subtracts a [float] from an [int]. The result is a [float]. + Subtracts the [float] from the [int]. The result is a [float]. </description> </operator> <operator name="operator -"> <return type="int" /> <param index="0" name="right" type="int" /> <description> - Subtracts two integers. + Subtracts the two [int]s. </description> </operator> <operator name="operator /"> <return type="float" /> <param index="0" name="right" type="float" /> <description> - Divides an [int] by a [float]. The result is a [float]. + Divides the [int] by the [float]. The result is a [float]. [codeblock] - print(10 / 3.0) # 3.333... + print(10 / 3.0) # Prints 3.33333333333333 [/codeblock] </description> </operator> @@ -254,10 +251,10 @@ <return type="int" /> <param index="0" name="right" type="int" /> <description> - Divides two integers. The decimal part of the result is discarded (truncated). + Divides the two [int]s. The result is an [int]. This will truncate the [float], discarding anything after the floating point. [codeblock] - print(10 / 2) # 5 - print(10 / 3) # 3 + print(6 / 2) # Prints 3 + print(5 / 3) # Prints 1 [/codeblock] </description> </operator> @@ -265,24 +262,24 @@ <return type="bool" /> <param index="0" name="right" type="float" /> <description> - Returns [code]true[/code] if this [int] is less than the given [float]. + Returns [code]true[/code] if the [int] is less than the [float]. </description> </operator> <operator name="operator <"> <return type="bool" /> <param index="0" name="right" type="int" /> <description> - Returns [code]true[/code] if the left integer is less than the right one. + Returns [code]true[/code] if the left [int] is less than the right [int]. </description> </operator> <operator name="operator <<"> <return type="int" /> <param index="0" name="right" type="int" /> <description> - Performs bitwise shift left operation on the integer. Effectively the same as multiplying by a power of 2. + Performs the bitwise shift left operation. Effectively the same as multiplying by a power of 2. [codeblock] - print(10 << 1) # 20 - print(10 << 4) # 160 + print(0b1010 << 1) # Prints 20 (binary 10100) + print(0b1010 << 3) # Prints 80 (binary 1010000) [/codeblock] </description> </operator> @@ -290,66 +287,66 @@ <return type="bool" /> <param index="0" name="right" type="float" /> <description> - Returns [code]true[/code] if this [int] is less than or equal to the given [float]. + Returns [code]true[/code] if the [int] is less than or equal to the [float]. </description> </operator> <operator name="operator <="> <return type="bool" /> <param index="0" name="right" type="int" /> <description> - Returns [code]true[/code] if the left integer is less than or equal to the right one. + Returns [code]true[/code] if the left [int] is less than or equal to the right [int]. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="float" /> <description> - Returns [code]true[/code] if the integer is equal to the given [float]. + Returns [code]true[/code] if the [int] is equal to the [float]. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="int" /> <description> - Returns [code]true[/code] if both integers are equal. + Returns [code]true[/code] if the two [int]s are equal. </description> </operator> <operator name="operator >"> <return type="bool" /> <param index="0" name="right" type="float" /> <description> - Returns [code]true[/code] if this [int] is greater than the given [float]. + Returns [code]true[/code] if the [int] is greater than the [float]. </description> </operator> <operator name="operator >"> <return type="bool" /> <param index="0" name="right" type="int" /> <description> - Returns [code]true[/code] if the left integer is greater than the right one. + Returns [code]true[/code] if the left [int] is greater than the right [int]. </description> </operator> <operator name="operator >="> <return type="bool" /> <param index="0" name="right" type="float" /> <description> - Returns [code]true[/code] if this [int] is greater than or equal to the given [float]. + Returns [code]true[/code] if the [int] is greater than or equal to the [float]. </description> </operator> <operator name="operator >="> <return type="bool" /> <param index="0" name="right" type="int" /> <description> - Returns [code]true[/code] if the left integer is greater than or equal to the right one. + Returns [code]true[/code] if the left [int] is greater than or equal to the right [int]. </description> </operator> <operator name="operator >>"> <return type="int" /> <param index="0" name="right" type="int" /> <description> - Performs bitwise shift right operation on the integer. Effectively the same as dividing by a power of 2. + Performs the bitwise shift right operation. Effectively the same as dividing by a power of 2. [codeblock] - print(10 >> 1) # 5 - print(10 >> 2) # 2 + print(0b1010 >> 1) # Prints 5 (binary 101) + print(0b1010 >> 2) # Prints 2 (binary 10) [/codeblock] </description> </operator> @@ -357,10 +354,9 @@ <return type="int" /> <param index="0" name="right" type="int" /> <description> - Returns the result of bitwise [code]XOR[/code] operation for two integers. + Performs the bitwise [code]XOR[/code] operation. [codeblock] - print(5 ^ 1) # 4 - print(4 ^ 7) # 3 + print(0b1100 ^ 0b1010) # Prints 6 (binary 110) [/codeblock] </description> </operator> @@ -380,27 +376,24 @@ <return type="int" /> <param index="0" name="right" type="int" /> <description> - Returns the result of bitwise [code]OR[/code] operation for two integers. + Performs the bitwise [code]OR[/code] operation. [codeblock] - print(2 | 4) # 6 - print(1 | 3) # 3 + print(0b1100 | 0b1010) # Prints 14 (binary 1110) [/codeblock] - It's useful to store binary flags in a variable. + This is useful for storing binary flags in a variable. [codeblock] var flags = 0 - # Turn first and third bit on. - flags |= 1 - flags |= 4 + flags |= 0b101 # Turn the first and third bits on. [/codeblock] </description> </operator> <operator name="operator ~"> <return type="int" /> <description> - Returns the result of bitwise [code]NOT[/code] operation for the integer. It's effectively equal to [code]-int + 1[/code]. + Performs the bitwise [code]NOT[/code] operation on the [int]. Due to [url=https://en.wikipedia.org/wiki/Two%27s_complement/]2's complement[/url], it's effectively equal to [code]-(int + 1)[/code]. [codeblock] - print(~4) # -3 - print(~7) # -6 + print(~4) # Prints -5 + print(~(-7)) # Prints 6 [/codeblock] </description> </operator> diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 6cc6b8224c..b3d6b01c6c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1994,6 +1994,28 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ _draw_sky(render_data.environment, render_data.cam_projection, render_data.cam_transform, sky_energy_multiplier, p_camera_data->view_count > 1, flip_y); } + if (scene_state.used_screen_texture || scene_state.used_depth_texture) { + texture_storage->copy_scene_to_backbuffer(rt, scene_state.used_screen_texture, scene_state.used_depth_texture); + glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rt->backbuffer_fbo); + if (scene_state.used_screen_texture) { + glBlitFramebuffer(0, 0, rt->size.x, rt->size.y, + 0, 0, rt->size.x, rt->size.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 5); + glBindTexture(GL_TEXTURE_2D, rt->backbuffer); + } + if (scene_state.used_depth_texture) { + glBlitFramebuffer(0, 0, rt->size.x, rt->size.y, + 0, 0, rt->size.x, rt->size.y, + GL_DEPTH_BUFFER_BIT, GL_NEAREST); + glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 6); + glBindTexture(GL_TEXTURE_2D, rt->backbuffer_depth); + } + glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo); + } + RENDER_TIMESTAMP("Render 3D Transparent Pass"); glEnable(GL_BLEND); @@ -2206,6 +2228,9 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, } RS::PrimitiveType primitive = surf->primitive; + if (shader->uses_point_size) { + primitive = RS::PRIMITIVE_POINTS; + } static const GLenum prim[5] = { GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP }; GLenum primitive_gl = prim[int(primitive)]; diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 847ae14041..1736ad1d42 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1510,7 +1510,7 @@ MaterialStorage::MaterialStorage() { actions.renames["LIGHT_VERTEX"] = "light_vertex"; actions.renames["SHADOW_VERTEX"] = "shadow_vertex"; actions.renames["UV"] = "uv"; - actions.renames["POINT_SIZE"] = "gl_PointSize"; + actions.renames["POINT_SIZE"] = "point_size"; actions.renames["MODEL_MATRIX"] = "model_matrix"; actions.renames["CANVAS_MATRIX"] = "canvas_transform"; @@ -1591,7 +1591,7 @@ MaterialStorage::MaterialStorage() { actions.renames["UV"] = "uv_interp"; actions.renames["UV2"] = "uv2_interp"; actions.renames["COLOR"] = "color_interp"; - actions.renames["POINT_SIZE"] = "gl_PointSize"; + actions.renames["POINT_SIZE"] = "point_size"; actions.renames["INSTANCE_ID"] = "gl_InstanceID"; actions.renames["VERTEX_ID"] = "gl_VertexID"; diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index dae722186c..4baffa93d5 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1782,7 +1782,64 @@ void TextureStorage::_create_render_target_backbuffer(RenderTarget *rt) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } } +void GLES3::TextureStorage::copy_scene_to_backbuffer(RenderTarget *rt, const bool uses_screen_texture, const bool uses_depth_texture) { + if (rt->backbuffer != 0 && rt->backbuffer_depth != 0) { + return; + } + Config *config = Config::get_singleton(); + bool use_multiview = rt->view_count > 1 && config->multiview_supported; + GLenum texture_target = use_multiview ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D; + if (rt->backbuffer_fbo == 0) { + glGenFramebuffers(1, &rt->backbuffer_fbo); + } + glBindFramebuffer(GL_FRAMEBUFFER, rt->backbuffer_fbo); + if (rt->backbuffer == 0 && uses_screen_texture) { + glGenTextures(1, &rt->backbuffer); + glBindTexture(texture_target, rt->backbuffer); + if (use_multiview) { + glTexImage3D(texture_target, 0, rt->color_internal_format, rt->size.x, rt->size.y, rt->view_count, 0, rt->color_format, rt->color_type, nullptr); + } else { + glTexImage2D(texture_target, 0, rt->color_internal_format, rt->size.x, rt->size.y, 0, rt->color_format, rt->color_type, nullptr); + } + + glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#ifndef IOS_ENABLED + if (use_multiview) { + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->backbuffer, 0, 0, rt->view_count); + } else { +#else + { +#endif + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->backbuffer, 0); + } + } + if (rt->backbuffer_depth == 0 && uses_depth_texture) { + glGenTextures(1, &rt->backbuffer_depth); + glBindTexture(texture_target, rt->backbuffer_depth); + if (use_multiview) { + glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, rt->size.x, rt->size.y, rt->view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + } else { + glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, rt->size.x, rt->size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + } + glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#ifndef IOS_ENABLED + if (use_multiview) { + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->backbuffer_depth, 0, 0, rt->view_count); + } else { +#else + { +#endif + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->backbuffer_depth, 0); + } + } +} void TextureStorage::_clear_render_target(RenderTarget *rt) { // there is nothing to clear when DIRECT_TO_SCREEN is used if (rt->direct_to_screen) { @@ -1848,6 +1905,10 @@ void TextureStorage::_clear_render_target(RenderTarget *rt) { rt->backbuffer = 0; rt->backbuffer_fbo = 0; } + if (rt->backbuffer_depth != 0) { + glDeleteTextures(1, &rt->backbuffer_depth); + rt->backbuffer_depth = 0; + } _render_target_clear_sdf(rt); } diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index fedda6b260..9803e9548a 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -345,6 +345,7 @@ struct RenderTarget { GLuint depth = 0; GLuint backbuffer_fbo = 0; GLuint backbuffer = 0; + GLuint backbuffer_depth = 0; GLuint color_internal_format = GL_RGBA8; GLuint color_format = GL_RGBA; @@ -604,6 +605,8 @@ public: RenderTarget *get_render_target(RID p_rid) { return render_target_owner.get_or_null(p_rid); }; bool owns_render_target(RID p_rid) { return render_target_owner.owns(p_rid); }; + void copy_scene_to_backbuffer(RenderTarget *rt, const bool uses_screen_texture, const bool uses_depth_texture); + virtual RID render_target_create() override; virtual void render_target_free(RID p_rid) override; diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 20a9e633a8..befe84f311 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -343,6 +343,9 @@ void ConnectDialog::_update_method_tree() { si_item->set_icon(0, get_theme_icon(SNAME("Script"), SNAME("EditorIcons"))); si_item->set_selectable(0, false); + if (si->get_script()->is_built_in()) { + si->get_script()->reload(); + } List<MethodInfo> methods; si->get_method_list(&methods); methods = _filter_method_list(methods, signal_info, search_string); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 563bf5ce7f..44426dc143 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3370,7 +3370,17 @@ void EditorInspector::set_keying(bool p_active) { return; } keying = p_active; - update_tree(); + _keying_changed(); +} + +void EditorInspector::_keying_changed() { + for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { + for (EditorProperty *E : F.value) { + if (E) { + E->set_keying(keying); + } + } + } } void EditorInspector::set_read_only(bool p_read_only) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 01231108d8..76fe929ce4 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -521,6 +521,8 @@ class EditorInspector : public ScrollContainer { void _changed_callback(); void _edit_request_change(Object *p_object, const String &p_prop); + void _keying_changed(); + void _filter_changed(const String &p_text); void _parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped); diff --git a/editor/editor_layouts_dialog.cpp b/editor/editor_layouts_dialog.cpp index 57a0a88810..655ab45099 100644 --- a/editor/editor_layouts_dialog.cpp +++ b/editor/editor_layouts_dialog.cpp @@ -140,7 +140,7 @@ EditorLayoutsDialog::EditorLayoutsDialog() { name = memnew(LineEdit); makevb->add_child(name); - name->set_placeholder("Or enter new layout name"); + name->set_placeholder(TTR("Or enter new layout name")); name->set_offset(SIDE_TOP, 5); name->set_anchor_and_offset(SIDE_LEFT, Control::ANCHOR_BEGIN, 5); name->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, -5); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b1c3c51a9c..0e17c72d90 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -356,12 +356,13 @@ void EditorNode::_update_scene_tabs() { scene_tabs->set_current_tab(editor_data.get_edited_scene()); } + const Size2 add_button_size = Size2(0, scene_tabs->get_size().y); if (scene_tabs->get_offset_buttons_visible()) { // Move the add button to a fixed position. if (scene_tab_add->get_parent() == scene_tabs) { scene_tabs->remove_child(scene_tab_add); scene_tab_add_ph->add_child(scene_tab_add); - scene_tab_add->set_position(Point2()); + scene_tab_add->set_rect(Rect2(Point2(), add_button_size)); } } else { // Move the add button to be after the last tab. @@ -371,16 +372,16 @@ void EditorNode::_update_scene_tabs() { } if (scene_tabs->get_tab_count() == 0) { - scene_tab_add->set_position(Point2()); + scene_tab_add->set_rect(Rect2(Point2(), add_button_size)); return; } Rect2 last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1); int hsep = scene_tabs->get_theme_constant(SNAME("h_separation")); if (scene_tabs->is_layout_rtl()) { - scene_tab_add->set_position(Point2(last_tab.position.x - scene_tab_add->get_size().x - hsep, last_tab.position.y)); + scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x - scene_tab_add->get_size().x - hsep, last_tab.position.y), add_button_size)); } else { - scene_tab_add->set_position(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y)); + scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y), add_button_size)); } } @@ -3533,6 +3534,10 @@ void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_chan singleton->editor_plugins_force_input_forwarding->remove_plugin(p_editor); singleton->remove_child(p_editor); singleton->editor_data.remove_editor_plugin(p_editor); + + for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : singleton->active_plugins) { + kv.value.erase(p_editor); + } } void EditorNode::_update_addon_config() { diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp index 6ae25cc224..3f425c0f0b 100644 --- a/editor/editor_path.cpp +++ b/editor/editor_path.cpp @@ -35,6 +35,12 @@ #include "editor/editor_scale.h" #include "editor/multi_node_edit.h" +Size2 EditorPath::get_minimum_size() const { + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); + return Button::get_minimum_size() + Size2(0, font->get_height(font_size)); +} + void EditorPath::_add_children_to_popup(Object *p_obj, int p_depth) { if (p_depth > 8) { return; diff --git a/editor/editor_path.h b/editor/editor_path.h index 747ff5a656..b6778b682e 100644 --- a/editor/editor_path.h +++ b/editor/editor_path.h @@ -61,6 +61,8 @@ protected: static void _bind_methods(); public: + virtual Size2 get_minimum_size() const override; + void update_path(); void clear_path(); void enable_path(); diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index 389c16fd66..d5ba841801 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -117,14 +117,20 @@ EditorPaths::EditorPaths() { // Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir. String exe_path = OS::get_singleton()->get_executable_path().get_base_dir(); + Ref<DirAccess> d = DirAccess::create_for_path(exe_path); + if (d->file_exists(exe_path + "/._sc_")) { + self_contained = true; + self_contained_file = exe_path + "/._sc_"; + } else if (d->file_exists(exe_path + "/_sc_")) { + self_contained = true; + self_contained_file = exe_path + "/_sc_"; + } // On macOS, look outside .app bundle, since .app bundle is read-only. + // Note: This will not work if Gatekeeper path randomization is active. if (OS::get_singleton()->has_feature("macos") && exe_path.ends_with("MacOS") && exe_path.path_join("..").simplify_path().ends_with("Contents")) { exe_path = exe_path.path_join("../../..").simplify_path(); - } - { - Ref<DirAccess> d = DirAccess::create_for_path(exe_path); - + d = DirAccess::create_for_path(exe_path); if (d->file_exists(exe_path + "/._sc_")) { self_contained = true; self_contained_file = exe_path + "/._sc_"; diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index f65f905b25..facd1fd6d3 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -248,12 +248,32 @@ void EditorUndoRedoManager::commit_action(bool p_execute) { } if (!history.undo_stack.is_empty()) { - const Action &prev_action = history.undo_stack.back()->get(); - if (pending_action.merge_mode != UndoRedo::MERGE_DISABLE && pending_action.merge_mode == prev_action.merge_mode && pending_action.action_name == prev_action.action_name) { - // Discard action if it should be merged (UndoRedo handles merging internally). - pending_action = Action(); - is_committing = false; - return; + // Discard action if it should be merged (UndoRedo handles merging internally). + switch (pending_action.merge_mode) { + case UndoRedo::MERGE_DISABLE: + break; // Nothing to do here. + case UndoRedo::MERGE_ENDS: { + if (history.undo_stack.size() < 2) { + break; + } + + const Action &prev_action = history.undo_stack.back()->get(); + const Action &pre_prev_action = history.undo_stack.back()->prev()->get(); + if (pending_action.merge_mode == prev_action.merge_mode && pending_action.merge_mode == pre_prev_action.merge_mode && + pending_action.action_name == prev_action.action_name && pending_action.action_name == pre_prev_action.action_name) { + pending_action = Action(); + is_committing = false; + return; + } + } break; + case UndoRedo::MERGE_ALL: { + const Action &prev_action = history.undo_stack.back()->get(); + if (pending_action.merge_mode == prev_action.merge_mode && pending_action.action_name == prev_action.action_name) { + pending_action = Action(); + is_committing = false; + return; + } + } break; } } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index fe3c2333ed..fa6b0a7716 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1026,13 +1026,13 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String type = ResourceLoader::get_resource_type(path); if (FileAccess::exists(path + ".import")) { - // Before doing this, try to see if it can be customized + // Before doing this, try to see if it can be customized. String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, false); if (export_path != path) { - // It was actually customized.. - // Since the original file is likely not recognized, just use the import system + // It was actually customized. + // Since the original file is likely not recognized, just use the import system. Ref<ConfigFile> config; config.instantiate(); @@ -1043,18 +1043,18 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } config->set_value("remap", "type", ResourceLoader::get_resource_type(export_path)); - // Erase all PAths + // Erase all Paths. List<String> keys; config->get_section_keys("remap", &keys); for (const String &K : keys) { - if (E.begins_with("path")) { + if (K.begins_with("path")) { config->erase_section_key("remap", K); } } // Set actual converted path. config->set_value("remap", "path", export_path); - // erase useless sections + // Erase useless sections. config->erase_section("deps"); config->erase_section("params"); @@ -1075,7 +1075,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & return err; } } else { - // file is imported and not customized, replace by what it imports + // File is imported and not customized, replace by what it imports. Ref<ConfigFile> config; config.instantiate(); err = config->load(path + ".import"); @@ -1087,7 +1087,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String importer_type = config->get_value("remap", "importer"); if (importer_type == "keep") { - //just keep file as-is + // Just keep file as-is. Vector<uint8_t> array = FileAccess::get_file_as_bytes(path); err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); @@ -1130,6 +1130,9 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String remapped_path = config->get_value("remap", remap); Vector<uint8_t> array = FileAccess::get_file_as_bytes(remapped_path); err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + } else { + // Remove paths if feature not enabled. + config->erase_section_key("remap", remap); } } } @@ -1138,9 +1141,17 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & return err; } - //also save the .import file - Vector<uint8_t> array = FileAccess::get_file_as_bytes(path + ".import"); - err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key); + // Erase useless sections. + config->erase_section("deps"); + config->erase_section("params"); + + String import_text = config->encode_to_text(); + CharString cs = import_text.utf8(); + Vector<uint8_t> sarr; + sarr.resize(cs.size()); + memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); + + err = p_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1148,7 +1159,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } } else { - // Customize + // Customize. bool do_export = true; for (int i = 0; i < export_plugins.size(); i++) { diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index 10a0c2662f..cce42d73a5 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -286,7 +286,7 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons Error ResourceImporterLayeredTexture::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { int compress_mode = p_options["compress/mode"]; float lossy = p_options["compress/lossy_quality"]; - float high_quality = p_options["compress/high_quality"]; + bool high_quality = p_options["compress/high_quality"]; int hdr_compression = p_options["compress/hdr_compression"]; bool mipmaps = p_options["mipmaps/generate"]; diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index 8471e0d8a3..204a8494e6 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -329,7 +329,7 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) { if (p_node == scene) { icon = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")); - item->set_text(0, "Scene"); + item->set_text(0, TTR("Scene")); } item->set_icon(0, icon); @@ -1032,12 +1032,12 @@ void SceneImportSettings::_save_path_changed(const String &p_path) { save_path_item->set_text(1, p_path); if (FileAccess::exists(p_path)) { - save_path_item->set_text(2, "Warning: File exists"); + save_path_item->set_text(2, TTR("Warning: File exists")); save_path_item->set_tooltip_text(2, TTR("Existing file with the same name will be replaced.")); save_path_item->set_icon(2, get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"))); } else { - save_path_item->set_text(2, "Will create new File"); + save_path_item->set_text(2, TTR("Will create new file")); save_path_item->set_icon(2, get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons"))); } } @@ -1077,7 +1077,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { if (md.has_import_id) { if (md.settings.has("use_external/enabled") && bool(md.settings["use_external/enabled"])) { - item->set_text(2, "Already External"); + item->set_text(2, TTR("Already External")); item->set_tooltip_text(2, TTR("This material already references an external file, no action will be taken.\nDisable the external property for it to be extracted again.")); } else { item->set_metadata(0, E.key); @@ -1092,12 +1092,12 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(1, path); if (FileAccess::exists(path)) { - item->set_text(2, "Warning: File exists"); + item->set_text(2, TTR("Warning: File exists")); item->set_tooltip_text(2, TTR("Existing file with the same name will be replaced.")); item->set_icon(2, get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"))); } else { - item->set_text(2, "Will create new File"); + item->set_text(2, TTR("Will create new file")); item->set_icon(2, get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons"))); } @@ -1105,7 +1105,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { } } else { - item->set_text(2, "No import ID"); + item->set_text(2, TTR("No import ID")); item->set_tooltip_text(2, TTR("Material has no name nor any other way to identify on re-import.\nPlease name it or ensure it is exported with an unique ID.")); item->set_icon(2, get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons"))); } @@ -1130,7 +1130,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { if (md.has_import_id) { if (md.settings.has("save_to_file/enabled") && bool(md.settings["save_to_file/enabled"])) { - item->set_text(2, "Already Saving"); + item->set_text(2, TTR("Already Saving")); item->set_tooltip_text(2, TTR("This mesh already saves to an external resource, no action will be taken.")); } else { item->set_metadata(0, E.key); @@ -1145,12 +1145,12 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(1, path); if (FileAccess::exists(path)) { - item->set_text(2, "Warning: File exists"); + item->set_text(2, TTR("Warning: File exists")); item->set_tooltip_text(2, TTR("Existing file with the same name will be replaced on import.")); item->set_icon(2, get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"))); } else { - item->set_text(2, "Will save to new File"); + item->set_text(2, TTR("Will save to new file")); item->set_icon(2, get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons"))); } @@ -1158,7 +1158,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { } } else { - item->set_text(2, "No import ID"); + item->set_text(2, TTR("No import ID")); item->set_tooltip_text(2, TTR("Mesh has no name nor any other way to identify on re-import.\nPlease name it or ensure it is exported with an unique ID.")); item->set_icon(2, get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons"))); } @@ -1182,7 +1182,7 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(0, name); if (ad.settings.has("save_to_file/enabled") && bool(ad.settings["save_to_file/enabled"])) { - item->set_text(2, "Already Saving"); + item->set_text(2, TTR("Already Saving")); item->set_tooltip_text(2, TTR("This animation already saves to an external resource, no action will be taken.")); } else { item->set_metadata(0, E.key); @@ -1197,12 +1197,12 @@ void SceneImportSettings::_save_dir_callback(const String &p_path) { item->set_text(1, path); if (FileAccess::exists(path)) { - item->set_text(2, "Warning: File exists"); + item->set_text(2, TTR("Warning: File exists")); item->set_tooltip_text(2, TTR("Existing file with the same name will be replaced on import.")); item->set_icon(2, get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"))); } else { - item->set_text(2, "Will save to new File"); + item->set_text(2, TTR("Will save to new file")); item->set_icon(2, get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons"))); } diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index c5b9bf9e0b..5ad8318ca0 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -212,7 +212,8 @@ private: bool selected_from_canvas = false; Point2 grid_offset; - Point2 grid_step = Point2(8, 8); // A power-of-two value works better as a default. + // A power-of-two value works better as a default grid size. + Point2 grid_step = Point2(8, 8); int primary_grid_steps = 8; int grid_step_multiplier = 0; diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index c2d5885e43..4afbb87197 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -356,6 +356,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e return false; } + original_point = handles[edit_handle]; original = get_handle_value(edit_handle); original_transform = node->get_global_transform(); last_point = original; @@ -572,6 +573,11 @@ void CollisionShape2DEditor::edit(Node *p_node) { _get_current_shape_type(); } else { + if (pressed) { + set_handle(edit_handle, original_point); + pressed = false; + } + edit_handle = -1; shape_type = -1; diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 68fc0ddbdf..9c37b6cf9d 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -71,6 +71,7 @@ class CollisionShape2DEditor : public Control { bool pressed; Variant original; Transform2D original_transform; + Vector2 original_point; Point2 last_point; Variant get_handle_value(int idx) const; diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index f3d2867030..d1c47ab14e 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -54,67 +54,94 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p String entry_symbol = config->get_value("configuration", "entry_symbol"); - PackedStringArray tags; - String library_path = GDExtension::find_extension_library( - p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); }, &tags); - if (!library_path.is_empty()) { - add_shared_object(library_path, tags); - - if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { - String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" - "\n" - "extern \"C\" void $ENTRY();\n" - "void $ENTRY_init() {\n" - " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" - "}\n" - "struct $ENTRY_struct {\n" - " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" - " }\n" - "};\n" - "$ENTRY_struct $ENTRY_struct_instance;\n\n"; - additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); - - String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); - } - } else { - Vector<String> features_vector; - for (const String &E : p_features) { - features_vector.append(E); + HashSet<String> all_archs; + all_archs.insert("x86_32"); + all_archs.insert("x86_64"); + all_archs.insert("arm32"); + all_archs.insert("arm64"); + all_archs.insert("rv64"); + all_archs.insert("ppc32"); + all_archs.insert("ppc64"); + all_archs.insert("wasm32"); + all_archs.insert("universal"); + + HashSet<String> archs; + HashSet<String> features_wo_arch; + for (const String &tag : p_features) { + if (all_archs.has(tag)) { + archs.insert(tag); + } else { + features_wo_arch.insert(tag); } - ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector))); } - List<String> dependencies; - if (config->has_section("dependencies")) { - config->get_section_keys("dependencies", &dependencies); + if (archs.is_empty()) { + archs.insert("unknown_arch"); // Not archs specified, still try to match. } - for (const String &E : dependencies) { - Vector<String> dependency_tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < dependency_tags.size(); i++) { - String tag = dependency_tags[i].strip_edges(); - if (!p_features.has(tag)) { - all_tags_met = false; - break; + for (const String &arch_tag : archs) { + PackedStringArray tags; + String library_path = GDExtension::find_extension_library( + p_path, config, [features_wo_arch, arch_tag](String p_feature) { return features_wo_arch.has(p_feature) || (p_feature == arch_tag); }, &tags); + if (!library_path.is_empty()) { + add_shared_object(library_path, tags); + + if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n" + "\n" + "extern \"C\" void $ENTRY();\n" + "void $ENTRY_init() {\n" + " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" + "}\n" + "struct $ENTRY_struct {\n" + " $ENTRY_struct() {\n" + " add_ios_init_callback($ENTRY_init);\n" + " }\n" + "};\n" + "$ENTRY_struct $ENTRY_struct_instance;\n\n"; + additional_code = additional_code.replace("$ENTRY", entry_symbol); + add_ios_cpp_code(additional_code); + + String linker_flags = "-Wl,-U,_" + entry_symbol; + add_ios_linker_flags(linker_flags); + } + } else { + Vector<String> features_vector; + for (const String &E : p_features) { + features_vector.append(E); } + ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector))); } - if (all_tags_met) { - Dictionary dependency = config->get_value("dependencies", E); - for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { - String dependency_path = *key; - String target_path = dependency[*key]; - if (dependency_path.is_relative_path()) { - dependency_path = p_path.get_base_dir().path_join(dependency_path); + List<String> dependencies; + if (config->has_section("dependencies")) { + config->get_section_keys("dependencies", &dependencies); + } + + for (const String &E : dependencies) { + Vector<String> dependency_tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); + if (!p_features.has(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + Dictionary dependency = config->get_value("dependencies", E); + for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { + String dependency_path = *key; + String target_path = dependency[*key]; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); + } + add_shared_object(dependency_path, dependency_tags, target_path); } - add_shared_object(dependency_path, dependency_tags, target_path); + break; } - break; } } } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 333771ceb4..7c5b8600d4 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2102,6 +2102,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (_edit.mode == TRANSFORM_NONE) { + if (_edit.gizmo.is_valid()) { + // Restore. + _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true); + _edit.gizmo = Ref<EditorNode3DGizmo>(); + } if (k->get_keycode() == Key::ESCAPE && !cursor.region_select) { _clear_selected(); return; diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index fb35668310..e5e77be7aa 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -1232,7 +1232,8 @@ Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const { Polygon2DEditor::Polygon2DEditor() { snap_offset = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_offset", Vector2()); - snap_step = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_step", Vector2(10, 10)); + // A power-of-two value works better as a default grid size. + snap_step = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_step", Vector2(8, 8)); use_snap = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_enabled", false); snap_show_grid = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "show_grid", false); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a584d357cd..49c815526b 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -3586,7 +3586,72 @@ void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_numb shader_editor->get_shader_editor(res)->goto_line_selection(line_number - 1, begin, end); return; } else if (fpath.get_extension() == "tscn") { + Ref<FileAccess> f = FileAccess::open(fpath, FileAccess::READ); + bool is_script_found = false; + + // Starting from top of the tscn file. + int scr_start_line = 1; + + String scr_header = "[sub_resource type=\"GDScript\" id=\""; + String scr_id = ""; + String line = ""; + + int l = 0; + + while (!f->eof_reached()) { + line = f->get_line(); + l++; + + if (!line.begins_with(scr_header)) { + continue; + } + + // Found the end of the script. + scr_id = line.get_slice(scr_header, 1); + scr_id = scr_id.get_slice("\"", 0); + + scr_start_line = l + 1; + int scr_line_count = 0; + + do { + line = f->get_line(); + l++; + String strline = line.strip_edges(); + + if (strline.ends_with("\"") && !strline.ends_with("\\\"")) { + // Found the end of script. + break; + } + scr_line_count++; + + } while (!f->eof_reached()); + + if (line_number > scr_start_line + scr_line_count) { + // Find in another built-in GDScript. + continue; + } + + // Real line number of the built-in script. + line_number = line_number - scr_start_line; + + is_script_found = true; + break; + } + EditorNode::get_singleton()->load_scene(fpath); + + if (is_script_found && !scr_id.is_empty()) { + Ref<Script> scr = ResourceLoader::load(fpath + "::" + scr_id, "Script"); + if (scr.is_valid()) { + edit(scr); + ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor()); + + if (ste) { + ste->goto_line_selection(line_number, begin, end); + } + } + } + return; } else { Ref<Script> scr = res; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 907bc81674..7861c8d354 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -376,6 +376,12 @@ bool ShaderEditorPlugin::can_drop_data_fw(const Point2 &p_point, const Variant & return true; } } + if (ResourceLoader::exists(file, "ShaderInclude")) { + Ref<ShaderInclude> sinclude = ResourceLoader::load(file); + if (sinclude.is_valid()) { + return true; + } + } } return false; } @@ -405,11 +411,10 @@ void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_da for (int i = 0; i < files.size(); i++) { String file = files[i]; - if (!ResourceLoader::exists(file, "Shader")) { - continue; + Ref<Resource> res; + if (ResourceLoader::exists(file, "Shader") || ResourceLoader::exists(file, "ShaderInclude")) { + res = ResourceLoader::load(file); } - - Ref<Resource> res = ResourceLoader::load(file); if (res.is_valid()) { edit(res.ptr()); } diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 7fa16e6cc6..96e9005850 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -1075,7 +1075,8 @@ TextureRegionEditor::TextureRegionEditor() { preview_tex = Ref<CanvasTexture>(memnew(CanvasTexture)); - snap_step = Vector2(10, 10); + // A power-of-two value works better as a default grid size. + snap_step = Vector2(8, 8); snap_separation = Vector2(0, 0); snap_mode = SNAP_NONE; edited_margin = -1; diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 4d9e2db682..55b5abf983 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -564,7 +564,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { has_mouse = true; - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2 mpos = xform.affine_inverse().xform(mm->get_position()); switch (drag_type) { @@ -613,7 +613,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { has_mouse = true; - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { @@ -736,14 +736,14 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over return; } - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2i tile_shape_size = tile_set->get_tile_size(); // Draw the selection. if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && tool_buttons_group->get_pressed_button() == select_tool_button) { // In select mode, we only draw the current selection if we are modifying it (pressing control or shift). if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { - // Do nothing + // Do nothing. } else { Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); @@ -839,10 +839,11 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Expand the grid if needed if (expand_grid && !preview.is_empty()) { - drawn_grid_rect = Rect2i(preview.begin()->key, Vector2i(1, 1)); + drawn_grid_rect = Rect2i(preview.begin()->key, Vector2i(0, 0)); for (const KeyValue<Vector2i, TileMapCell> &E : preview) { drawn_grid_rect.expand_to(E.key); } + drawn_grid_rect.size += Vector2i(1, 1); } } @@ -910,8 +911,6 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over dest_rect.position = (tile_map->map_to_local(E.key) - dest_rect.size / 2 - tile_offset); } - dest_rect = xform.xform(dest_rect); - if (tile_data->get_flip_h()) { dest_rect.size.x = -dest_rect.size.x; } @@ -927,7 +926,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over modulate *= tile_map->get_layer_modulate(tile_map_layer); // Draw the tile. + p_overlay->draw_set_transform_matrix(xform); p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping()); + p_overlay->draw_set_transform_matrix(Transform2D()); } else { tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } @@ -1237,7 +1238,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { return; } - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position()); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); @@ -2649,7 +2650,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { return; } - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position()); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); @@ -2845,7 +2846,7 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { has_mouse = true; - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2 mpos = xform.affine_inverse().xform(mm->get_position()); switch (drag_type) { @@ -2872,7 +2873,7 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { has_mouse = true; - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { @@ -2976,7 +2977,7 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o return; } - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Vector2i tile_shape_size = tile_set->get_tile_size(); // Handle the preview of the tiles to be placed. @@ -3817,7 +3818,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { return; } - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform_with_canvas(); Transform2D xform_inv = xform.affine_inverse(); Vector2i tile_shape_size = tile_set->get_tile_size(); diff --git a/editor/renames_map_3_to_4.cpp b/editor/renames_map_3_to_4.cpp index 617d4f8b13..a8c438b25a 100644 --- a/editor/renames_map_3_to_4.cpp +++ b/editor/renames_map_3_to_4.cpp @@ -1134,6 +1134,7 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = { { "tab_align", "tab_alignment" }, // TabContainer { "table_hseparation", "table_h_separation" }, // Theme { "table_vseparation", "table_v_separation" }, // Theme + { "tangent", "orthogonal" }, // Vector2 { "toplevel", "top_level" }, // Node { "translation", "position" }, // Node3D { "unit_db", "volume_db" }, // AudioStreamPlayer3D @@ -1198,6 +1199,7 @@ const char *RenamesMap3To4::csharp_properties_renames[][2] = { { "Oneshot", "OneShot" }, // AnimatedTexture { "OutOfRangeMode", "MaxPolyphony" }, // AudioStreamPlayer3D { "PauseMode", "ProcessMode" }, // Node + { "Perpendicular", "Orthogonal" }, // Vector2 - Only exists in C# { "PhysicalScancode", "PhysicalKeycode" }, // InputEventKey { "PopupExclusive", "Exclusive" }, // Window { "ProximityFadeEnable", "ProximityFadeEnabled" }, // Material @@ -1228,6 +1230,7 @@ const char *RenamesMap3To4::csharp_properties_renames[][2] = { { "TabAlign", "TabAlignment" }, // TabContainer { "TableHseparation", "TableHSeparation" }, // Theme { "TableVseparation", "TableVSeparation" }, // Theme + { "Tangent", "Orthogonal" }, // Vector2 { "Toplevel", "TopLevel" }, // Node { "Translation", "Position" }, // Node3D { "UnitDb", "VolumeDb" }, // AudioStreamPlayer3D diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index effc0aab96..13d223b0d9 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -18,12 +18,13 @@ <param index="2" name="b8" type="int" /> <param index="3" name="a8" type="int" default="255" /> <description> - Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value. + Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value. Using [method Color8] instead of the standard [Color] constructor is useful when you need to match exact color values in an [Image]. [codeblock] var red = Color8(255, 0, 0) # Same as Color(1, 0, 0). var dark_blue = Color8(0, 0, 51) # Same as Color(0, 0, 0.2). var my_color = Color8(306, 255, 0, 102) # Same as Color(1.2, 1, 0, 0.4). [/codeblock] + [b]Note:[/b] Due to the lower precision of [method Color8] compared to the standard [Color] constructor, a color created with [method Color8] will generally not be equal to the same color created with the standard [Color] constructor. Use [method Color.is_equal_approx] for comparisons to avoid issues with floating-point precision error. </description> </method> <method name="assert"> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 5883ec863d..bba11363d5 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -294,17 +294,27 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l !(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) && !(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) && !(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) && - !((str[j] == '-' || str[j] == '+' || str[j] == '~') && !prev_is_binary_op && str[j - 1] != 'e')) { - /* This condition continues Number highlighting in special cases. - 1st row: '+' or '-' after scientific notation; + !((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) { + /* This condition continues number highlighting in special cases. + 1st row: '+' or '-' after scientific notation (like 3e-4); 2nd row: '_' as a numeric separator; 3rd row: Scientific notation 'e' and floating points; 4th row: Floating points inside the number, or leading if after a unary mathematical operator; - 5th row: Multiple unary mathematical operators */ + 5th row: Multiple unary mathematical operators (like ~-7)*/ in_number = false; } - } else if (!is_binary_op && (str[j] == '-' || str[j] == '+' || str[j] == '~' || (str[j] == '.' && str[j + 1] != '.' && (j == 0 || (j > 0 && str[j - 1] != '.'))))) { + } else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) { + // Start number highlighting from leading decimal points (like .42) in_number = true; + } else if ((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op) { + // Only start number highlighting on unary operators if a digit follows them. + int non_op = j + 1; + while (str[non_op] == '-' || str[non_op] == '+' || str[non_op] == '~') { + non_op++; + } + if (is_digit(str[non_op]) || (str[non_op] == '.' && non_op < line_length && is_digit(str[non_op + 1]))) { + in_number = true; + } } if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) { diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 214d4d8ec2..541a95ab6f 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -913,7 +913,7 @@ void GridMapEditor::update_palette() { } void GridMapEditor::edit(GridMap *p_gridmap) { - if (node) { + if (node && node->is_connected("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids))) { node->disconnect("cell_size_changed", callable_mp(this, &GridMapEditor::_draw_grids)); } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 3c0bd56e86..db8c645558 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -557,10 +557,14 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } //erase navigation - for (const KeyValue<IndexKey, Octant::NavigationCell> &E : g.navigation_cell_ids) { - NavigationServer3D::get_singleton()->free(E.value.region); + for (KeyValue<IndexKey, Octant::NavigationCell> &E : g.navigation_cell_ids) { + if (E.value.region.is_valid()) { + NavigationServer3D::get_singleton()->free(E.value.region); + E.value.region = RID(); + } if (E.value.navigation_mesh_debug_instance.is_valid()) { RS::get_singleton()->free(E.value.navigation_mesh_debug_instance); + E.value.navigation_mesh_debug_instance = RID(); } } g.navigation_cell_ids.clear(); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1dbf6b3471..a77b1d83ad 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -334,8 +334,8 @@ bool CSharpLanguage::is_using_templates() { } Ref<Script> CSharpLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { - Ref<CSharpScript> script; - script.instantiate(); + Ref<CSharpScript> scr; + scr.instantiate(); String class_name_no_spaces = p_class_name.replace(" ", "_"); String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces); @@ -344,8 +344,8 @@ Ref<Script> CSharpLanguage::make_template(const String &p_template, const String .replace("_BASE_", base_class_name) .replace("_CLASS_", class_name_no_spaces) .replace("_TS_", _get_indentation()); - script->set_source_code(processed_template); - return script; + scr->set_source_code(processed_template); + return scr; } Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(StringName p_object) { @@ -780,28 +780,28 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // As scripts are going to be reloaded, must proceed without locking here - for (Ref<CSharpScript> &script : scripts) { + for (Ref<CSharpScript> &scr : scripts) { // If someone removes a script from a node, deletes the script, builds, adds a script to the // same node, then builds again, the script might have no path and also no script_class. In // that case, we can't (and don't need to) reload it. - if (script->get_path().is_empty() && !script->valid) { + if (scr->get_path().is_empty() && !scr->valid) { continue; } - to_reload.push_back(script); + to_reload.push_back(scr); // Script::instances are deleted during managed object disposal, which happens on domain finalize. // Only placeholders are kept. Therefore we need to keep a copy before that happens. - for (Object *obj : script->instances) { - script->pending_reload_instances.insert(obj->get_instance_id()); + for (Object *obj : scr->instances) { + scr->pending_reload_instances.insert(obj->get_instance_id()); // Since this script instance wasn't a placeholder, add it to the list of placeholders // that will have to be eventually replaced with a script instance in case it turns into one. // This list is not cleared after the reload and the collected instances only leave // the list if the script is instantiated or if it was a tool script but becomes a // non-tool script in a rebuild. - script->pending_replace_placeholders.insert(obj->get_instance_id()); + scr->pending_replace_placeholders.insert(obj->get_instance_id()); RefCounted *rc = Object::cast_to<RefCounted>(obj); if (rc) { @@ -810,9 +810,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #ifdef TOOLS_ENABLED - for (PlaceHolderScriptInstance *script_instance : script->placeholders) { - Object *obj = script_instance->get_owner(); - script->pending_reload_instances.insert(obj->get_instance_id()); + for (PlaceHolderScriptInstance *instance : scr->placeholders) { + Object *obj = instance->get_owner(); + scr->pending_reload_instances.insert(obj->get_instance_id()); RefCounted *rc = Object::cast_to<RefCounted>(obj); if (rc) { @@ -822,9 +822,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif // Save state and remove script from instances - RBMap<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state; + RBMap<ObjectID, CSharpScript::StateBackup> &owners_map = scr->pending_reload_state; - for (Object *obj : script->instances) { + for (Object *obj : scr->instances) { ERR_CONTINUE(!obj->get_script_instance()); CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); @@ -849,14 +849,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // After the state of all instances is saved, clear scripts and script instances - for (Ref<CSharpScript> &script : scripts) { - while (script->instances.begin()) { - Object *obj = *script->instances.begin(); + for (Ref<CSharpScript> &scr : scripts) { + while (scr->instances.begin()) { + Object *obj = *scr->instances.begin(); obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload) } - script->was_tool_before_reload = script->tool; - script->_clear(); + scr->was_tool_before_reload = scr->tool; + scr->_clear(); } // Do domain reload @@ -901,44 +901,44 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { List<Ref<CSharpScript>> to_reload_state; - for (Ref<CSharpScript> &script : to_reload) { + for (Ref<CSharpScript> &scr : to_reload) { #ifdef TOOLS_ENABLED - script->exports_invalidated = true; + scr->exports_invalidated = true; #endif - if (!script->get_path().is_empty()) { - script->reload(p_soft_reload); + if (!scr->get_path().is_empty()) { + scr->reload(p_soft_reload); - if (!script->valid) { - script->pending_reload_instances.clear(); - script->pending_reload_state.clear(); + if (!scr->valid) { + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); continue; } } else { - bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr()); + bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(scr.ptr()); if (!success) { // Couldn't reload - script->pending_reload_instances.clear(); - script->pending_reload_state.clear(); + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); continue; } } - StringName native_name = script->get_instance_base_type(); + StringName native_name = scr->get_instance_base_type(); { - for (const ObjectID &obj_id : script->pending_reload_instances) { + for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); if (!obj) { - script->pending_reload_state.erase(obj_id); + scr->pending_reload_state.erase(obj_id); continue; } if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) { // No longer inherits the same compatible type, can't reload - script->pending_reload_state.erase(obj_id); + scr->pending_reload_state.erase(obj_id); continue; } @@ -946,11 +946,11 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Check if the script must be instantiated or kept as a placeholder // when the script may not be a tool (see #65266) - bool replace_placeholder = script->pending_replace_placeholders.has(obj->get_instance_id()); - if (!script->is_tool() && script->was_tool_before_reload) { + bool replace_placeholder = scr->pending_replace_placeholders.has(obj->get_instance_id()); + if (!scr->is_tool() && scr->was_tool_before_reload) { // The script was a tool before the rebuild so the removal was intentional. replace_placeholder = false; - script->pending_replace_placeholders.erase(obj->get_instance_id()); + scr->pending_replace_placeholders.erase(obj->get_instance_id()); } #ifdef TOOLS_ENABLED @@ -959,20 +959,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // Non-placeholder script instances are removed in godot_icall_Object_Disposed. CRASH_COND(!si->is_placeholder()); - if (replace_placeholder || script->is_tool() || ScriptServer::is_scripting_enabled()) { + if (replace_placeholder || scr->is_tool() || ScriptServer::is_scripting_enabled()) { // Replace placeholder with a script instance. - CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; + CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; // Backup placeholder script instance state before replacing it with a script instance. si->get_property_state(state_backup.properties); - ScriptInstance *script_instance = script->instance_create(obj); + ScriptInstance *instance = scr->instance_create(obj); - if (script_instance) { - script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); - script->pending_replace_placeholders.erase(obj->get_instance_id()); - obj->set_script_instance(script_instance); + if (instance) { + scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si)); + scr->pending_replace_placeholders.erase(obj->get_instance_id()); + obj->set_script_instance(instance); } } @@ -983,18 +983,18 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif // Re-create the script instance. - if (replace_placeholder || script->is_tool() || ScriptServer::is_scripting_enabled()) { + if (replace_placeholder || scr->is_tool() || ScriptServer::is_scripting_enabled()) { // Create script instance or replace placeholder with a script instance. - ScriptInstance *script_instance = script->instance_create(obj); + ScriptInstance *instance = scr->instance_create(obj); - if (script_instance) { - script->pending_replace_placeholders.erase(obj->get_instance_id()); - obj->set_script_instance(script_instance); + if (instance) { + scr->pending_replace_placeholders.erase(obj->get_instance_id()); + obj->set_script_instance(instance); continue; } } // The script instance could not be instantiated or wasn't in the list of placeholders to replace. - obj->set_script(script); + obj->set_script(scr); #if DEBUG_ENABLED // If we reached here, the instantiated script must be a placeholder. CRASH_COND(!obj->get_script_instance()->is_placeholder()); @@ -1002,21 +1002,21 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } } - to_reload_state.push_back(script); + to_reload_state.push_back(scr); } - for (Ref<CSharpScript> &script : to_reload_state) { - for (const ObjectID &obj_id : script->pending_reload_instances) { + for (Ref<CSharpScript> &scr : to_reload_state) { + for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); if (!obj) { - script->pending_reload_state.erase(obj_id); + scr->pending_reload_state.erase(obj_id); continue; } ERR_CONTINUE(!obj->get_script_instance()); - CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; + CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id]; CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); @@ -1033,8 +1033,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } } - script->pending_reload_instances.clear(); - script->pending_reload_state.clear(); + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); } // Deserialize managed callables @@ -2144,8 +2144,8 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda _update_exports_values(values, propnames); if (changed) { - for (PlaceHolderScriptInstance *script_instance : placeholders) { - script_instance->update(propnames, values); + for (PlaceHolderScriptInstance *instance : placeholders) { + instance->update(propnames, values); } } else { p_instance_to_update->update(propnames, values); @@ -2711,28 +2711,28 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const // TODO ignore anything inside bin/ and obj/ in tools builds? - Ref<CSharpScript> script; + Ref<CSharpScript> scr; if (GDMonoCache::godot_api_cache_updated) { - GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &script); + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr); } else { - script = Ref<CSharpScript>(memnew(CSharpScript)); + scr = Ref<CSharpScript>(memnew(CSharpScript)); } #if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) - Error err = script->load_source_code(p_path); + Error err = scr->load_source_code(p_path); ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_path + "'."); #endif - script->set_path(p_original_path); + scr->set_path(p_original_path); - script->reload(); + scr->reload(); if (r_error) { *r_error = OK; } - return script; + return scr; } void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 0d0889c491..894053c5de 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -14,6 +14,7 @@ <GodotProjectDir Condition=" '$(GodotProjectDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir> <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir> + <GodotProjectDirBase64 Condition=" $([MSBuild]::VersionGreaterThanOrEquals($(MSBuildAssemblyVersion), '17.3')) ">$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64> <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. --> <BaseOutputPath>$(GodotProjectDir).godot\mono\temp\bin\</BaseOutputPath> @@ -29,6 +30,13 @@ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> + <Target Condition=" $([MSBuild]::VersionLessThan($(MSBuildAssemblyVersion), '17.3')) " Name="GodotProjectDir" BeforeTargets="Build"> + <Error Text="Cannot build from path containing '%23', move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%23'))" /> <!-- # --> + <Error Text="Cannot build from path containing '%3B', move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%3B'))" /> <!-- ; --> + <Error Text="Cannot build from path containing newlines, move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%0A'))" /> <!-- \n --> + <Error Text="Cannot build from path containing newlines, move your project or update dotnet to the latest version." Condition="$(GodotProjectDir.Contains('%0D'))" /> <!-- \r --> + </Target> + <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " /> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj index 8e78e0385d..3f569ebac3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -7,6 +7,7 @@ <PropertyGroup> <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk --> <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir> + <GodotProjectDirBase64 Condition=" $([MSBuild]::VersionGreaterThanOrEquals($(MSBuildAssemblyVersion), '17.3')) ">$([MSBuild]::ConvertToBase64('$(GodotProjectDir)'))</GodotProjectDirBase64> <!-- For compiling GetGodotPropertyDefaultValues. --> <DefineConstants>$(DefineConstants);TOOLS</DefineConstants> </PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props index 7881ed0a8c..2a8ae7f958 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -2,6 +2,7 @@ <ItemGroup> <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> <CompilerVisibleProperty Include="GodotProjectDir" /> + <CompilerVisibleProperty Include="GodotProjectDirBase64" /> <CompilerVisibleProperty Include="GodotSourceGenerators" /> <CompilerVisibleProperty Include="IsGodotToolsProject" /> </ItemGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index eae7e41da8..d14e3c3781 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -22,10 +22,17 @@ namespace Godot.SourceGenerators // NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0 // ReSharper disable once ReplaceWithStringIsNullOrEmpty - if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) - || godotProjectDir!.Length == 0) + if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDirBase64", out string? godotProjectDir) || godotProjectDir!.Length == 0) { - throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); + if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out godotProjectDir) || godotProjectDir!.Length == 0) + { + throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); + } + } + else + { + // Workaround for https://github.com/dotnet/roslyn/issues/51692 + godotProjectDir = Encoding.UTF8.GetString(Convert.FromBase64String(godotProjectDir)); } Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 83101c1443..6690a3badb 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -523,7 +523,10 @@ void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const Ty p_xml_output.append(target_imethod->proxy_name); p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -563,7 +566,10 @@ void BindingsGenerator::_append_xml_member(StringBuilder &p_xml_output, const Ty p_xml_output.append(target_iprop->proxy_name); p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -591,7 +597,10 @@ void BindingsGenerator::_append_xml_signal(StringBuilder &p_xml_output, const Ty p_xml_output.append(target_isignal->proxy_name); p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -613,7 +622,10 @@ void BindingsGenerator::_append_xml_enum(StringBuilder &p_xml_output, const Type p_xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any p_xml_output.append("\"/>"); } else { - ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -673,7 +685,10 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const // Also search in @GlobalScope as a last resort if no class was specified _append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target); } else { - ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'."); + if (!p_target_itype->is_intentionally_ignored(p_link_target)) { + ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'."); + } + _append_xml_undeclared(p_xml_output, p_link_target); } } @@ -2936,6 +2951,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { if (method_has_ptr_parameter(method_info)) { // Pointers are not supported. + itype.ignored_members.insert(method_info.name); continue; } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 5c266ed31f..eac281ddb4 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -408,6 +408,7 @@ class BindingsGenerator { List<PropertyInterface> properties; List<MethodInterface> methods; List<SignalInterface> signals_; + HashSet<String> ignored_members; bool has_virtual_methods = false; @@ -471,6 +472,10 @@ class BindingsGenerator { return nullptr; } + bool is_intentionally_ignored(const String &p_name) const { + return ignored_members.has(p_name); + } + private: static DocData::ClassDoc *_get_type_doc(TypeInterface &itype) { String doc_name = itype.name.begins_with("_") ? itype.name.substr(1) : itype.name; diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index d08dedcfcb..bff7d04b55 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -66,23 +66,25 @@ String _get_mono_user_dir() { if (EditorPaths::get_singleton()) { return EditorPaths::get_singleton()->get_data_dir().path_join("mono"); } else { - String settings_path; + String settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name()); // Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir. String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); - - // On macOS, look outside .app bundle, since .app bundle is read-only. - if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) { - exe_dir = exe_dir.path_join("../../..").simplify_path(); - } - Ref<DirAccess> d = DirAccess::create_for_path(exe_dir); - if (d->file_exists("._sc_") || d->file_exists("_sc_")) { // contain yourself settings_path = exe_dir.path_join("editor_data"); - } else { - settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name()); + } + + // On macOS, look outside .app bundle, since .app bundle is read-only. + // Note: This will not work if Gatekeeper path randomization is active. + if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) { + exe_dir = exe_dir.path_join("../../..").simplify_path(); + d = DirAccess::create_for_path(exe_dir); + if (d->file_exists("._sc_") || d->file_exists("_sc_")) { + // contain yourself + settings_path = exe_dir.path_join("editor_data"); + } } return settings_path.path_join("mono"); diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index b1674c8fc5..4dcdbd6446 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -894,9 +894,9 @@ void NavMap::sync() { if (agents_dirty) { // cannot use LocalVector here as RVO library expects std::vector to build KdTree std::vector<RVO::Agent *> raw_agents; - raw_agents.reserve(agents.size()); - for (NavAgent *agent : agents) { - raw_agents.push_back(agent->get_agent()); + raw_agents.reserve(controlled_agents.size()); + for (NavAgent *controlled_agent : controlled_agents) { + raw_agents.push_back(controlled_agent->get_agent()); } rvo.buildAgentTree(raw_agents); } diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index 669c395b3e..d2f6be2233 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -178,6 +178,7 @@ void OpenXRActionMap::create_default_action_sets() { Ref<OpenXRAction> grip = action_set->add_new_action("grip", "Grip", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_click = action_set->add_new_action("grip_click", "Grip click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_touch = action_set->add_new_action("grip_touch", "Grip touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_force = action_set->add_new_action("grip_force", "Grip force", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> primary = action_set->add_new_action("primary", "Primary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> primary_click = action_set->add_new_action("primary_click", "Primary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> primary_touch = action_set->add_new_action("primary_touch", "Primary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); @@ -349,6 +350,7 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion + profile->add_new_binding(grip_force, "/user/hand/left/input/squeeze/force,/user/hand/right/input/squeeze/force"); // grip force seems to be unique to the Valve Index // primary on our index controller is our thumbstick profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); diff --git a/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp b/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp index 1118b53d65..70879c6b6b 100644 --- a/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile_meta_data.cpp @@ -376,7 +376,9 @@ void OpenXRInteractionProfileMetaData::_register_core_metadata() { register_io_path("/interaction_profiles/valve/index_controller", "Trigger touch", "/user/hand/right", "/user/hand/right/input/trigger/touch", "", OpenXRAction::OPENXR_ACTION_BOOL); register_io_path("/interaction_profiles/valve/index_controller", "Squeeze", "/user/hand/left", "/user/hand/left/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + register_io_path("/interaction_profiles/valve/index_controller", "Squeeze force", "/user/hand/left", "/user/hand/left/input/squeeze/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); register_io_path("/interaction_profiles/valve/index_controller", "Squeeze", "/user/hand/right", "/user/hand/right/input/squeeze/value", "", OpenXRAction::OPENXR_ACTION_FLOAT); + register_io_path("/interaction_profiles/valve/index_controller", "Squeeze force", "/user/hand/right", "/user/hand/right/input/squeeze/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); register_io_path("/interaction_profiles/valve/index_controller", "Thumbstick", "/user/hand/left", "/user/hand/left/input/thumbstick", "", OpenXRAction::OPENXR_ACTION_VECTOR2); register_io_path("/interaction_profiles/valve/index_controller", "Thumbstick click", "/user/hand/left", "/user/hand/left/input/thumbstick/click", "", OpenXRAction::OPENXR_ACTION_BOOL); diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 84279635b5..2c855c3cde 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -80,7 +80,9 @@ public: // this happens right before physics process and normal processing is run. // This is when controller data is queried and made available to game logic. virtual void on_process() {} - virtual void on_pre_render() {} // `on_pre_render` is called right before we start rendering our XR viewport. + virtual void on_pre_render() {} // `on_pre_render` is called right before we start rendering our XR viewports. + virtual void on_pre_draw_viewport(RID p_render_target) {} // `on_pre_draw_viewport` is called right before we start rendering this viewport + virtual void on_post_draw_viewport(RID p_render_target) {} // `on_port_draw_viewport` is called right after we start rendering this viewport (note that on Vulkan draw commands may only be queued) virtual void on_state_idle() {} // `on_state_idle` is called when the OpenXR session state is changed to idle. virtual void on_state_ready() {} // `on_state_ready` is called when the OpenXR session state is changed to ready, this means OpenXR is ready to setup our session. diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index 0d201161f1..20ccfe3906 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -37,6 +37,28 @@ #include "servers/rendering/rendering_server_globals.h" #include "servers/rendering_server.h" +// OpenXR requires us to submit sRGB textures so that it recognises the content +// as being in sRGB color space. We do fall back on "normal" textures but this +// will likely result in incorrect colors as OpenXR will double the sRGB conversion. +// All major XR runtimes support sRGB textures. + +// In OpenGL output of the fragment shader is assumed to be in the color space of +// the developers choice, however a linear to sRGB HW conversion can be enabled +// through enabling GL_FRAMEBUFFER_SRGB if an sRGB color attachment is used. +// This is a global setting. +// See: https://www.khronos.org/opengl/wiki/Framebuffer + +// In OpenGLES output of the fragment shader is assumed to be in linear color space +// and will be converted by default to sRGB if an sRGB color attachment is used. +// The extension GL_EXT_sRGB_write_control was introduced to enable turning this +// feature off. +// See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_sRGB_write_control.txt + +// On OpenGLES this is not defined in our standard headers.. +#ifndef GL_FRAMEBUFFER_SRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif + HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; @@ -157,8 +179,8 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex } void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { - p_usable_swap_chains.push_back(GL_RGBA8); p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); + p_usable_swap_chains.push_back(GL_RGBA8); } void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_depth_formats) { @@ -168,6 +190,23 @@ void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_d p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT24); } +void OpenXROpenGLExtension::on_pre_draw_viewport(RID p_render_target) { + if (srgb_ext_is_available) { + hw_linear_to_srgb_is_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); + if (hw_linear_to_srgb_is_enabled) { + // Disable this. + glDisable(GL_FRAMEBUFFER_SRGB); + } + } +} + +void OpenXROpenGLExtension::on_post_draw_viewport(RID p_render_target) { + if (srgb_ext_is_available && hw_linear_to_srgb_is_enabled) { + // Re-enable this. + glEnable(GL_FRAMEBUFFER_SRGB); + } +} + bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); ERR_FAIL_NULL_V(texture_storage, false); diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index 03a640cbfb..29a9f35c51 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -79,6 +79,9 @@ public: virtual void on_instance_created(const XrInstance p_instance) override; virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; + virtual void on_pre_draw_viewport(RID p_render_target) override; + virtual void on_post_draw_viewport(RID p_render_target) override; + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; @@ -103,6 +106,9 @@ private: Vector<RID> texture_rids; }; + bool srgb_ext_is_available = true; + bool hw_linear_to_srgb_is_enabled = false; + bool check_graphics_api_support(XrVersion p_desired_version); #ifdef ANDROID_ENABLED diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index ddb3114b59..af59fe7dde 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1815,6 +1815,10 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { } } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_pre_draw_viewport(p_render_target); + } + return true; } @@ -1839,7 +1843,9 @@ void OpenXRAPI::post_draw_viewport(RID p_render_target) { return; } - // Nothing to do here at this point in time... + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_post_draw_viewport(p_render_target); + } }; void OpenXRAPI::end_frame() { diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 3463cb5d9d..3cf6288fef 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -386,6 +386,8 @@ int64_t TextServerAdvanced::_get_features() const { void TextServerAdvanced::_free_rid(const RID &p_rid) { _THREAD_SAFE_METHOD_ if (font_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + FontAdvanced *fd = font_owner.get_or_null(p_rid); { MutexLock lock(fd->mutex); @@ -1321,45 +1323,48 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f // Init dynamic font. #ifdef MODULE_FREETYPE_ENABLED int error = 0; - if (!ft_library) { - error = FT_Init_FreeType(&ft_library); - if (error != 0) { - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); - } + { + MutexLock ftlock(ft_mutex); + if (!ft_library) { + error = FT_Init_FreeType(&ft_library); + if (error != 0) { + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } #ifdef MODULE_SVG_ENABLED - FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); #endif - } - - memset(&fd->stream, 0, sizeof(FT_StreamRec)); - fd->stream.base = (unsigned char *)p_font_data->data_ptr; - fd->stream.size = p_font_data->data_size; - fd->stream.pos = 0; - - FT_Open_Args fargs; - memset(&fargs, 0, sizeof(FT_Open_Args)); - fargs.memory_base = (unsigned char *)p_font_data->data_ptr; - fargs.memory_size = p_font_data->data_size; - fargs.flags = FT_OPEN_MEMORY; - fargs.stream = &fd->stream; + } - int max_index = 0; - FT_Face tmp_face = nullptr; - error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); - if (tmp_face && error == 0) { - max_index = tmp_face->num_faces - 1; - } - if (tmp_face) { - FT_Done_Face(tmp_face); - } + memset(&fd->stream, 0, sizeof(FT_StreamRec)); + fd->stream.base = (unsigned char *)p_font_data->data_ptr; + fd->stream.size = p_font_data->data_size; + fd->stream.pos = 0; + + FT_Open_Args fargs; + memset(&fargs, 0, sizeof(FT_Open_Args)); + fargs.memory_base = (unsigned char *)p_font_data->data_ptr; + fargs.memory_size = p_font_data->data_size; + fargs.flags = FT_OPEN_MEMORY; + fargs.stream = &fd->stream; + + int max_index = 0; + FT_Face tmp_face = nullptr; + error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); + if (tmp_face && error == 0) { + max_index = tmp_face->num_faces - 1; + } + if (tmp_face) { + FT_Done_Face(tmp_face); + } - error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); - if (error) { - FT_Done_Face(fd->face); - fd->face = nullptr; - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); + if (error) { + FT_Done_Face(fd->face); + fd->face = nullptr; + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + } } if (p_font_data->msdf) { @@ -1788,6 +1793,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f } _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_data) { + MutexLock ftlock(ft_mutex); + for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : p_font_data->cache) { memdelete(E.value); } @@ -1894,6 +1901,8 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { fargs.flags = FT_OPEN_MEMORY; fargs.stream = &stream; + MutexLock ftlock(ft_mutex); + FT_Face tmp_face = nullptr; error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); if (error == 0) { @@ -2285,6 +2294,7 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) { ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : fd->cache) { memdelete(E.value); } @@ -2296,6 +2306,7 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); if (fd->cache.has(p_size)) { memdelete(fd->cache[p_size]); fd->cache.erase(p_size); diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index f092fa8cca..ce08cf7694 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -616,6 +616,8 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs); + Mutex ft_mutex; + // HarfBuzz bitmap font interface. static hb_font_funcs_t *funcs; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 15124ad488..8687726287 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -113,6 +113,8 @@ int64_t TextServerFallback::_get_features() const { void TextServerFallback::_free_rid(const RID &p_rid) { _THREAD_SAFE_METHOD_ if (font_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + FontFallback *fd = font_owner.get_or_null(p_rid); { MutexLock lock(fd->mutex); @@ -760,45 +762,48 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f // Init dynamic font. #ifdef MODULE_FREETYPE_ENABLED int error = 0; - if (!ft_library) { - error = FT_Init_FreeType(&ft_library); - if (error != 0) { - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); - } + { + MutexLock ftlock(ft_mutex); + if (!ft_library) { + error = FT_Init_FreeType(&ft_library); + if (error != 0) { + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); + } #ifdef MODULE_SVG_ENABLED - FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); #endif - } - - memset(&fd->stream, 0, sizeof(FT_StreamRec)); - fd->stream.base = (unsigned char *)p_font_data->data_ptr; - fd->stream.size = p_font_data->data_size; - fd->stream.pos = 0; - - FT_Open_Args fargs; - memset(&fargs, 0, sizeof(FT_Open_Args)); - fargs.memory_base = (unsigned char *)p_font_data->data_ptr; - fargs.memory_size = p_font_data->data_size; - fargs.flags = FT_OPEN_MEMORY; - fargs.stream = &fd->stream; + } - int max_index = 0; - FT_Face tmp_face = nullptr; - error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); - if (tmp_face && error == 0) { - max_index = tmp_face->num_faces - 1; - } - if (tmp_face) { - FT_Done_Face(tmp_face); - } + memset(&fd->stream, 0, sizeof(FT_StreamRec)); + fd->stream.base = (unsigned char *)p_font_data->data_ptr; + fd->stream.size = p_font_data->data_size; + fd->stream.pos = 0; + + FT_Open_Args fargs; + memset(&fargs, 0, sizeof(FT_Open_Args)); + fargs.memory_base = (unsigned char *)p_font_data->data_ptr; + fargs.memory_size = p_font_data->data_size; + fargs.flags = FT_OPEN_MEMORY; + fargs.stream = &fd->stream; + + int max_index = 0; + FT_Face tmp_face = nullptr; + error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); + if (tmp_face && error == 0) { + max_index = tmp_face->num_faces - 1; + } + if (tmp_face) { + FT_Done_Face(tmp_face); + } - error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); - if (error) { - FT_Done_Face(fd->face); - fd->face = nullptr; - memdelete(fd); - ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); + if (error) { + FT_Done_Face(fd->face); + fd->face = nullptr; + memdelete(fd); + ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'."); + } } if (p_font_data->msdf) { @@ -909,6 +914,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f } _FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontFallback *p_font_data) { + MutexLock ftlock(ft_mutex); + for (const KeyValue<Vector2i, FontForSizeFallback *> &E : p_font_data->cache) { memdelete(E.value); } @@ -1012,6 +1019,8 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { fargs.flags = FT_OPEN_MEMORY; fargs.stream = &stream; + MutexLock ftlock(ft_mutex); + FT_Face tmp_face = nullptr; error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); if (error == 0) { @@ -1393,6 +1402,7 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) { ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); for (const KeyValue<Vector2i, FontForSizeFallback *> &E : fd->cache) { memdelete(E.value); } @@ -1404,6 +1414,7 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); + MutexLock ftlock(ft_mutex); if (fd->cache.has(p_size)) { memdelete(fd->cache[p_size]); fd->cache.erase(p_size); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 12ed21ee95..d9e471154d 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -531,6 +531,8 @@ class TextServerFallback : public TextServerExtension { void _realign(ShapedTextDataFallback *p_sd) const; + Mutex ft_mutex; + protected: static void _bind_methods(){}; diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index a03da7292b..9b65a52b70 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -74,10 +74,14 @@ import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.Surface; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; import android.view.WindowManager; import android.widget.Button; import android.widget.FrameLayout; @@ -291,14 +295,64 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC editText.setView(mRenderView); io.setEdit(editText); - view.getViewTreeObserver().addOnGlobalLayoutListener(() -> { - Point fullSize = new Point(); - activity.getWindowManager().getDefaultDisplay().getSize(fullSize); - Rect gameSize = new Rect(); - mRenderView.getView().getWindowVisibleDisplayFrame(gameSize); - final int keyboardHeight = fullSize.y - gameSize.bottom; - GodotLib.setVirtualKeyboardHeight(keyboardHeight); - }); + // Listeners for keyboard height. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Report the height of virtual keyboard as it changes during the animation. + final View decorView = activity.getWindow().getDecorView(); + decorView.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP) { + int startBottom, endBottom; + @Override + public void onPrepare(@NonNull WindowInsetsAnimation animation) { + startBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom; + } + + @NonNull + @Override + public WindowInsetsAnimation.Bounds onStart(@NonNull WindowInsetsAnimation animation, @NonNull WindowInsetsAnimation.Bounds bounds) { + endBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom; + return bounds; + } + + @NonNull + @Override + public WindowInsets onProgress(@NonNull WindowInsets windowInsets, @NonNull List<WindowInsetsAnimation> list) { + // Find the IME animation. + WindowInsetsAnimation imeAnimation = null; + for (WindowInsetsAnimation animation : list) { + if ((animation.getTypeMask() & WindowInsets.Type.ime()) != 0) { + imeAnimation = animation; + break; + } + } + // Update keyboard height based on IME animation. + if (imeAnimation != null) { + float interpolatedFraction = imeAnimation.getInterpolatedFraction(); + // Linear interpolation between start and end values. + float keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction; + GodotLib.setVirtualKeyboardHeight((int)keyboardHeight); + } + return windowInsets; + } + + @Override + public void onEnd(@NonNull WindowInsetsAnimation animation) { + } + }); + } else { + // Infer the virtual keyboard height using visible area. + view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + // Don't allocate a new Rect every time the callback is called. + final Rect visibleSize = new Rect(); + + @Override + public void onGlobalLayout() { + final SurfaceView view = mRenderView.getView(); + view.getWindowVisibleDisplayFrame(visibleSize); + final int keyboardHeight = view.getHeight() - visibleSize.bottom; + GodotLib.setVirtualKeyboardHeight(keyboardHeight); + } + }); + } mRenderView.queueOnRenderThread(() -> { for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt index 833ab40af0..8ee3d5f48f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt @@ -76,6 +76,13 @@ internal enum class StorageScope { return UNKNOWN } + // If we have 'All Files Access' permission, we can access all directories without + // restriction. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + && Environment.isExternalStorageManager()) { + return APP + } + val canonicalPathFile = pathFile.canonicalPath if (internalAppDir != null && canonicalPathFile.startsWith(internalAppDir)) { @@ -90,7 +97,7 @@ internal enum class StorageScope { return APP } - var rootDir: String? = System.getenv("ANDROID_ROOT") + val rootDir: String? = System.getenv("ANDROID_ROOT") if (rootDir != null && canonicalPathFile.startsWith(rootDir)) { return APP } diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index e203dca005..54351757cd 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -70,7 +70,7 @@ def configure(env: "Environment"): 'Unsupported CPU architecture "%s" for Linux / *BSD. Supported architectures are: %s.' % (env["arch"], ", ".join(supported_arches)) ) - sys.exit() + sys.exit(255) ## Build type @@ -208,7 +208,7 @@ def configure(env: "Environment"): "freetype, libpng, zlib, graphite, harfbuzz.\n" "Please specify `builtin_<name>=no` for all of them, or none." ) - sys.exit() + sys.exit(255) if not env["builtin_freetype"]: env.ParseConfig("pkg-config freetype2 --cflags --libs") @@ -307,11 +307,12 @@ def configure(env: "Environment"): if not env["use_sowrap"]: if os.system("pkg-config --exists libpulse") == 0: # 0 means found env.ParseConfig("pkg-config libpulse --cflags --libs") - env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED", "_REENTRANT"]) + env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"]) else: print("Warning: PulseAudio development libraries not found. Disabling the PulseAudio audio driver.") env["pulseaudio"] = False - env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED", "_REENTRANT"]) + else: + env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED", "_REENTRANT"]) if env["dbus"]: if not env["use_sowrap"]: diff --git a/platform/macos/detect.py b/platform/macos/detect.py index cd46dab4f3..e3c1f17b8f 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -242,17 +242,17 @@ def configure(env: "Environment"): env.Append(LINKFLAGS=["-lMoltenVK"]) mvk_found = False - mkv_list = [get_mvk_sdk_path(), "/opt/homebrew/lib", "/usr/local/homebrew/lib", "/opt/local/lib"] + mvk_list = [get_mvk_sdk_path(), "/opt/homebrew/lib", "/usr/local/homebrew/lib", "/opt/local/lib"] if env["vulkan_sdk_path"] != "": - mkv_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"])) - mkv_list.insert( + mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"])) + mvk_list.insert( 0, os.path.join( os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" ), ) - for mvk_path in mkv_list: + for mvk_path in mvk_list: if mvk_path and os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): mvk_found = True print("MoltenVK found at: " + mvk_path) diff --git a/platform/web/api/web_tools_editor_plugin.cpp b/platform/web/api/web_tools_editor_plugin.cpp index 146a48db81..213204ff33 100644 --- a/platform/web/api/web_tools_editor_plugin.cpp +++ b/platform/web/api/web_tools_editor_plugin.cpp @@ -75,7 +75,7 @@ void WebToolsEditorPlugin::_download_zip() { const String project_name_safe = project_name.to_lower().replace(" ", "_"); const String datetime_safe = Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); - const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip")); + const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip", project_name_safe, datetime_safe)); const String output_path = String("/tmp").path_join(output_name); zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 66dc9a2a5a..e0a08cf7e1 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3418,9 +3418,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA gr_mem = alt_mem; } } - if (wParam == VK_LWIN || wParam == VK_RWIN) { - meta_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); - } if (windows[window_id].ime_suppress_next_keyup && (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)) { windows[window_id].ime_suppress_next_keyup = false; diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 8f7006caca..7de6219b10 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -301,13 +301,13 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { frames->get_animation_list(&al); if (al.size() == 0) { set_animation(StringName()); - set_autoplay(String()); + autoplay = String(); } else { if (!frames->has_animation(animation)) { set_animation(al[0]); } if (!frames->has_animation(autoplay)) { - set_autoplay(String()); + autoplay = String(); } } } diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index bafb83361a..5b743e0b31 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -151,11 +151,18 @@ bool ShapeCast2D::is_enabled() const { } void ShapeCast2D::set_shape(const Ref<Shape2D> &p_shape) { + if (p_shape == shape) { + return; + } + if (shape.is_valid()) { + shape->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_shape_changed)); + } shape = p_shape; - if (p_shape.is_valid()) { - shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_redraw_shape)); + if (shape.is_valid()) { + shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast2D::_shape_changed)); shape_rid = shape->get_rid(); } + update_configuration_warnings(); queue_redraw(); } @@ -186,7 +193,7 @@ bool ShapeCast2D::get_exclude_parent_body() const { return exclude_parent_body; } -void ShapeCast2D::_redraw_shape() { +void ShapeCast2D::_shape_changed() { queue_redraw(); } diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index 8a62b799f8..a577c351fd 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -61,7 +61,7 @@ class ShapeCast2D : public Node2D { real_t collision_unsafe_fraction = 1.0; Array _get_collision_result() const; - void _redraw_shape(); + void _shape_changed(); protected: void _notification(int p_what); diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index d880e422f0..602eb664dd 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -331,16 +331,14 @@ void ShapeCast3D::set_shape(const Ref<Shape3D> &p_shape) { if (p_shape == shape) { return; } - if (!shape.is_null()) { + if (shape.is_valid()) { shape->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast3D::_shape_changed)); shape->unregister_owner(this); } shape = p_shape; - if (!shape.is_null()) { + if (shape.is_valid()) { shape->register_owner(this); shape->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &ShapeCast3D::_shape_changed)); - } - if (p_shape.is_valid()) { shape_rid = shape->get_rid(); } diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 55fc5aa2ae..fe4b8d4809 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -108,7 +108,7 @@ private: /* Line numbers */ int line_number_gutter = -1; - int line_number_digits = 0; + int line_number_digits = 1; String line_number_padding = " "; Color line_number_color = Color(1, 1, 1); void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index ec75fcb665..3ecb9c47c0 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -360,7 +360,7 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const { void Control::_get_property_list(List<PropertyInfo> *p_list) const { Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme(); - p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Theme Overrides", "theme_override_"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); { List<StringName> names; @@ -371,7 +371,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::COLOR, "theme_override_colors/" + E, PROPERTY_HINT_NONE, "", usage)); + p_list->push_back(PropertyInfo(Variant::COLOR, PNAME("theme_override_colors") + String("/") + E, PROPERTY_HINT_NONE, "", usage)); } } { @@ -383,7 +383,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "theme_override_constants/" + E, PROPERTY_HINT_RANGE, "-16384,16384", usage)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("theme_override_constants") + String("/") + E, PROPERTY_HINT_RANGE, "-16384,16384", usage)); } } { @@ -395,7 +395,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_fonts/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("theme_override_fonts") + String("/") + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); } } { @@ -407,7 +407,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("theme_override_font_sizes") + String("/") + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage)); } } { @@ -419,7 +419,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_icons/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("theme_override_icons") + String("/") + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); } } { @@ -431,7 +431,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_styles/" + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("theme_override_styles") + String("/") + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); } } } @@ -2793,19 +2793,34 @@ bool Control::is_layout_rtl() const { if (data.is_rtl_dirty) { const_cast<Control *>(this)->data.is_rtl_dirty = false; if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) { - Window *parent_window = get_parent_window(); - Control *parent_control = get_parent_control(); - if (parent_control) { - const_cast<Control *>(this)->data.is_rtl = parent_control->is_layout_rtl(); - } else if (parent_window) { - const_cast<Control *>(this)->data.is_rtl = parent_window->is_layout_rtl(); - } else { - if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { - const_cast<Control *>(this)->data.is_rtl = true; - } else { - String locale = TranslationServer::get_singleton()->get_tool_locale(); - const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + const_cast<Control *>(this)->data.is_rtl = true; + return data.is_rtl; + } + Node *parent_node = get_parent(); + while (parent_node) { + Control *parent_control = Object::cast_to<Control>(parent_node); + if (parent_control) { + const_cast<Control *>(this)->data.is_rtl = parent_control->is_layout_rtl(); + return data.is_rtl; } + + Window *parent_window = Object::cast_to<Window>(parent_node); + if (parent_window) { + const_cast<Control *>(this)->data.is_rtl = parent_window->is_layout_rtl(); + return data.is_rtl; + } + parent_node = parent_node->get_parent(); + } + + int root_dir = GLOBAL_GET(SNAME("internationalization/rendering/root_node_layout_direction")); + if (root_dir == 1) { + const_cast<Control *>(this)->data.is_rtl = false; + } else if (root_dir == 2) { + const_cast<Control *>(this)->data.is_rtl = true; + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); } } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) { if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { @@ -3230,7 +3245,7 @@ void Control::_bind_methods() { ADD_GROUP("Layout", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Based on Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 58b820c31f..9c0c25b1ac 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -839,6 +839,14 @@ bool GraphEdit::is_in_input_hotzone(GraphNode *p_node, int p_port, const Vector2 } bool GraphEdit::is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { + if (p_node->is_resizable()) { + Ref<Texture2D> resizer = p_node->get_theme_icon(SNAME("resizer")); + Rect2 resizer_rect = Rect2(p_node->get_position() / zoom + p_node->get_size() - resizer->get_size(), resizer->get_size()); + if (resizer_rect.has_point(p_mouse_pos)) { + return false; + } + } + bool success; if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_node, p_port, p_mouse_pos, success)) { return success; @@ -848,10 +856,10 @@ bool GraphEdit::is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector } } -bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { +bool GraphEdit::is_in_port_hotzone(const Vector2 &p_pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { Rect2 hotzone = Rect2( - pos.x - (p_left ? port_hotzone_outer_extent : port_hotzone_inner_extent), - pos.y - p_port_size.height / 2.0, + p_pos.x - (p_left ? port_hotzone_outer_extent : port_hotzone_inner_extent), + p_pos.y - p_port_size.height / 2.0, port_hotzone_inner_extent + port_hotzone_outer_extent, p_port_size.height); @@ -1175,9 +1183,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { minimap->queue_redraw(); } - Ref<InputEventMouseButton> b = p_ev; - if (b.is_valid()) { - if (b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { + Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid()) { + if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { if (box_selecting) { box_selecting = false; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1194,12 +1202,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (connecting) { force_connection_drag_end(); } else { - emit_signal(SNAME("popup_request"), b->get_position()); + emit_signal(SNAME("popup_request"), mb->get_position()); } } } - if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && dragging) { + if (mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed() && dragging) { if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) { //deselect current node for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1208,7 +1216,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (gn) { Rect2 r = gn->get_rect(); r.size *= zoom; - if (r.has_point(b->get_position())) { + if (r.has_point(mb->get_position())) { gn->set_selected(false); } } @@ -1240,7 +1248,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { connections_layer->queue_redraw(); } - if (b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { GraphNode *gn = nullptr; // Find node which was clicked on. @@ -1255,14 +1263,14 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { continue; } - if (gn_selected->has_point((b->get_position() - gn_selected->get_position()) / zoom)) { + if (gn_selected->has_point((mb->get_position() - gn_selected->get_position()) / zoom)) { gn = gn_selected; break; } } if (gn) { - if (_filter_input(b->get_position())) { + if (_filter_input(mb->get_position())) { return; } @@ -1297,7 +1305,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } else { - if (_filter_input(b->get_position())) { + if (_filter_input(mb->get_position())) { return; } if (panner->is_panning()) { @@ -1306,8 +1314,8 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Left-clicked on empty space, start box select. box_selecting = true; - box_selecting_from = b->get_position(); - if (b->is_ctrl_pressed()) { + box_selecting_from = mb->get_position(); + if (mb->is_ctrl_pressed()) { box_selection_mode_additive = true; previous_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1318,7 +1326,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { previous_selected.push_back(gn2); } - } else if (b->is_shift_pressed()) { + } else if (mb->is_shift_pressed()) { box_selection_mode_additive = false; previous_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1344,7 +1352,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } - if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && box_selecting) { + if (mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed() && box_selecting) { // Box selection ended. Nodes were selected during mouse movement. box_selecting = false; box_selecting_rect = Rect2(); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index f8d2ff0d2c..7d9cdd62ff 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -805,13 +805,13 @@ void GraphNode::_connpos_update() { Size2i size = c->get_rect().size; int y = sb->get_margin(SIDE_TOP) + vofs; - int h = size.y; + int h = size.height; if (slot_info.has(idx)) { if (slot_info[idx].enable_left) { PortCache cc; cc.position = Point2i(edgeofs, y + h / 2); - cc.height = size.height; + cc.height = h; cc.slot_idx = idx; cc.type = slot_info[idx].type_left; @@ -822,7 +822,7 @@ void GraphNode::_connpos_update() { if (slot_info[idx].enable_right) { PortCache cc; cc.position = Point2i(get_size().width - edgeofs, y + h / 2); - cc.height = size.height; + cc.height = h; cc.slot_idx = idx; cc.type = slot_info[idx].type_right; @@ -833,7 +833,7 @@ void GraphNode::_connpos_update() { } vofs += sep; - vofs += size.y; + vofs += h; idx++; } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index a57dccd5c8..6b2faf1a40 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -638,6 +638,11 @@ HorizontalAlignment LineEdit::get_horizontal_alignment() const { } Variant LineEdit::get_drag_data(const Point2 &p_point) { + Variant ret = Control::get_drag_data(p_point); + if (ret != Variant()) { + return ret; + } + if (selection.drag_attempt && selection.enabled) { String t = text.substr(selection.begin, selection.end - selection.begin); Label *l = memnew(Label); @@ -1068,8 +1073,11 @@ void LineEdit::_notification(int p_what) { if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + pos, get_viewport()->get_window_id()); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); } } } break; @@ -1088,8 +1096,11 @@ void LineEdit::_notification(int p_what) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + pos, get_viewport()->get_window_id()); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); } show_virtual_keyboard(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index ec1fbb7e28..68e12b9bb5 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4735,6 +4735,11 @@ void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) { } Variant RichTextLabel::get_drag_data(const Point2 &p_point) { + Variant ret = Control::get_drag_data(p_point); + if (ret != Variant()) { + return ret; + } + if (selection.drag_attempt && selection.enabled) { String t = get_selected_text(); Label *l = memnew(Label); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index b8faf22a59..fcf9302953 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -55,12 +55,14 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { accept_event(); if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) { - set_value(get_value() + get_page() / 4.0); + double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0; + set_value(get_value() + MAX(change, get_step())); accept_event(); } if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) { - set_value(get_value() - get_page() / 4.0); + double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0; + set_value(get_value() - MAX(change, get_step())); accept_event(); } @@ -99,7 +101,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (scrolling) { target_scroll = CLAMP(target_scroll - get_page(), get_min(), get_max() - get_page()); } else { - target_scroll = CLAMP(get_value() - get_page(), get_min(), get_max() - get_page()); + double change = get_page() != 0.0 ? get_page() : (get_max() - get_min()) / 16.0; + target_scroll = CLAMP(get_value() - change, get_min(), get_max() - get_page()); } if (smooth_scroll_enabled) { @@ -122,7 +125,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (scrolling) { target_scroll = CLAMP(target_scroll + get_page(), get_min(), get_max() - get_page()); } else { - target_scroll = CLAMP(get_value() + get_page(), get_min(), get_max() - get_page()); + double change = get_page() != 0.0 ? get_page() : (get_max() - get_min()) / 16.0; + target_scroll = CLAMP(get_value() + change, get_min(), get_max() - get_page()); } if (smooth_scroll_enabled) { diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index f10e1c2cd1..f9e96a44ed 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -58,6 +58,7 @@ void SubViewportContainer::set_stretch(bool p_enable) { } stretch = p_enable; + recalc_force_viewport_sizes(); update_minimum_size(); queue_sort(); queue_redraw(); @@ -75,10 +76,16 @@ void SubViewportContainer::set_stretch_shrink(int p_shrink) { shrink = p_shrink; + recalc_force_viewport_sizes(); + queue_redraw(); +} + +void SubViewportContainer::recalc_force_viewport_sizes() { if (!stretch) { return; } + // If stretch is enabled, make sure that all child SubViwewports have the correct size. for (int i = 0; i < get_child_count(); i++) { SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); if (!c) { @@ -87,8 +94,6 @@ void SubViewportContainer::set_stretch_shrink(int p_shrink) { c->set_size_force(get_size() / shrink); } - - queue_redraw(); } int SubViewportContainer::get_stretch_shrink() const { @@ -106,18 +111,7 @@ Vector<int> SubViewportContainer::get_allowed_size_flags_vertical() const { void SubViewportContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_RESIZED: { - if (!stretch) { - return; - } - - for (int i = 0; i < get_child_count(); i++) { - SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); - if (!c) { - continue; - } - - c->set_size_force(get_size() / shrink); - } + recalc_force_viewport_sizes(); } break; case NOTIFICATION_ENTER_TREE: diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index d3236b0c4e..c1a74e5b98 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -58,6 +58,7 @@ public: virtual void unhandled_input(const Ref<InputEvent> &p_event) override; void set_stretch_shrink(int p_shrink); int get_stretch_shrink() const; + void recalc_force_viewport_sizes(); virtual Size2 get_minimum_size() const override; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 8f425436b7..30bac0f58c 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1480,7 +1480,11 @@ void TextEdit::_notification(int p_what) { if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + get_caret_draw_pos(), get_viewport()->get_window_id()); + Point2 pos = get_global_position() + get_caret_draw_pos(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); } } } break; @@ -1494,7 +1498,11 @@ void TextEdit::_notification(int p_what) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + get_caret_draw_pos(), get_viewport()->get_window_id()); + Point2 pos = get_global_position() + get_caret_draw_pos(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id()); } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { @@ -3027,6 +3035,11 @@ bool TextEdit::is_text_field() const { } Variant TextEdit::get_drag_data(const Point2 &p_point) { + Variant ret = Control::get_drag_data(p_point); + if (ret != Variant()) { + return ret; + } + if (has_selection() && selection_drag_attempt) { String t = get_selected_text(); Label *l = memnew(Label); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index b36353158b..72fb838732 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -289,11 +289,12 @@ void CanvasItem::_notification(int p_what) { } } + _enter_canvas(); + RenderingServer::get_singleton()->canvas_item_set_visible(canvas_item, is_visible_in_tree()); // The visibility of the parent may change. if (is_visible_in_tree()) { notification(NOTIFICATION_VISIBILITY_CHANGED); // Considered invisible until entered. } - _enter_canvas(); _update_texture_filter_changed(false); _update_texture_repeat_changed(false); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 244e0d5b93..8cd57536bf 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -4281,6 +4281,11 @@ void SubViewport::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); + + SubViewportContainer *parent_svc = Object::cast_to<SubViewportContainer>(get_parent()); + if (parent_svc) { + parent_svc->recalc_force_viewport_sizes(); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -4323,6 +4328,17 @@ void SubViewport::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_ALWAYS); } +void SubViewport::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "size") { + SubViewportContainer *parent_svc = Object::cast_to<SubViewportContainer>(get_parent()); + if (parent_svc && parent_svc->is_stretch_enabled()) { + p_property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY; + } else { + p_property.usage = PROPERTY_USAGE_DEFAULT; + } + } +} + SubViewport::SubViewport() { RS::get_singleton()->viewport_set_size(get_viewport_rid(), get_size().width, get_size().height); } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 055fad5369..5213c0db01 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -784,6 +784,7 @@ public: virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override; virtual Transform2D get_popup_base_transform() const override; + void _validate_property(PropertyInfo &p_property) const; SubViewport(); ~SubViewport(); }; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 9fb4ed458f..9bbb91fe1b 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -146,7 +146,7 @@ bool Window::_get(const StringName &p_name, Variant &r_ret) const { void Window::_get_property_list(List<PropertyInfo> *p_list) const { Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme(); - p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Theme Overrides", "theme_override_"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); { List<StringName> names; @@ -157,7 +157,7 @@ void Window::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::COLOR, "theme_override_colors/" + E, PROPERTY_HINT_NONE, "", usage)); + p_list->push_back(PropertyInfo(Variant::COLOR, PNAME("theme_override_colors") + String("/") + E, PROPERTY_HINT_NONE, "", usage)); } } { @@ -169,7 +169,7 @@ void Window::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "theme_override_constants/" + E, PROPERTY_HINT_RANGE, "-16384,16384", usage)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("theme_override_constants") + String("/") + E, PROPERTY_HINT_RANGE, "-16384,16384", usage)); } } { @@ -181,7 +181,7 @@ void Window::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_fonts/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("theme_override_fonts") + String("/") + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); } } { @@ -193,7 +193,7 @@ void Window::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("theme_override_font_sizes") + String("/") + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage)); } } { @@ -205,7 +205,7 @@ void Window::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_icons/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("theme_override_icons") + String("/") + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); } } { @@ -217,7 +217,7 @@ void Window::_get_property_list(List<PropertyInfo> *p_list) const { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_styles/" + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("theme_override_styles") + String("/") + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); } } } @@ -2116,22 +2116,39 @@ Window::LayoutDirection Window::get_layout_direction() const { bool Window::is_layout_rtl() const { if (layout_dir == LAYOUT_DIRECTION_INHERITED) { - Window *parent_w = Object::cast_to<Window>(get_parent()); - if (parent_w) { - return parent_w->is_layout_rtl(); - } else { - if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { - return true; + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + return true; + } + Node *parent_node = get_parent(); + while (parent_node) { + Control *parent_control = Object::cast_to<Control>(parent_node); + if (parent_control) { + return parent_control->is_layout_rtl(); } + + Window *parent_window = Object::cast_to<Window>(parent_node); + if (parent_window) { + return parent_window->is_layout_rtl(); + } + parent_node = parent_node->get_parent(); + } + + int root_dir = GLOBAL_GET(SNAME("internationalization/rendering/root_node_layout_direction")); + if (root_dir == 1) { + return false; + } else if (root_dir == 2) { + return true; + } else { String locale = TranslationServer::get_singleton()->get_tool_locale(); return TS->is_locale_right_to_left(locale); } } else if (layout_dir == LAYOUT_DIRECTION_LOCALE) { if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { return true; + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + return TS->is_locale_right_to_left(locale); } - String locale = TranslationServer::get_singleton()->get_tool_locale(); - return TS->is_locale_right_to_left(locale); } else { return (layout_dir == LAYOUT_DIRECTION_RTL); } diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp index 0209bf9aec..eafc4bec7d 100644 --- a/scene/resources/gradient.cpp +++ b/scene/resources/gradient.cpp @@ -153,6 +153,7 @@ void Gradient::reverse() { points.write[i].offset = 1.0 - points[i].offset; } + is_sorted = false; _update_sorting(); emit_signal(CoreStringNames::get_singleton()->changed); } diff --git a/scene/resources/shape_3d.cpp b/scene/resources/shape_3d.cpp index 18a4001f38..5a79392ba5 100644 --- a/scene/resources/shape_3d.cpp +++ b/scene/resources/shape_3d.cpp @@ -117,7 +117,7 @@ void Shape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_debug_mesh"), &Shape3D::get_debug_mesh); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001,suffix:m"), "set_margin", "get_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater,suffix:m"), "set_margin", "get_margin"); } Shape3D::Shape3D() { diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 651bad1aa7..282c531555 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -2935,6 +2935,10 @@ TypedArray<Image> ImageTextureLayered::_get_images() const { return images; } +void ImageTextureLayered::_set_images(const TypedArray<Image> &p_images) { + ERR_FAIL_COND(_create_from_images(p_images) != OK); +} + Error ImageTextureLayered::create_from_images(Vector<Ref<Image>> p_images) { int new_layers = p_images.size(); ERR_FAIL_COND_V(new_layers == 0, ERR_INVALID_PARAMETER); @@ -3014,8 +3018,9 @@ void ImageTextureLayered::_bind_methods() { ClassDB::bind_method(D_METHOD("update_layer", "image", "layer"), &ImageTextureLayered::update_layer); ClassDB::bind_method(D_METHOD("_get_images"), &ImageTextureLayered::_get_images); + ClassDB::bind_method(D_METHOD("_set_images", "images"), &ImageTextureLayered::_set_images); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_images", PROPERTY_HINT_ARRAY_TYPE, "Image", PROPERTY_USAGE_INTERNAL), "create_from_images", "_get_images"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_images", PROPERTY_HINT_ARRAY_TYPE, "Image", PROPERTY_USAGE_INTERNAL), "_set_images", "_get_images"); } ImageTextureLayered::ImageTextureLayered(LayeredType p_layered_type) { diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 7c4d479da8..50bcec58f6 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -427,6 +427,7 @@ class ImageTextureLayered : public TextureLayered { Error _create_from_images(const TypedArray<Image> &p_images); TypedArray<Image> _get_images() const; + void _set_images(const TypedArray<Image> &p_images); protected: static void _bind_methods(); diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 9f5a64597a..7fdad8e930 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -1709,8 +1709,11 @@ String VisualShaderNodeLinearSceneDepth::generate_code(Shader::Mode p_mode, Visu code += " {\n"; code += " float __log_depth = textureLod(" + make_unique_id(p_type, p_id, "depth_tex") + ", SCREEN_UV, 0.0).x;\n"; - code += " vec3 __depth_ndc = vec3(SCREEN_UV * 2.0 - 1.0, __log_depth);\n"; - code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(__depth_ndc, 1.0);\n"; + if (!RenderingServer::get_singleton()->is_low_end()) { + code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, __log_depth, 1.0);\n"; + } else { + code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(vec3(SCREEN_UV, __log_depth) * 2.0 - 1.0, 1.0);\n"; + } code += " __depth_view.xyz /= __depth_view.w;\n"; code += vformat(" %s = -__depth_view.z;\n", p_output_vars[0]); diff --git a/servers/physics_3d/godot_body_pair_3d.cpp b/servers/physics_3d/godot_body_pair_3d.cpp index 3a91e5e480..292563b6ab 100644 --- a/servers/physics_3d/godot_body_pair_3d.cpp +++ b/servers/physics_3d/godot_body_pair_3d.cpp @@ -237,17 +237,10 @@ bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, Vector3 hitpos = p_xform_B.xform(segment_hit_local); real_t newlen = hitpos.distance_to(supports_A[segment_support_idx]); - if (shape_B_ptr->is_concave()) { - // Subtracting 5% of body length from the distance between collision and support point - // should cause body A's support point to arrive just before a face of B next frame. - newlen = MAX(newlen - (max - min) * 0.05, 0.0); - // NOTE: This may stop body A completely, without a proper collision response. - // We consider this preferable to tunneling. - } else { - // Adding 1% of body length to the distance between collision and support point - // should cause body A's support point to arrive just within B's collider next frame. - newlen += (max - min) * 0.01; - } + // Adding 1% of body length to the distance between collision and support point + // should cause body A's support point to arrive just within B's collider next frame. + newlen += (max - min) * 0.01; + // FIXME: This doesn't always work well when colliding with a triangle face of a trimesh shape. p_A->set_linear_velocity((mnormal * newlen) / p_step); diff --git a/servers/physics_server_2d.cpp b/servers/physics_server_2d.cpp index f9a8d5f156..4ad83ef327 100644 --- a/servers/physics_server_2d.cpp +++ b/servers/physics_server_2d.cpp @@ -409,17 +409,17 @@ Vector<real_t> PhysicsDirectSpaceState2D::_cast_motion(const Ref<PhysicsShapeQue return ret; } -TypedArray<PackedVector2Array> PhysicsDirectSpaceState2D::_collide_shape(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query, int p_max_results) { - ERR_FAIL_COND_V(!p_shape_query.is_valid(), Array()); +TypedArray<Vector2> PhysicsDirectSpaceState2D::_collide_shape(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query, int p_max_results) { + ERR_FAIL_COND_V(!p_shape_query.is_valid(), TypedArray<Vector2>()); Vector<Vector2> ret; ret.resize(p_max_results * 2); int rc = 0; bool res = collide_shape(p_shape_query->get_parameters(), ret.ptrw(), p_max_results, rc); if (!res) { - return TypedArray<PackedVector2Array>(); + return TypedArray<Vector2>(); } - TypedArray<PackedVector2Array> r; + TypedArray<Vector2> r; r.resize(rc * 2); for (int i = 0; i < rc * 2; i++) { r[i] = ret[i]; diff --git a/servers/physics_server_2d.h b/servers/physics_server_2d.h index 3e254e610e..56ec94ba9f 100644 --- a/servers/physics_server_2d.h +++ b/servers/physics_server_2d.h @@ -120,7 +120,7 @@ class PhysicsDirectSpaceState2D : public Object { TypedArray<Dictionary> _intersect_point(const Ref<PhysicsPointQueryParameters2D> &p_point_query, int p_max_results = 32); TypedArray<Dictionary> _intersect_shape(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query, int p_max_results = 32); Vector<real_t> _cast_motion(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query); - TypedArray<PackedVector2Array> _collide_shape(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query, int p_max_results = 32); + TypedArray<Vector2> _collide_shape(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query, int p_max_results = 32); Dictionary _get_rest_info(const Ref<PhysicsShapeQueryParameters2D> &p_shape_query); protected: diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp index c9cf8f99af..9495ce2262 100644 --- a/servers/physics_server_3d.cpp +++ b/servers/physics_server_3d.cpp @@ -429,17 +429,17 @@ Vector<real_t> PhysicsDirectSpaceState3D::_cast_motion(const Ref<PhysicsShapeQue return ret; } -TypedArray<PackedVector3Array> PhysicsDirectSpaceState3D::_collide_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results) { - ERR_FAIL_COND_V(!p_shape_query.is_valid(), Array()); +TypedArray<Vector3> PhysicsDirectSpaceState3D::_collide_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results) { + ERR_FAIL_COND_V(!p_shape_query.is_valid(), TypedArray<Vector3>()); Vector<Vector3> ret; ret.resize(p_max_results * 2); int rc = 0; bool res = collide_shape(p_shape_query->get_parameters(), ret.ptrw(), p_max_results, rc); if (!res) { - return TypedArray<PackedVector3Array>(); + return TypedArray<Vector3>(); } - TypedArray<PackedVector3Array> r; + TypedArray<Vector3> r; r.resize(rc * 2); for (int i = 0; i < rc * 2; i++) { r[i] = ret[i]; diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h index 2c7ebeea66..0275aee818 100644 --- a/servers/physics_server_3d.h +++ b/servers/physics_server_3d.h @@ -125,7 +125,7 @@ private: TypedArray<Dictionary> _intersect_point(const Ref<PhysicsPointQueryParameters3D> &p_point_query, int p_max_results = 32); TypedArray<Dictionary> _intersect_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); Vector<real_t> _cast_motion(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query); - TypedArray<PackedVector3Array> _collide_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); + TypedArray<Vector3> _collide_shape(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query, int p_max_results = 32); Dictionary _get_rest_info(const Ref<PhysicsShapeQueryParameters3D> &p_shape_query); protected: diff --git a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl index 28507e6c12..f961249dce 100644 --- a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl +++ b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl @@ -585,23 +585,14 @@ void main() { if (spot_lights.data[light_index].shadow_opacity > 0.001) { //has shadow vec4 uv_rect = spot_lights.data[light_index].atlas_rect; - vec2 flip_offset = spot_lights.data[light_index].direction.xy; - vec3 local_vert = (spot_lights.data[light_index].shadow_matrix * vec4(view_pos, 1.0)).xyz; + vec4 v = vec4(view_pos, 1.0); - float shadow_len = length(local_vert); //need to remember shadow len from here - vec3 shadow_sample = normalize(local_vert); - - if (shadow_sample.z >= 0.0) { - uv_rect.xy += flip_offset; - } - - shadow_sample.z = 1.0 + abs(shadow_sample.z); - vec3 pos = vec3(shadow_sample.xy / shadow_sample.z, shadow_len - spot_lights.data[light_index].shadow_bias); - pos.z *= spot_lights.data[light_index].inv_radius; + vec4 splane = (spot_lights.data[light_index].shadow_matrix * v); + splane.z -= spot_lights.data[light_index].shadow_bias / (d * spot_lights.data[light_index].inv_radius); + splane /= splane.w; - pos.xy = pos.xy * 0.5 + 0.5; - pos.xy = uv_rect.xy + pos.xy * uv_rect.zw; + vec3 pos = vec3(splane.xy * spot_lights.data[light_index].atlas_rect.zw + spot_lights.data[light_index].atlas_rect.xy, splane.z); float depth = texture(sampler2D(shadow_atlas, linear_sampler), pos.xy).r; diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index 85ef684d40..bab8e9ae4b 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -52,11 +52,14 @@ XRServer *XRServer::get_singleton() { void XRServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_world_scale"), &XRServer::get_world_scale); ClassDB::bind_method(D_METHOD("set_world_scale", "scale"), &XRServer::set_world_scale); + ClassDB::bind_method(D_METHOD("get_world_origin"), &XRServer::get_world_origin); + ClassDB::bind_method(D_METHOD("set_world_origin", "world_origin"), &XRServer::set_world_origin); ClassDB::bind_method(D_METHOD("get_reference_frame"), &XRServer::get_reference_frame); ClassDB::bind_method(D_METHOD("center_on_hmd", "rotation_mode", "keep_height"), &XRServer::center_on_hmd); ClassDB::bind_method(D_METHOD("get_hmd_transform"), &XRServer::get_hmd_transform); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "world_origin"), "set_world_origin", "get_world_origin"); ClassDB::bind_method(D_METHOD("add_interface", "interface"), &XRServer::add_interface); ClassDB::bind_method(D_METHOD("get_interface_count"), &XRServer::get_interface_count); diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index 04d5bb5107..ccb02ed5fa 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -253,6 +253,7 @@ TEST_CASE("[Array] slice()") { array.push_back(2); array.push_back(3); array.push_back(4); + array.push_back(5); Array slice0 = array.slice(0, 0); CHECK(slice0.size() == 0); @@ -263,42 +264,52 @@ TEST_CASE("[Array] slice()") { CHECK(slice1[1] == Variant(2)); Array slice2 = array.slice(1, -1); - CHECK(slice2.size() == 3); + CHECK(slice2.size() == 4); CHECK(slice2[0] == Variant(1)); CHECK(slice2[1] == Variant(2)); CHECK(slice2[2] == Variant(3)); + CHECK(slice2[3] == Variant(4)); Array slice3 = array.slice(3); - CHECK(slice3.size() == 2); + CHECK(slice3.size() == 3); CHECK(slice3[0] == Variant(3)); CHECK(slice3[1] == Variant(4)); + CHECK(slice3[2] == Variant(5)); Array slice4 = array.slice(2, -2); - CHECK(slice4.size() == 1); + CHECK(slice4.size() == 2); CHECK(slice4[0] == Variant(2)); + CHECK(slice4[1] == Variant(3)); Array slice5 = array.slice(-2); CHECK(slice5.size() == 2); - CHECK(slice5[0] == Variant(3)); - CHECK(slice5[1] == Variant(4)); + CHECK(slice5[0] == Variant(4)); + CHECK(slice5[1] == Variant(5)); Array slice6 = array.slice(2, 42); - CHECK(slice6.size() == 3); + CHECK(slice6.size() == 4); CHECK(slice6[0] == Variant(2)); CHECK(slice6[1] == Variant(3)); CHECK(slice6[2] == Variant(4)); + CHECK(slice6[3] == Variant(5)); Array slice7 = array.slice(4, 0, -2); CHECK(slice7.size() == 2); CHECK(slice7[0] == Variant(4)); CHECK(slice7[1] == Variant(2)); - ERR_PRINT_OFF; - Array slice8 = array.slice(4, 1); - CHECK(slice8.size() == 0); + Array slice8 = array.slice(5, 0, -2); + CHECK(slice8.size() == 3); + CHECK(slice8[0] == Variant(5)); + CHECK(slice8[1] == Variant(3)); + CHECK(slice8[2] == Variant(1)); - Array slice9 = array.slice(3, -4); + ERR_PRINT_OFF; + Array slice9 = array.slice(4, 1); CHECK(slice9.size() == 0); + + Array slice10 = array.slice(3, -4); + CHECK(slice10.size() == 0); ERR_PRINT_ON; } |