diff options
54 files changed, 1120 insertions, 330 deletions
diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 1f46223a1d..67aa2bbbfb 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -613,6 +613,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_NONE); diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 65353d8118..ce5e9aa9b3 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -365,12 +365,12 @@ Basis Basis::rotated_local(const Vector3 &p_axis, real_t p_angle) const { return (*this) * Basis(p_axis, p_angle); } -Basis Basis::rotated(const Vector3 &p_euler) const { - return Basis(p_euler) * (*this); +Basis Basis::rotated(const Vector3 &p_euler, EulerOrder p_order) const { + return Basis::from_euler(p_euler, p_order) * (*this); } -void Basis::rotate(const Vector3 &p_euler) { - *this = rotated(p_euler); +void Basis::rotate(const Vector3 &p_euler, EulerOrder p_order) { + *this = rotated(p_euler, p_order); } Basis Basis::rotated(const Quaternion &p_quaternion) const { @@ -935,9 +935,9 @@ void Basis::set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Ve rotate(p_axis, p_angle); } -void Basis::set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale) { +void Basis::set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order) { _set_diagonal(p_scale); - rotate(p_euler); + rotate(p_euler, p_order); } void Basis::set_quaternion_scale(const Quaternion &p_quaternion, const Vector3 &p_scale) { diff --git a/core/math/basis.h b/core/math/basis.h index 9cce22510b..4be325cdd2 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -56,6 +56,15 @@ struct _NO_DISCARD_ Basis { _FORCE_INLINE_ real_t determinant() const; + enum EulerOrder { + EULER_ORDER_XYZ, + EULER_ORDER_XZY, + EULER_ORDER_YXZ, + EULER_ORDER_YZX, + EULER_ORDER_ZXY, + EULER_ORDER_ZYX + }; + void from_z(const Vector3 &p_z); void rotate(const Vector3 &p_axis, real_t p_angle); @@ -64,21 +73,12 @@ struct _NO_DISCARD_ Basis { void rotate_local(const Vector3 &p_axis, real_t p_angle); Basis rotated_local(const Vector3 &p_axis, real_t p_angle) const; - void rotate(const Vector3 &p_euler); - Basis rotated(const Vector3 &p_euler) const; + void rotate(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ); + Basis rotated(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ) const; void rotate(const Quaternion &p_quaternion); Basis rotated(const Quaternion &p_quaternion) const; - enum EulerOrder { - EULER_ORDER_XYZ, - EULER_ORDER_XZY, - EULER_ORDER_YXZ, - EULER_ORDER_YZX, - EULER_ORDER_ZXY, - EULER_ORDER_ZYX - }; - Vector3 get_euler_normalized(EulerOrder p_order = EULER_ORDER_YXZ) const; void get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const; void get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const; @@ -119,7 +119,7 @@ struct _NO_DISCARD_ Basis { Vector3 get_scale_local() const; void set_axis_angle_scale(const Vector3 &p_axis, real_t p_angle, const Vector3 &p_scale); - void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale); + void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale, EulerOrder p_order = EULER_ORDER_YXZ); void set_quaternion_scale(const Quaternion &p_quaternion, const Vector3 &p_scale); // transposed dot products diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index c8a55341aa..53deb9bd42 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -253,6 +253,27 @@ public: (-p_pre + 3.0f * p_from - 3.0f * p_to + p_post) * (p_weight * p_weight * p_weight)); } + static _ALWAYS_INLINE_ double bezier_interpolate(double p_start, double p_control_1, double p_control_2, double p_end, double p_t) { + /* Formula from Wikipedia article on Bezier curves. */ + double omt = (1.0 - p_t); + double omt2 = omt * omt; + double omt3 = omt2 * omt; + double t2 = p_t * p_t; + double t3 = t2 * p_t; + + return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3; + } + static _ALWAYS_INLINE_ float bezier_interpolate(float p_start, float p_control_1, float p_control_2, float p_end, float p_t) { + /* Formula from Wikipedia article on Bezier curves. */ + float omt = (1.0f - p_t); + float omt2 = omt * omt; + float omt3 = omt2 * omt; + float t2 = p_t * p_t; + float t3 = t2 * p_t; + + return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0f + p_control_2 * omt * t2 * 3.0f + p_end * t3; + } + static _ALWAYS_INLINE_ double lerp_angle(double p_from, double p_to, double p_weight) { double difference = fmod(p_to - p_from, Math_TAU); double distance = fmod(2.0 * difference, Math_TAU) - difference; diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index a27227905c..d9b5d55454 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -152,13 +152,6 @@ Vector2 Vector2::limit_length(const real_t p_len) const { return v; } -Vector2 Vector2::cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const { - Vector2 res = *this; - res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight); - res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight); - return res; -} - Vector2 Vector2::move_toward(const Vector2 &p_to, const real_t p_delta) const { Vector2 v = *this; Vector2 vd = p_to - v; diff --git a/core/math/vector2.h b/core/math/vector2.h index bd67299f33..91d3d3a56b 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -113,7 +113,9 @@ struct _NO_DISCARD_ Vector2 { _FORCE_INLINE_ Vector2 lerp(const Vector2 &p_to, const real_t p_weight) const; _FORCE_INLINE_ Vector2 slerp(const Vector2 &p_to, const real_t p_weight) const; - Vector2 cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const; + _FORCE_INLINE_ Vector2 cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const; + _FORCE_INLINE_ Vector2 bezier_interpolate(const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) const; + Vector2 move_toward(const Vector2 &p_to, const real_t p_delta) const; Vector2 slide(const Vector2 &p_normal) const; @@ -261,6 +263,26 @@ Vector2 Vector2::slerp(const Vector2 &p_to, const real_t p_weight) const { return rotated(angle * p_weight) * (result_length / start_length); } +Vector2 Vector2::cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const { + Vector2 res = *this; + res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight); + res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight); + return res; +} + +Vector2 Vector2::bezier_interpolate(const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) const { + Vector2 res = *this; + + /* Formula from Wikipedia article on Bezier curves. */ + real_t omt = (1.0 - p_t); + real_t omt2 = omt * omt; + real_t omt3 = omt2 * omt; + real_t t2 = p_t * p_t; + real_t t3 = t2 * p_t; + + return res * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3; +} + Vector2 Vector2::direction_to(const Vector2 &p_to) const { Vector2 ret(p_to.x - x, p_to.y - y); ret.normalize(); diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp index f94f39b7f2..d71d365053 100644 --- a/core/math/vector3.cpp +++ b/core/math/vector3.cpp @@ -85,14 +85,6 @@ Vector3 Vector3::limit_length(const real_t p_len) const { return v; } -Vector3 Vector3::cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const { - Vector3 res = *this; - res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight); - res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight); - res.z = Math::cubic_interpolate(res.z, p_b.z, p_pre_a.z, p_post_b.z, p_weight); - return res; -} - Vector3 Vector3::move_toward(const Vector3 &p_to, const real_t p_delta) const { Vector3 v = *this; Vector3 vd = p_to - v; diff --git a/core/math/vector3.h b/core/math/vector3.h index 8891532f42..970416234d 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -104,7 +104,9 @@ struct _NO_DISCARD_ Vector3 { _FORCE_INLINE_ Vector3 lerp(const Vector3 &p_to, const real_t p_weight) const; _FORCE_INLINE_ Vector3 slerp(const Vector3 &p_to, const real_t p_weight) const; - Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const; + _FORCE_INLINE_ Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const; + _FORCE_INLINE_ Vector3 bezier_interpolate(const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) const; + Vector3 move_toward(const Vector3 &p_to, const real_t p_delta) const; Vector2 octahedron_encode() const; @@ -227,6 +229,27 @@ Vector3 Vector3::slerp(const Vector3 &p_to, const real_t p_weight) const { return rotated(cross(p_to).normalized(), angle * p_weight) * (result_length / start_length); } +Vector3 Vector3::cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const { + Vector3 res = *this; + res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight); + res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight); + res.z = Math::cubic_interpolate(res.z, p_b.z, p_pre_a.z, p_post_b.z, p_weight); + return res; +} + +Vector3 Vector3::bezier_interpolate(const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) const { + Vector3 res = *this; + + /* Formula from Wikipedia article on Bezier curves. */ + real_t omt = (1.0 - p_t); + real_t omt2 = omt * omt; + real_t omt3 = omt2 * omt; + real_t t2 = p_t * p_t; + real_t t3 = t2 * p_t; + + return res * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3; +} + real_t Vector3::distance_to(const Vector3 &p_to) const { return (p_to - *this).length(); } diff --git a/core/object/object.h b/core/object/object.h index 2a9f2ebf93..7dac96bc2b 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -91,6 +91,7 @@ enum PropertyHint { PROPERTY_HINT_INT_IS_POINTER, PROPERTY_HINT_LOCALE_ID, PROPERTY_HINT_LOCALIZABLE_STRING, + PROPERTY_HINT_NODE_TYPE, ///< a node object type PROPERTY_HINT_MAX, // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit }; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 6007268e21..cc22b9c451 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3327,13 +3327,20 @@ Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Varia void Variant::static_assign(const Variant &p_variant) { } -bool Variant::is_shared() const { - switch (type) { +bool Variant::is_type_shared(Variant::Type p_type) { + switch (p_type) { case OBJECT: - return true; case ARRAY: - return true; case DICTIONARY: + case PACKED_BYTE_ARRAY: + case PACKED_INT32_ARRAY: + case PACKED_INT64_ARRAY: + case PACKED_FLOAT32_ARRAY: + case PACKED_FLOAT64_ARRAY: + case PACKED_STRING_ARRAY: + case PACKED_VECTOR2_ARRAY: + case PACKED_VECTOR3_ARRAY: + case PACKED_COLOR_ARRAY: return true; default: { } @@ -3342,6 +3349,10 @@ bool Variant::is_shared() const { return false; } +bool Variant::is_shared() const { + return is_type_shared(type); +} + void Variant::_variant_call_error(const String &p_method, Callable::CallError &error) { switch (error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { diff --git a/core/variant/variant.h b/core/variant/variant.h index 992d9cad40..cb3a622417 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -297,6 +297,7 @@ public: static String get_type_name(Variant::Type p_type); static bool can_convert(Type p_type_from, Type p_type_to); static bool can_convert_strict(Type p_type_from, Type p_type_to); + static bool is_type_shared(Variant::Type p_type); bool is_ref_counted() const; _FORCE_INLINE_ bool is_num() const { diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 8e420ecf04..5b9f77ad16 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1556,6 +1556,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, lerp, sarray("to", "weight"), varray()); bind_method(Vector2, slerp, sarray("to", "weight"), varray()); bind_method(Vector2, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); + bind_method(Vector2, bezier_interpolate, sarray("control_1", "control_2", "end", "t"), varray()); bind_method(Vector2, max_axis_index, sarray(), varray()); bind_method(Vector2, min_axis_index, sarray(), varray()); bind_method(Vector2, move_toward, sarray("to", "delta"), varray()); @@ -1643,6 +1644,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, lerp, sarray("to", "weight"), varray()); bind_method(Vector3, slerp, sarray("to", "weight"), varray()); bind_method(Vector3, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); + bind_method(Vector3, bezier_interpolate, sarray("control_1", "control_2", "end", "t"), varray()); bind_method(Vector3, move_toward, sarray("to", "delta"), varray()); bind_method(Vector3, dot, sarray("with"), varray()); bind_method(Vector3, cross, sarray("with"), varray()); diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 2d87a404ee..d1b5e285d2 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -231,6 +231,10 @@ struct VariantUtilityFunctions { return Math::cubic_interpolate(from, to, pre, post, weight); } + static inline double bezier_interpolate(double p_start, double p_control_1, double p_control_2, double p_end, double p_t) { + return Math::bezier_interpolate(p_start, p_control_1, p_control_2, p_end, p_t); + } + static inline double lerp_angle(double from, double to, double weight) { return Math::lerp_angle(from, to, weight); } @@ -1250,6 +1254,7 @@ void Variant::_register_variant_utility_functions() { FUNCBINDR(lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(cubic_interpolate, sarray("from", "to", "pre", "post", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); + FUNCBINDR(bezier_interpolate, sarray("start", "control_1", "control_2", "end", "t"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(lerp_angle, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(inverse_lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(range_lerp, sarray("value", "istart", "istop", "ostart", "ostop"), Variant::UTILITY_FUNC_TYPE_MATH); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index e031f9caba..e06863be7a 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -106,6 +106,17 @@ [/codeblock] </description> </method> + <method name="bezier_interpolate"> + <return type="float" /> + <argument index="0" name="start" type="float" /> + <argument index="1" name="control_1" type="float" /> + <argument index="2" name="control_2" type="float" /> + <argument index="3" name="end" type="float" /> + <argument index="4" name="t" type="float" /> + <description> + Returns the point at the given [code]t[/code] on a one-dimnesional [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by the given [code]control_1[/code], [code]control_2[/code], and [code]end[/code] points. + </description> + </method> <method name="bytes2var"> <return type="Variant" /> <argument index="0" name="bytes" type="PackedByteArray" /> @@ -2604,7 +2615,9 @@ <constant name="PROPERTY_HINT_LOCALIZABLE_STRING" value="44" enum="PropertyHint"> Hints that a dictionary property is string translation map. Dictionary keys are locale codes and, values are translated strings. </constant> - <constant name="PROPERTY_HINT_MAX" value="45" enum="PropertyHint"> + <constant name="PROPERTY_HINT_NODE_TYPE" value="45" enum="PropertyHint"> + </constant> + <constant name="PROPERTY_HINT_MAX" value="46" enum="PropertyHint"> </constant> <constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags"> </constant> diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index c428233372..84f8523da3 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -38,7 +38,7 @@ Gets the edited object. </description> </method> - <method name="get_edited_property"> + <method name="get_edited_property" qualifiers="const"> <return type="StringName" /> <description> Gets the edited property. If your editor is for a single property (added via [method EditorInspectorPlugin._parse_property]), then this will return the property. diff --git a/doc/classes/MovieWriter.xml b/doc/classes/MovieWriter.xml index 9c713bd7ae..bc702adde6 100644 --- a/doc/classes/MovieWriter.xml +++ b/doc/classes/MovieWriter.xml @@ -6,8 +6,8 @@ <description> Godot can record videos with non-real-time simulation. Like the [code]--fixed-fps[/code] command line argument, this forces the reported [code]delta[/code] in [method Node._process] functions to be identical across frames, regardless of how long it actually took to render the frame. This can be used to record high-quality videos with perfect frame pacing regardless of your hardware's capabilities. Godot has 2 built-in [MovieWriter]s: - - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. AVI output is currently limited to a file of 4 GB in size at most. - - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported. + - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. MJPEG does not support transparency. AVI output is currently limited to a file of 4 GB in size at most. + - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported, even if the root viewport is set to be transparent. If you need to encode to a different format or pipe a stream through third-party software, you can extend the [MovieWriter] class to create your own movie writers. This should typically be done using GDExtension for performance reasons. [b]Editor usage:[/b] A default movie file path can be specified in [member ProjectSettings.editor/movie_writer/movie_file]. Alternatively, for running single scenes, a [code]movie_path[/code] metadata can be added to the root node, specifying the path to a movie file that will be used when recording that scene. Once a path is set, click the video reel icon in the top-right corner of the editor to enable Movie Maker mode, then run any scene as usual. The engine will start recording as soon as the splash screen is finished, and it will only stop recording when the engine quits. Click the video reel icon again to disable Movie Maker mode. Note that toggling Movie Maker mode does not affect project instances that are already running. [b]Note:[/b] MovieWriter is available for use in both the editor and exported projects, but it is [i]not[/i] designed for use by end users to record videos while playing. Players wishing to record gameplay videos should install tools such as [url=https://obsproject.com/]OBS Studio[/url] or [url=https://www.maartenbaert.be/simplescreenrecorder/]SimpleScreenRecorder[/url] instead. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index fc86b67c60..2391cb892c 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -558,22 +558,21 @@ [b]Note:[/b] [member editor/movie_writer/disable_vsync] has no effect if the operating system or graphics driver forces V-Sync with no way for applications to disable it. </member> <member name="editor/movie_writer/fps" type="int" setter="" getter="" default="60"> - The number of frames per second to record in the video when writing a movie. Simulation speed will adjust to always match the specified framerate, which means the engine will appear to run slower at higher [member editor/movie_writer/fps] values. Certain FPS values will require you to adjust [member editor/movie_writer/mix_rate_hz] to prevent audio from desynchronizing over time. + The number of frames per second to record in the video when writing a movie. Simulation speed will adjust to always match the specified framerate, which means the engine will appear to run slower at higher [member editor/movie_writer/fps] values. Certain FPS values will require you to adjust [member editor/movie_writer/mix_rate] to prevent audio from desynchronizing over time. This can be specified manually on the command line using the [code]--fixed-fps <fps>[/code] command line argument. </member> - <member name="editor/movie_writer/mix_rate_hz" type="int" setter="" getter="" default="48000"> + <member name="editor/movie_writer/mix_rate" type="int" setter="" getter="" default="48000"> The audio mix rate to use in the recorded audio when writing a movie (in Hz). This can be different from [member audio/driver/mix_rate], but this value must be divisible by [member editor/movie_writer/fps] to prevent audio from desynchronizing over time. </member> <member name="editor/movie_writer/mjpeg_quality" type="float" setter="" getter="" default="0.75"> - The JPEG quality to use when writing a video to an AVI file, between [code]0.01[/code] and [code]1.0[/code] (inclusive). Higher [code]quality[/code] values result in better-looking output at the cost of larger file sizes. Recommended [code]quality[/code] values are between [code]0.75[/code] and [code]0.90[/code]. Even at quality [code]1.00[/code], JPEG compression remains lossy. - [b]Note:[/b] JPEG does not saving an alpha channel. If the [Image] contains an alpha channel, the image will still be saved, but the resulting JPEG file won't contain the alpha channel. + The JPEG quality to use when writing a video to an AVI file, between [code]0.01[/code] and [code]1.0[/code] (inclusive). Higher [code]quality[/code] values result in better-looking output at the cost of larger file sizes. Recommended [code]quality[/code] values are between [code]0.75[/code] and [code]0.9[/code]. Even at quality [code]1.0[/code], JPEG compression remains lossy. [b]Note:[/b] This does not affect the audio quality or writing PNG image sequences. </member> <member name="editor/movie_writer/movie_file" type="String" setter="" getter="" default=""""> The output path for the movie. The file extension determines the [MovieWriter] that will be used. Godot has 2 built-in [MovieWriter]s: - - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. AVI output is currently limited to a file of 4 GB in size at most. - - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported. + - AVI container with MJPEG for video and uncompressed audio ([code].avi[/code] file extension). Lossy compression, medium file sizes, fast encoding. The lossy compression quality can be adjusted by changing [member ProjectSettings.editor/movie_writer/mjpeg_quality]. The resulting file can be viewed in most video players, but it must be converted to another format for viewing on the web or by Godot with [VideoStreamPlayer]. MJPEG does not support transparency. AVI output is currently limited to a file of 4 GB in size at most. + - PNG image sequence for video and WAV for audio ([code].png[/code] file extension). Lossless compression, large file sizes, slow encoding. Designed to be encoded to a video file with another tool such as [url=https://ffmpeg.org/]FFmpeg[/url] after recording. Transparency is currently not supported, even if the root viewport is set to be transparent. If you need to encode to a different format or pipe a stream through third-party software, you can extend this [MovieWriter] class to create your own movie writers. When using PNG output, the frame number will be appended at the end of the file name. It starts from 0 and is padded with 8 digits to ensure correct sorting and easier processing. For example, if the output path is [code]/tmp/hello.png[/code], the first two frames will be [code]/tmp/hello00000000.png[/code] and [code]/tmp/hello00000001.png[/code]. The audio will be saved at [code]/tmp/hello.wav[/code]. </member> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 6ccc0fc6a6..454db51919 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -85,6 +85,16 @@ Returns the aspect ratio of this vector, the ratio of [member x] to [member y]. </description> </method> + <method name="bezier_interpolate" qualifiers="const"> + <return type="Vector2" /> + <argument index="0" name="control_1" type="Vector2" /> + <argument index="1" name="control_2" type="Vector2" /> + <argument index="2" name="end" type="Vector2" /> + <argument index="3" name="t" type="float" /> + <description> + Returns the point at the given [code]t[/code] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [code]control_1[/code], [code]control_2[/code], and [code]end[/code] points. + </description> + </method> <method name="bounce" qualifiers="const"> <return type="Vector2" /> <argument index="0" name="n" type="Vector2" /> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index d907ceb52b..c181720a66 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -61,6 +61,16 @@ Returns the unsigned minimum angle to the given vector, in radians. </description> </method> + <method name="bezier_interpolate" qualifiers="const"> + <return type="Vector3" /> + <argument index="0" name="control_1" type="Vector3" /> + <argument index="1" name="control_2" type="Vector3" /> + <argument index="2" name="end" type="Vector3" /> + <argument index="3" name="t" type="float" /> + <description> + Returns the point at the given [code]t[/code] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [code]control_1[/code], [code]control_2[/code], and [code]end[/code] points. + </description> + </method> <method name="bounce" qualifiers="const"> <return type="Vector3" /> <argument index="0" name="n" type="Vector3" /> diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index f0650ee446..391cd009f1 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -44,17 +44,6 @@ float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) { return h; } -static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; -} - void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float scale = timeline->get_zoom_scale(); @@ -151,7 +140,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { for (int k = 0; k < iterations; k++) { float middle = (low + high) / 2; - Vector2 interp = _bezier_interp(middle, start, out_handle, in_handle, end); + Vector2 interp = start.bezier_interpolate(out_handle, in_handle, end, middle); if (interp.x < t) { low = middle; @@ -161,8 +150,8 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { } //interpolate the result: - Vector2 low_pos = _bezier_interp(low, start, out_handle, in_handle, end); - Vector2 high_pos = _bezier_interp(high, start, out_handle, in_handle, end); + Vector2 low_pos = start.bezier_interpolate(out_handle, in_handle, end, low); + Vector2 high_pos = start.bezier_interpolate(out_handle, in_handle, end, high); float c = (t - low_pos.x) / (high_pos.x - low_pos.x); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 3469e96a0a..31c169a0fb 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -474,6 +474,13 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) { get_ok_button()->set_disabled(false); } +void CreateDialog::select_base() { + if (search_options_types.is_empty()) { + _update_search(); + } + select_type(base_type, false); +} + String CreateDialog::get_selected_type() { TreeItem *selected = search_options->get_selected(); if (!selected) { diff --git a/editor/create_dialog.h b/editor/create_dialog.h index 3ab27ea58c..dc8618a1c0 100644 --- a/editor/create_dialog.h +++ b/editor/create_dialog.h @@ -115,6 +115,7 @@ public: void set_base_type(const String &p_base) { base_type = p_base; } String get_base_type() const { return base_type; } + void select_base(); void set_preferred_search_result_type(const String &p_preferred_type) { preferred_search_result_type = p_preferred_type; } String get_preferred_search_result_type() { return preferred_search_result_type; } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 0f31e3e7bb..2bf0cd2f20 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -406,7 +406,7 @@ Object *EditorProperty::get_edited_object() { return object; } -StringName EditorProperty::get_edited_property() { +StringName EditorProperty::get_edited_property() const { return property; } @@ -437,16 +437,20 @@ Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid); } -bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property) { +bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value) { bool is_valid_revert = false; Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_object, p_property, &is_valid_revert); if (!is_valid_revert) { return false; } - Variant current_value = p_object->get(p_property); + Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property); return PropertyUtils::is_property_value_different(current_value, revert_value); } +StringName EditorProperty::_get_revert_property() const { + return property; +} + void EditorProperty::update_revert_and_pin_status() { if (property == StringName()) { return; //no property, so nothing to do @@ -458,7 +462,8 @@ void EditorProperty::update_revert_and_pin_status() { CRASH_COND(!node); new_pinned = node->is_property_pinned(property); } - bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property) && !is_read_only(); + Variant current = object->get(_get_revert_property()); + bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, ¤t) && !is_read_only(); if (new_can_revert != can_revert || new_pinned != pinned) { can_revert = new_can_revert; @@ -717,11 +722,15 @@ void EditorProperty::set_bottom_editor(Control *p_control) { bottom_editor = p_control; } +Variant EditorProperty::_get_cache_value(const StringName &p_prop, bool &r_valid) const { + return object->get(p_prop, &r_valid); +} + bool EditorProperty::is_cache_valid() const { if (object) { for (const KeyValue<StringName, Variant> &E : cache) { bool valid; - Variant value = object->get(E.key, &valid); + Variant value = _get_cache_value(E.key, valid); if (!valid || value != E.value) { return false; } @@ -733,7 +742,7 @@ void EditorProperty::update_cache() { cache.clear(); if (object && property != StringName()) { bool valid; - Variant value = object->get(property, &valid); + Variant value = _get_cache_value(property, valid); if (valid) { cache[property] = value; } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 555fedf939..d70d06c48b 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -50,7 +50,7 @@ public: static bool is_property_value_different(const Variant &p_a, const Variant &p_b); static Variant get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid); - static bool can_property_revert(Object *p_object, const StringName &p_property); + static bool can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value = nullptr); }; class EditorProperty : public Container { @@ -131,6 +131,9 @@ protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; const Color *_get_property_colors(); + virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const; + virtual StringName _get_revert_property() const; + public: void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false); @@ -143,7 +146,7 @@ public: bool is_read_only() const; Object *get_edited_object(); - StringName get_edited_property(); + StringName get_edited_property() const; virtual void update_property(); void update_revert_and_pin_status(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 9b0ac305d1..b4b82b1edf 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1620,34 +1620,6 @@ bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_nod return false; } -static bool _find_edited_resources(const Ref<Resource> &p_resource, HashSet<Ref<Resource>> &edited_resources) { - if (p_resource->is_edited()) { - edited_resources.insert(p_resource); - return true; - } - - List<PropertyInfo> plist; - - p_resource->get_property_list(&plist); - - for (const PropertyInfo &E : plist) { - if (E.type == Variant::OBJECT && E.usage & PROPERTY_USAGE_STORAGE && !(E.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)) { - Ref<Resource> res = p_resource->get(E.name); - if (res.is_null()) { - continue; - } - if (res->get_path().is_resource_file()) { // Not a subresource, continue. - continue; - } - if (_find_edited_resources(res, edited_resources)) { - return true; - } - } - } - - return false; -} - int EditorNode::_save_external_resources() { // Save external resources and its subresources if any was modified. @@ -1657,29 +1629,43 @@ int EditorNode::_save_external_resources() { } flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; - HashSet<Ref<Resource>> edited_subresources; + HashSet<String> edited_resources; int saved = 0; List<Ref<Resource>> cached; ResourceCache::get_cached_resources(&cached); - for (const Ref<Resource> &res : cached) { - if (!res->get_path().is_resource_file()) { + + for (Ref<Resource> res : cached) { + if (!res->is_edited()) { continue; } - // not only check if this resource is edited, check contained subresources too - if (_find_edited_resources(res, edited_subresources)) { - ResourceSaver::save(res->get_path(), res, flg); - saved++; - } - } - // Clear later, because user may have put the same subresource in two different resources, - // which will be shared until the next reload. + String path = res->get_path(); + if (path.begins_with("res://")) { + int subres_pos = path.find("::"); + if (subres_pos == -1) { + // Actual resource. + edited_resources.insert(path); + } else { + edited_resources.insert(path.substr(0, subres_pos)); + } + } - for (const Ref<Resource> &E : edited_subresources) { - Ref<Resource> res = E; res->set_edited(false); } + for (const String &E : edited_resources) { + Ref<Resource> res = ResourceCache::get_ref(E); + if (!res.is_valid()) { + continue; // Maybe it was erased in a thread, who knows. + } + Ref<PackedScene> ps = res; + if (ps.is_valid()) { + continue; // Do not save PackedScenes, this will mess up the editor. + } + ResourceSaver::save(res->get_path(), res, flg); + saved++; + } + return saved; } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 2562c740aa..0e6c9162ce 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -43,6 +43,7 @@ #include "scene/main/window.h" #include "scene/resources/font.h" #include "scene/resources/mesh.h" +#include "scene/resources/packed_scene.h" ///////////////////// Nil ///////////////////////// @@ -3017,6 +3018,23 @@ void EditorPropertyNodePath::_set_read_only(bool p_read_only) { clear->set_disabled(p_read_only); }; +String EditorPropertyNodePath::_get_meta_pointer_property() const { + ERR_FAIL_COND_V(!pointer_mode, String()); + return SceneState::get_meta_pointer_property(get_edited_property()); +} + +Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const { + if (p_prop == get_edited_property()) { + r_valid = true; + return const_cast<EditorPropertyNodePath *>(this)->get_edited_object()->get(_get_meta_pointer_property(), &r_valid); + } + return Variant(); +} + +StringName EditorPropertyNodePath::_get_revert_property() const { + return _get_meta_pointer_property(); +} + void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { NodePath path = p_path; Node *base_node = nullptr; @@ -3048,7 +3066,11 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { if (base_node) { // for AnimationTrackKeyEdit path = base_node->get_path().rel_path_to(p_path); } - emit_changed(get_edited_property(), path); + if (pointer_mode && base_node) { + emit_changed(_get_meta_pointer_property(), path); + } else { + emit_changed(get_edited_property(), path); + } update_property(); } @@ -3064,7 +3086,11 @@ void EditorPropertyNodePath::_node_assign() { } void EditorPropertyNodePath::_node_clear() { - emit_changed(get_edited_property(), NodePath()); + if (pointer_mode) { + emit_changed(_get_meta_pointer_property(), NodePath()); + } else { + emit_changed(get_edited_property(), NodePath()); + } update_property(); } @@ -3092,7 +3118,12 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const } void EditorPropertyNodePath::update_property() { - NodePath p = get_edited_object()->get(get_edited_property()); + NodePath p; + if (pointer_mode) { + p = get_edited_object()->get(_get_meta_pointer_property()); + } else { + p = get_edited_object()->get(get_edited_property()); + } assign->set_tooltip(p); if (p == NodePath()) { @@ -3131,7 +3162,8 @@ void EditorPropertyNodePath::update_property() { assign->set_icon(EditorNode::get_singleton()->get_object_icon(target_node, "Node")); } -void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root) { +void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root, bool p_pointer_mode) { + pointer_mode = p_pointer_mode; base_hint = p_base_hint; valid_types = p_valid_types; use_path_from_scene_root = p_use_path_from_scene_root; @@ -3927,23 +3959,31 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ return editor; } break; case Variant::OBJECT: { - EditorPropertyResource *editor = memnew(EditorPropertyResource); - editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); - - if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) { - String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector"); - for (int i = 0; i < open_in_new.get_slice_count(","); i++) { - String type = open_in_new.get_slicec(',', i).strip_edges(); - for (int j = 0; j < p_hint_text.get_slice_count(","); j++) { - String inherits = p_hint_text.get_slicec(',', j); - if (ClassDB::is_parent_class(inherits, type)) { - editor->set_use_sub_inspector(false); + if (p_hint == PROPERTY_HINT_NODE_TYPE) { + EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath); + Vector<String> types = p_hint_text.split(",", false); + Vector<StringName> sn = Variant(types); //convert via variant + editor->setup(NodePath(), sn, false, true); + return editor; + } else { + EditorPropertyResource *editor = memnew(EditorPropertyResource); + editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); + + if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) { + String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector"); + for (int i = 0; i < open_in_new.get_slice_count(","); i++) { + String type = open_in_new.get_slicec(',', i).strip_edges(); + for (int j = 0; j < p_hint_text.get_slice_count(","); j++) { + String inherits = p_hint_text.get_slicec(',', j); + if (ClassDB::is_parent_class(inherits, type)) { + editor->set_use_sub_inspector(false); + } } } } - } - return editor; + return editor; + } } break; case Variant::DICTIONARY: { diff --git a/editor/editor_properties.h b/editor/editor_properties.h index a3b98b7724..72b2b0b283 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -704,6 +704,7 @@ class EditorPropertyNodePath : public EditorProperty { SceneTreeDialog *scene_tree = nullptr; NodePath base_hint; bool use_path_from_scene_root = false; + bool pointer_mode = false; Vector<StringName> valid_types; void _node_selected(const NodePath &p_path); @@ -714,6 +715,10 @@ class EditorPropertyNodePath : public EditorProperty { void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); bool is_drop_valid(const Dictionary &p_drag_data) const; + String _get_meta_pointer_property() const; + virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const override; + virtual StringName _get_revert_property() const override; + protected: virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); @@ -721,7 +726,7 @@ protected: public: virtual void update_property() override; - void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root = true); + void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types, bool p_use_path_from_scene_root = true, bool p_pointer_mode = false); EditorPropertyNodePath(); }; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 3dd0044ab9..2d6ec0c63a 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -43,6 +43,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/import_dock.h" +#include "editor/scene_create_dialog.h" #include "editor/scene_tree_dock.h" #include "editor/shader_create_dialog.h" #include "scene/gui/label.h" @@ -1469,44 +1470,12 @@ void FileSystemDock::_make_dir_confirm() { } void FileSystemDock::_make_scene_confirm() { - String scene_name = make_scene_dialog_text->get_text().strip_edges(); - - if (scene_name.length() == 0) { - EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; - } - - String directory = path; - if (!directory.ends_with("/")) { - directory = directory.get_base_dir(); - } - - String extension = scene_name.get_extension(); - List<String> extensions; - Ref<PackedScene> sd = memnew(PackedScene); - ResourceSaver::get_recognized_extensions(sd, &extensions); - - bool extension_correct = false; - for (const String &E : extensions) { - if (E == extension) { - extension_correct = true; - break; - } - } - if (!extension_correct) { - scene_name = scene_name.get_basename() + ".tscn"; - } - - scene_name = directory.plus_file(scene_name); - - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - if (da->file_exists(scene_name)) { - EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); - return; - } + const String scene_path = make_scene_dialog->get_scene_path(); int idx = EditorNode::get_singleton()->new_scene(); - EditorNode::get_singleton()->get_editor_data().set_scene_path(idx, scene_name); + EditorNode::get_singleton()->get_editor_data().set_scene_path(idx, scene_path); + EditorNode::get_singleton()->set_edited_scene(make_scene_dialog->create_scene_root()); + EditorNode::get_singleton()->save_scene_list({ scene_path }); } void FileSystemDock::_file_removed(String p_file) { @@ -2003,10 +1972,12 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCENE: { - make_scene_dialog_text->set_text("new scene"); - make_scene_dialog_text->select_all(); - make_scene_dialog->popup_centered(Size2(250, 80) * EDSCALE); - make_scene_dialog_text->grab_focus(); + String directory = path; + if (!directory.ends_with("/")) { + directory = directory.get_base_dir(); + } + make_scene_dialog->config(directory); + make_scene_dialog->popup_centered(); } break; case FILE_NEW_SCRIPT: { @@ -3216,15 +3187,8 @@ FileSystemDock::FileSystemDock() { make_dir_dialog->register_text_enter(make_dir_dialog_text); make_dir_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_make_dir_confirm)); - make_scene_dialog = memnew(ConfirmationDialog); - make_scene_dialog->set_title(TTR("Create Scene")); - VBoxContainer *make_scene_dialog_vb = memnew(VBoxContainer); - make_scene_dialog->add_child(make_scene_dialog_vb); - - make_scene_dialog_text = memnew(LineEdit); - make_scene_dialog_vb->add_margin_child(TTR("Name:"), make_scene_dialog_text); + make_scene_dialog = memnew(SceneCreateDialog); add_child(make_scene_dialog); - make_scene_dialog->register_text_enter(make_scene_dialog_text); make_scene_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_make_scene_confirm)); make_script_dialog = memnew(ScriptCreateDialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index f20c0b2f76..f73e076ac0 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -47,6 +47,7 @@ #include "scene/gui/split_container.h" #include "scene/gui/tree.h" +class SceneCreateDialog; class ShaderCreateDialog; class FileSystemDock : public VBoxContainer { @@ -148,9 +149,8 @@ private: LineEdit *duplicate_dialog_text = nullptr; ConfirmationDialog *make_dir_dialog = nullptr; LineEdit *make_dir_dialog_text = nullptr; - ConfirmationDialog *make_scene_dialog = nullptr; - LineEdit *make_scene_dialog_text = nullptr; ConfirmationDialog *overwrite_dialog = nullptr; + SceneCreateDialog *make_scene_dialog = nullptr; ScriptCreateDialog *make_script_dialog = nullptr; ShaderCreateDialog *make_shader_dialog = nullptr; CreateDialog *new_resource_dialog = nullptr; diff --git a/editor/scene_create_dialog.cpp b/editor/scene_create_dialog.cpp new file mode 100644 index 0000000000..64aea54c5f --- /dev/null +++ b/editor/scene_create_dialog.cpp @@ -0,0 +1,312 @@ +/*************************************************************************/ +/* scene_create_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_create_dialog.h" + +#include "core/io/dir_access.h" +#include "editor/create_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "scene/2d/node_2d.h" +#include "scene/3d/node_3d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/packed_scene.h" + +void SceneCreateDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + select_node_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); + node_type_2d->set_icon(get_theme_icon(SNAME("Node2D"), SNAME("EditorIcons"))); + node_type_3d->set_icon(get_theme_icon(SNAME("Node3D"), SNAME("EditorIcons"))); + node_type_gui->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons"))); + node_type_other->add_theme_icon_override(SNAME("icon"), get_theme_icon(SNAME("Node"), SNAME("EditorIcons"))); + status_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } break; + } +} + +void SceneCreateDialog::config(const String &p_dir) { + directory = p_dir; + root_name_edit->set_text(""); + scene_name_edit->set_text(""); + scene_name_edit->call_deferred(SNAME("grab_focus")); + update_dialog(); +} + +void SceneCreateDialog::accept_create() { + if (!get_ok_button()->is_disabled()) { + hide(); + emit_signal(SNAME("confirmed")); + } +} + +void SceneCreateDialog::browse_types() { + select_node_dialog->popup_create(true); + select_node_dialog->set_title(TTR("Pick Root Node Type")); + select_node_dialog->get_ok_button()->set_text(TTR("Pick")); +} + +void SceneCreateDialog::on_type_picked() { + other_type_display->set_text(select_node_dialog->get_selected_type().get_slice(" ", 0)); + if (node_type_other->is_pressed()) { + update_dialog(); + } else { + node_type_other->set_pressed(true); // Calls update_dialog() via group. + } +} + +void SceneCreateDialog::update_dialog() { + scene_name = scene_name_edit->get_text().strip_edges(); + update_error(file_error_label, MSG_OK, TTR("Scene name is valid.")); + + bool is_valid = true; + if (scene_name.is_empty()) { + update_error(file_error_label, MSG_ERROR, TTR("Scene name is empty.")); + is_valid = false; + } + + if (is_valid) { + if (!scene_name.ends_with(".")) { + scene_name += "."; + } + scene_name += scene_extension_picker->get_selected_metadata().operator String(); + } + + if (is_valid && !scene_name.is_valid_filename()) { + update_error(file_error_label, MSG_ERROR, TTR("File name invalid.")); + is_valid = false; + } + + if (is_valid) { + scene_name = directory.plus_file(scene_name); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists(scene_name)) { + update_error(file_error_label, MSG_ERROR, TTR("File already exists.")); + is_valid = false; + } + } + + const StringName root_type_name = StringName(other_type_display->get_text()); + if (has_theme_icon(root_type_name, SNAME("EditorIcons"))) { + node_type_other->set_icon(get_theme_icon(root_type_name, SNAME("EditorIcons"))); + } else { + node_type_other->set_icon(nullptr); + } + + update_error(node_error_label, MSG_OK, "Root node valid."); + + root_name = root_name_edit->get_text().strip_edges(); + if (root_name.is_empty()) { + root_name = scene_name.get_file().get_basename(); + } + + if (!root_name.is_valid_identifier()) { + update_error(node_error_label, MSG_ERROR, TTR("Invalid root node name.")); + is_valid = false; + } + + get_ok_button()->set_disabled(!is_valid); +} + +void SceneCreateDialog::update_error(Label *p_label, MsgType p_type, const String &p_msg) { + p_label->set_text(String::utf8("• ") + p_msg); + switch (p_type) { + case MSG_OK: + p_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); + break; + case MSG_ERROR: + p_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + break; + } +} + +String SceneCreateDialog::get_scene_path() const { + return scene_name; +} + +Node *SceneCreateDialog::create_scene_root() { + ERR_FAIL_NULL_V(node_type_group->get_pressed_button(), nullptr); + RootType type = (RootType)node_type_group->get_pressed_button()->get_meta(type_meta).operator int(); + + Node *root = nullptr; + switch (type) { + case ROOT_2D_SCENE: + root = memnew(Node2D); + break; + case ROOT_3D_SCENE: + root = memnew(Node3D); + break; + case ROOT_USER_INTERFACE: { + Control *gui = memnew(Control); + gui->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + root = gui; + } break; + case ROOT_OTHER: + root = Object::cast_to<Node>(select_node_dialog->instance_selected()); + break; + } + + ERR_FAIL_NULL_V(root, nullptr); + root->set_name(root_name); + return root; +} + +SceneCreateDialog::SceneCreateDialog() { + select_node_dialog = memnew(CreateDialog); + add_child(select_node_dialog); + select_node_dialog->set_base_type("Node"); + select_node_dialog->select_base(); + select_node_dialog->connect("create", callable_mp(this, &SceneCreateDialog::on_type_picked)); + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + + GridContainer *gc = memnew(GridContainer); + main_vb->add_child(gc); + gc->set_columns(2); + + { + Label *label = memnew(Label(TTR("Root Type:"))); + gc->add_child(label); + label->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + + VBoxContainer *vb = memnew(VBoxContainer); + gc->add_child(vb); + + node_type_group.instantiate(); + + node_type_2d = memnew(CheckBox); + vb->add_child(node_type_2d); + node_type_2d->set_text(TTR("2D Scene")); + node_type_2d->set_button_group(node_type_group); + node_type_2d->set_meta(type_meta, ROOT_2D_SCENE); + node_type_2d->set_pressed(true); + + node_type_3d = memnew(CheckBox); + vb->add_child(node_type_3d); + node_type_3d->set_text(TTR("3D Scene")); + node_type_3d->set_button_group(node_type_group); + node_type_3d->set_meta(type_meta, ROOT_3D_SCENE); + + node_type_gui = memnew(CheckBox); + vb->add_child(node_type_gui); + node_type_gui->set_text(TTR("User Interface")); + node_type_gui->set_button_group(node_type_group); + node_type_gui->set_meta(type_meta, ROOT_USER_INTERFACE); + + HBoxContainer *hb = memnew(HBoxContainer); + vb->add_child(hb); + + node_type_other = memnew(CheckBox); + hb->add_child(node_type_other); + node_type_other->set_button_group(node_type_group); + node_type_other->set_meta(type_meta, ROOT_OTHER); + + Control *spacing = memnew(Control); + hb->add_child(spacing); + spacing->set_custom_minimum_size(Size2(4 * EDSCALE, 0)); + + other_type_display = memnew(LineEdit); + hb->add_child(other_type_display); + other_type_display->set_h_size_flags(Control::SIZE_EXPAND_FILL); + other_type_display->set_editable(false); + other_type_display->set_text("Node"); + + select_node_button = memnew(Button); + hb->add_child(select_node_button); + select_node_button->connect("pressed", callable_mp(this, &SceneCreateDialog::browse_types)); + + node_type_group->connect("pressed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); + } + + { + Label *label = memnew(Label(TTR("Scene Name:"))); + gc->add_child(label); + + HBoxContainer *hb = memnew(HBoxContainer); + gc->add_child(hb); + + scene_name_edit = memnew(LineEdit); + hb->add_child(scene_name_edit); + scene_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scene_name_edit->connect("text_changed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); + scene_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1)); + + List<String> extensions; + Ref<PackedScene> sd = memnew(PackedScene); + ResourceSaver::get_recognized_extensions(sd, &extensions); + + scene_extension_picker = memnew(OptionButton); + hb->add_child(scene_extension_picker); + for (const String &E : extensions) { + scene_extension_picker->add_item("." + E); + scene_extension_picker->set_item_metadata(-1, E); + } + } + + { + Label *label = memnew(Label(TTR("Root Name:"))); + gc->add_child(label); + + root_name_edit = memnew(LineEdit); + gc->add_child(root_name_edit); + root_name_edit->set_placeholder(TTR("Leave empty to use scene name")); + root_name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + root_name_edit->connect("text_changed", callable_mp(this, &SceneCreateDialog::update_dialog).unbind(1)); + root_name_edit->connect("text_submitted", callable_mp(this, &SceneCreateDialog::accept_create).unbind(1)); + } + + Control *spacing = memnew(Control); + main_vb->add_child(spacing); + spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); + + status_panel = memnew(PanelContainer); + main_vb->add_child(status_panel); + status_panel->set_h_size_flags(Control::SIZE_FILL); + status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + VBoxContainer *status_vb = memnew(VBoxContainer); + status_panel->add_child(status_vb); + + file_error_label = memnew(Label); + status_vb->add_child(file_error_label); + + node_error_label = memnew(Label); + status_vb->add_child(node_error_label); + + set_title(TTR("Create New Scene")); + set_min_size(Size2i(400 * EDSCALE, 0)); +} diff --git a/editor/scene_create_dialog.h b/editor/scene_create_dialog.h new file mode 100644 index 0000000000..5ac9d89cd7 --- /dev/null +++ b/editor/scene_create_dialog.h @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* scene_create_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_CREATE_DIALOG_H +#define SCENE_CREATE_DIALOG_H + +#include "scene/gui/dialogs.h" + +class ButtonGroup; +class CheckBox; +class CreateDialog; +class EditorFileDialog; +class Label; +class LineEdit; +class OptionButton; +class PanelContainer; + +class SceneCreateDialog : public ConfirmationDialog { + GDCLASS(SceneCreateDialog, ConfirmationDialog); + + enum MsgType { + MSG_OK, + MSG_ERROR, + }; + + const StringName type_meta = StringName("type"); + +public: + enum RootType { + ROOT_2D_SCENE, + ROOT_3D_SCENE, + ROOT_USER_INTERFACE, + ROOT_OTHER, + }; + +private: + String directory; + String scene_name; + String root_name; + + Ref<ButtonGroup> node_type_group; + CheckBox *node_type_2d = nullptr; + CheckBox *node_type_3d = nullptr; + CheckBox *node_type_gui = nullptr; + CheckBox *node_type_other = nullptr; + + LineEdit *other_type_display = nullptr; + Button *select_node_button = nullptr; + CreateDialog *select_node_dialog = nullptr; + + LineEdit *scene_name_edit = nullptr; + OptionButton *scene_extension_picker = nullptr; + LineEdit *root_name_edit = nullptr; + + PanelContainer *status_panel = nullptr; + Label *file_error_label = nullptr; + Label *node_error_label = nullptr; + + void accept_create(); + void browse_types(); + void on_type_picked(); + void update_dialog(); + void update_error(Label *p_label, MsgType p_type, const String &p_msg); + +protected: + void _notification(int p_what); + +public: + void config(const String &p_dir); + + String get_scene_path() const; + Node *create_scene_root(); + + SceneCreateDialog(); +}; + +#endif // SCENE_CREATE_DIALOG_H diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 3d5a39bf38..6a1effd680 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -1336,6 +1336,18 @@ void GDScriptByteCodeGenerator::write_endif() { if_jmp_addrs.pop_back(); } +void GDScriptByteCodeGenerator::write_jump_if_shared(const Address &p_value) { + append(GDScriptFunction::OPCODE_JUMP_IF_SHARED, 1); + append(p_value); + if_jmp_addrs.push_back(opcodes.size()); + append(0); // Jump destination, will be patched. +} + +void GDScriptByteCodeGenerator::write_end_jump_if_shared() { + patch_jump(if_jmp_addrs.back()->get()); + if_jmp_addrs.pop_back(); +} + void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) { Address counter(Address::LOCAL_VARIABLE, add_local("@counter_pos", p_iterator_type), p_iterator_type); Address container(Address::LOCAL_VARIABLE, add_local("@container_pos", p_list_type), p_list_type); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 6ee8fda533..f4b402fc96 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -479,6 +479,8 @@ public: virtual void write_if(const Address &p_condition) override; virtual void write_else() override; virtual void write_endif() override; + virtual void write_jump_if_shared(const Address &p_value) override; + virtual void write_end_jump_if_shared() override; virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override; virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override; virtual void write_for() override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 326b66a295..81fa265aca 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -140,6 +140,8 @@ public: virtual void write_if(const Address &p_condition) = 0; virtual void write_else() = 0; virtual void write_endif() = 0; + virtual void write_jump_if_shared(const Address &p_value) = 0; + virtual void write_end_jump_if_shared() = 0; virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0; virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0; virtual void write_for() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 16a0b17d61..1daf8ff7a9 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1056,13 +1056,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Set back the values into their bases. for (const ChainInfo &info : set_chain) { - if (!info.is_named) { - gen->write_set(info.base, info.key, assigned); - if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + bool known_type = assigned.type.has_type; + bool is_shared = Variant::is_type_shared(assigned.type.builtin_type); + + if (!known_type) { + // Jump shared values since they are already updated in-place. + gen->write_jump_if_shared(assigned); + } + if (known_type && !is_shared) { + if (!info.is_named) { + gen->write_set(info.base, info.key, assigned); + if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + gen->write_set_named(info.base, info.name, assigned); } - } else { - gen->write_set_named(info.base, info.name, assigned); + } + if (!known_type) { + gen->write_end_jump_if_shared(); } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { gen->pop_temporary(); @@ -1070,19 +1082,35 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code assigned = info.base; } - // If this is a class member property, also assign to it. - // This allow things like: position.x += 2.0 - if (assign_class_member_property != StringName()) { - gen->write_set_member(assigned, assign_class_member_property); - } - // Same as above but for members - if (is_member_property) { - if (member_property_has_setter && !member_property_is_in_setter) { - Vector<GDScriptCodeGenerator::Address> args; - args.push_back(assigned); - gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args); - } else { - gen->write_assign(target_member_property, assigned); + bool known_type = assigned.type.has_type; + bool is_shared = Variant::is_type_shared(assigned.type.builtin_type); + + if (!known_type || !is_shared) { + // If this is a class member property, also assign to it. + // This allow things like: position.x += 2.0 + if (assign_class_member_property != StringName()) { + if (!known_type) { + gen->write_jump_if_shared(assigned); + } + gen->write_set_member(assigned, assign_class_member_property); + if (!known_type) { + gen->write_end_jump_if_shared(); + } + } else if (is_member_property) { + // Same as above but for script members. + if (!known_type) { + gen->write_jump_if_shared(assigned); + } + if (member_property_has_setter && !member_property_is_in_setter) { + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(assigned); + gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args); + } else { + gen->write_assign(target_member_property, assigned); + } + if (!known_type) { + gen->write_end_jump_if_shared(); + } } } diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index dc114f2eff..726f0efe2b 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -838,6 +838,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 1; } break; + case OPCODE_JUMP_IF_SHARED: { + text += "jump-if-shared "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; case OPCODE_RETURN: { text += "return "; text += DADDR(1); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index d2ca795977..3f1265679b 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -304,6 +304,7 @@ public: OPCODE_JUMP_IF, OPCODE_JUMP_IF_NOT, OPCODE_JUMP_TO_DEF_ARGUMENT, + OPCODE_JUMP_IF_SHARED, OPCODE_RETURN, OPCODE_RETURN_TYPED_BUILTIN, OPCODE_RETURN_TYPED_ARRAY, diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 20b8d29ec3..988a98a591 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -311,6 +311,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_JUMP_IF, \ &&OPCODE_JUMP_IF_NOT, \ &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ + &&OPCODE_JUMP_IF_SHARED, \ &&OPCODE_RETURN, \ &&OPCODE_RETURN_TYPED_BUILTIN, \ &&OPCODE_RETURN_TYPED_ARRAY, \ @@ -2361,6 +2362,21 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_JUMP_IF_SHARED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(val, 0); + + if (val->is_shared()) { + int to = _code_ptr[ip + 2]; + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } else { + ip += 3; + } + } + DISPATCH_OPCODE; + OPCODE(OPCODE_RETURN) { CHECK_SPACE(2); GET_INSTRUCTION_ARG(r, 0); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 60858b00b1..4c00250162 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -85,12 +85,20 @@ void Node3D::_notify_dirty() { } void Node3D::_update_local_transform() const { - if (this->get_rotation_edit_mode() != ROTATION_EDIT_MODE_BASIS) { - data.local_transform = data.local_transform.orthogonalized(); - } - data.local_transform.basis.set_euler_scale(data.rotation, data.scale); + // This function is called when the local transform (data.local_transform) is dirty and the right value is contained in the Euler rotation and scale. + + data.local_transform.basis.set_euler_scale(data.euler_rotation, data.scale, data.euler_rotation_order); - data.dirty &= ~DIRTY_LOCAL; + data.dirty &= ~DIRTY_LOCAL_TRANSFORM; +} + +void Node3D::_update_rotation_and_scale() const { + // This function is called when the Euler rotation (data.euler_rotation) is dirty and the right value is contained in the local transform + + data.scale = data.local_transform.basis.get_scale(); + data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); + + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; } void Node3D::_propagate_transform_changed(Node3D *p_origin) { @@ -113,7 +121,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) { #endif get_tree()->xform_change_list.add(&xform_change); } - data.dirty |= DIRTY_GLOBAL; + data.dirty |= DIRTY_GLOBAL_TRANSFORM; data.children_lock--; } @@ -137,12 +145,12 @@ void Node3D::_notification(int p_what) { if (data.top_level && !Engine::get_singleton()->is_editor_hint()) { if (data.parent) { data.local_transform = data.parent->get_global_transform() * get_transform(); - data.dirty = DIRTY_VECTORS; //global is always dirty upon entering a scene + data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // As local transform was updated, rot/scale should be dirty. } data.top_level_active = true; } - data.dirty |= DIRTY_GLOBAL; //global is always dirty upon entering a scene + data.dirty |= DIRTY_GLOBAL_TRANSFORM; // Global is always dirty upon entering a scene. _notify_dirty(); notification(NOTIFICATION_ENTER_WORLD); @@ -212,12 +220,27 @@ void Node3D::set_basis(const Basis &p_basis) { set_transform(Transform3D(p_basis, data.local_transform.origin)); } void Node3D::set_quaternion(const Quaternion &p_quaternion) { - set_transform(Transform3D(Basis(p_quaternion), data.local_transform.origin)); + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + // We need the scale part, so if these are dirty, update it + data.scale = data.local_transform.basis.get_scale(); + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; + } + data.local_transform.basis = Basis(p_quaternion, data.scale); + // Rotscale should not be marked dirty because that would cause precision loss issues with the scale. Instead reconstruct rotation now. + data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); + + data.dirty = DIRTY_NONE; + + _propagate_transform_changed(this); + if (data.notify_local_transform) { + notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + } } void Node3D::set_transform(const Transform3D &p_transform) { data.local_transform = p_transform; - data.dirty |= DIRTY_VECTORS; + data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // Make rot/scale dirty. + _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -227,8 +250,13 @@ void Node3D::set_transform(const Transform3D &p_transform) { Basis Node3D::get_basis() const { return get_transform().basis; } + Quaternion Node3D::get_quaternion() const { - return Quaternion(get_transform().basis); + if (data.dirty & DIRTY_LOCAL_TRANSFORM) { + _update_local_transform(); + } + + return data.local_transform.basis.get_rotation_quaternion(); } void Node3D::set_global_transform(const Transform3D &p_transform) { @@ -240,7 +268,7 @@ void Node3D::set_global_transform(const Transform3D &p_transform) { } Transform3D Node3D::get_transform() const { - if (data.dirty & DIRTY_LOCAL) { + if (data.dirty & DIRTY_LOCAL_TRANSFORM) { _update_local_transform(); } @@ -249,8 +277,8 @@ Transform3D Node3D::get_transform() const { Transform3D Node3D::get_global_transform() const { ERR_FAIL_COND_V(!is_inside_tree(), Transform3D()); - if (data.dirty & DIRTY_GLOBAL) { - if (data.dirty & DIRTY_LOCAL) { + if (data.dirty & DIRTY_GLOBAL_TRANSFORM) { + if (data.dirty & DIRTY_LOCAL_TRANSFORM) { _update_local_transform(); } @@ -264,7 +292,7 @@ Transform3D Node3D::get_global_transform() const { data.global_transform.basis.orthonormalize(); } - data.dirty &= ~DIRTY_GLOBAL; + data.dirty &= ~DIRTY_GLOBAL_TRANSFORM; } return data.global_transform; @@ -314,13 +342,27 @@ void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) { if (data.rotation_edit_mode == p_mode) { return; } + + bool transform_changed = false; + if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !(data.dirty & DIRTY_LOCAL_TRANSFORM)) { + data.local_transform.orthogonalize(); + transform_changed = true; + } + data.rotation_edit_mode = p_mode; - // Shearing is not allowed except in ROTATION_EDIT_MODE_BASIS. - data.dirty |= DIRTY_LOCAL; - _propagate_transform_changed(this); - if (data.notify_local_transform) { - notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + if (p_mode == ROTATION_EDIT_MODE_EULER && (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE)) { + // If going to Euler mode, ensure that vectors are _not_ dirty, else the retrieved value may be wrong. + // Otherwise keep what is there, so switching back and forth between modes does not break the vectors. + + _update_rotation_and_scale(); + } + + if (transform_changed) { + _propagate_transform_changed(this); + if (data.notify_local_transform) { + notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + } } notify_property_list_changed(); @@ -333,38 +375,47 @@ Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const { void Node3D::set_rotation_order(RotationOrder p_order) { Basis::EulerOrder order = Basis::EulerOrder(p_order); - if (data.rotation_order == order) { + if (data.euler_rotation_order == order) { return; } ERR_FAIL_INDEX(int32_t(order), 6); + bool transform_changed = false; - if (data.dirty & DIRTY_VECTORS) { - data.rotation = data.local_transform.basis.get_euler_normalized(order); - data.scale = data.local_transform.basis.get_scale(); - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + _update_rotation_and_scale(); + } else if (data.dirty & DIRTY_LOCAL_TRANSFORM) { + data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(order); + transform_changed = true; } else { - data.rotation = Basis::from_euler(data.rotation, data.rotation_order).get_euler_normalized(order); + data.dirty |= DIRTY_LOCAL_TRANSFORM; + transform_changed = true; } - data.rotation_order = order; - //changing rotation order should not affect transform + data.euler_rotation_order = order; - notify_property_list_changed(); //will change rotation + if (transform_changed) { + _propagate_transform_changed(this); + if (data.notify_local_transform) { + notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); + } + } + notify_property_list_changed(); // Will change the rotation property. } Node3D::RotationOrder Node3D::get_rotation_order() const { - return RotationOrder(data.rotation_order); + return RotationOrder(data.euler_rotation_order); } void Node3D::set_rotation(const Vector3 &p_euler_rad) { - if (data.dirty & DIRTY_VECTORS) { + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + // Update scale only if rotation and scale are dirty, as rotation will be overridden. data.scale = data.local_transform.basis.get_scale(); - data.dirty &= ~DIRTY_VECTORS; + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; } - data.rotation = p_euler_rad; - data.dirty |= DIRTY_LOCAL; + data.euler_rotation = p_euler_rad; + data.dirty = DIRTY_LOCAL_TRANSFORM; _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -372,13 +423,14 @@ void Node3D::set_rotation(const Vector3 &p_euler_rad) { } void Node3D::set_scale(const Vector3 &p_scale) { - if (data.dirty & DIRTY_VECTORS) { - data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order); - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + // Update rotation only if rotation and scale are dirty, as scale will be overridden. + data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order); + data.dirty &= ~DIRTY_EULER_ROTATION_AND_SCALE; } data.scale = p_scale; - data.dirty |= DIRTY_LOCAL; + data.dirty = DIRTY_LOCAL_TRANSFORM; _propagate_transform_changed(this); if (data.notify_local_transform) { notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); @@ -390,22 +442,16 @@ Vector3 Node3D::get_position() const { } Vector3 Node3D::get_rotation() const { - if (data.dirty & DIRTY_VECTORS) { - data.scale = data.local_transform.basis.get_scale(); - data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order); - - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + _update_rotation_and_scale(); } - return data.rotation; + return data.euler_rotation; } Vector3 Node3D::get_scale() const { - if (data.dirty & DIRTY_VECTORS) { - data.scale = data.local_transform.basis.get_scale(); - data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order); - - data.dirty &= ~DIRTY_VECTORS; + if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { + _update_rotation_and_scale(); } return data.scale; @@ -865,14 +911,14 @@ Variant Node3D::property_get_revert(const String &p_name) { } else if (p_name == "quaternion") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Quaternion(Transform3D(variant).get_basis()); + r_ret = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion()); } else { return Quaternion(); } } else if (p_name == "rotation") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.rotation_order); + r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order); } else { return Vector3(); } diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 6d857a83ea..cfd88585e4 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -52,6 +52,9 @@ class Node3D : public Node { GDCLASS(Node3D, Node); public: + // Edit mode for the rotation. + // THIS MODE ONLY AFFECTS HOW DATA IS EDITED AND SAVED + // IT DOES _NOT_ AFFECT THE TRANSFORM LOGIC (see comment in TransformDirty). enum RotationEditMode { ROTATION_EDIT_MODE_EULER, ROTATION_EDIT_MODE_QUATERNION, @@ -68,11 +71,27 @@ public: }; private: + // For the sake of ease of use, Node3D can operate with Transforms (Basis+Origin), Quaterinon/Scale and Euler Rotation/Scale. + // Transform and Quaterinon are stored in data.local_transform Basis (so quaternion is not really stored, but converted back/forth from 3x3 matrix on demand). + // Euler needs to be kept separate because converting to Basis and back may result in a different vector (which is troublesome for users + // editing in the inspector, not only because of the numerical precision loss but because they expect these rotations to be consistent, or support + // "redundant" rotations for animation interpolation, like going from 0 to 720 degrees). + // + // As such, the system works in a way where if the local transform is set (via transform/basis/quaternion), the EULER rotation and scale becomes dirty. + // It will remain dirty until reading back is attempted (for performance reasons). Likewise, if the Euler rotation scale are set, the local transform + // will become dirty (and again, will not become valid again until read). + // + // All this is transparent from outside the Node3D API, which allows everything to works by calling these functions in exchange. + // + // Additionally, setting either transform, quaternion, Euler rotation or scale makes the global transform dirty, which will be updated when read again. + // + // NOTE: Again, RotationEditMode is _independent_ of this mechanism, it is only meant to expose the right set of properties for editing (editor) and saving + // (to scene, in order to keep the same values and avoid data loss on conversions). It has zero influence in the logic described above. enum TransformDirty { DIRTY_NONE = 0, - DIRTY_VECTORS = 1, - DIRTY_LOCAL = 2, - DIRTY_GLOBAL = 4 + DIRTY_EULER_ROTATION_AND_SCALE = 1, + DIRTY_LOCAL_TRANSFORM = 2, + DIRTY_GLOBAL_TRANSFORM = 4 }; mutable SelfList<Node> xform_change; @@ -80,8 +99,8 @@ private: struct Data { mutable Transform3D global_transform; mutable Transform3D local_transform; - mutable Basis::EulerOrder rotation_order = Basis::EULER_ORDER_YXZ; - mutable Vector3 rotation; + mutable Basis::EulerOrder euler_rotation_order = Basis::EULER_ORDER_YXZ; + mutable Vector3 euler_rotation; mutable Vector3 scale = Vector3(1, 1, 1); mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER; @@ -131,6 +150,7 @@ protected: _FORCE_INLINE_ void set_ignore_transform_notification(bool p_ignore) { data.ignore_notification = p_ignore; } _FORCE_INLINE_ void _update_local_transform() const; + _FORCE_INLINE_ void _update_rotation_and_scale() const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 7b97b0fa47..118e77c009 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -723,8 +723,20 @@ void Control::_notification(int p_notification) { data.parent_window = Object::cast_to<Window>(get_parent()); data.is_rtl_dirty = true; + if (data.theme.is_null()) { + if (data.parent && (data.parent->data.theme_owner || data.parent->data.theme_owner_window)) { + data.theme_owner = data.parent->data.theme_owner; + data.theme_owner_window = data.parent->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else if (data.parent_window && (data.parent_window->theme_owner || data.parent_window->theme_owner_window)) { + data.theme_owner = data.parent_window->theme_owner; + data.theme_owner_window = data.parent_window->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } + CanvasItem *node = this; - Control *parent_control = nullptr; + bool has_parent_control = false; while (!node->is_set_as_top_level()) { CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent()); @@ -732,22 +744,19 @@ void Control::_notification(int p_notification) { break; } - parent_control = Object::cast_to<Control>(parent); + Control *parent_control = Object::cast_to<Control>(parent); if (parent_control) { + has_parent_control = true; break; } node = parent; } - if (parent_control) { + if (has_parent_control) { // Do nothing, has a parent control. - if (data.theme.is_null() && parent_control->data.theme_owner) { - data.theme_owner = parent_control->data.theme_owner; - notification(NOTIFICATION_THEME_CHANGED); - } } else { - //is a regular root control or top_level + // Is a regular root control or top_level. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); data.RI = viewport->_gui_add_root_control(this); @@ -758,7 +767,7 @@ void Control::_notification(int p_notification) { if (data.parent_canvas_item) { data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed)); } else { - //connect viewport + // Connect viewport. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); viewport->connect("size_changed", callable_mp(this, &Control::_size_changed)); diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 122e36904b..86334882fa 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -174,6 +174,28 @@ void VideoStreamPlayer::_notification(int p_notification) { Size2 s = expand ? get_size() : texture->get_size(); draw_texture_rect(texture, Rect2(Point2(), s), false); } break; + + case NOTIFICATION_PAUSED: { + if (is_playing() && !is_paused()) { + paused_from_tree = true; + if (playback.is_valid()) { + playback->set_paused(true); + set_process_internal(false); + } + last_audio_time = 0; + } + } break; + + case NOTIFICATION_UNPAUSED: { + if (paused_from_tree) { + paused_from_tree = false; + if (playback.is_valid()) { + playback->set_paused(false); + set_process_internal(true); + } + last_audio_time = 0; + } + } break; } } @@ -255,6 +277,10 @@ void VideoStreamPlayer::play() { playback->play(); set_process_internal(true); last_audio_time = 0; + + if (!can_process()) { + _notification(NOTIFICATION_PAUSED); + } } void VideoStreamPlayer::stop() { @@ -281,6 +307,14 @@ bool VideoStreamPlayer::is_playing() const { void VideoStreamPlayer::set_paused(bool p_paused) { paused = p_paused; + if (!p_paused && !can_process()) { + paused_from_tree = true; + return; + } else if (p_paused && paused_from_tree) { + paused_from_tree = false; + return; + } + if (playback.is_valid()) { playback->set_paused(p_paused); set_process_internal(!p_paused); diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 130b2901f1..d2822a989b 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -60,6 +60,7 @@ class VideoStreamPlayer : public Control { int wait_resampler_limit = 2; bool paused = false; + bool paused_from_tree = false; bool autoplay = false; float volume = 1.0; double last_audio_time = 0.0; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index a30eb036db..545ff68b72 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -384,11 +384,7 @@ void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { for (int i = motion_from; i <= motion_to; i++) { data.children[i]->notification(NOTIFICATION_MOVED_IN_PARENT); } - for (const KeyValue<StringName, GroupData> &E : p_child->data.grouped) { - if (E.value.group) { - E.value.group->changed = true; - } - } + p_child->_propagate_groups_dirty(); data.blocked--; } @@ -408,6 +404,18 @@ void Node::raise() { } } +void Node::_propagate_groups_dirty() { + for (const KeyValue<StringName, GroupData> &E : data.grouped) { + if (E.value.group) { + E.value.group->changed = true; + } + } + + for (int i = 0; i < data.children.size(); i++) { + data.children[i]->_propagate_groups_dirty(); + } +} + void Node::add_child_notify(Node *p_child) { // to be used when not wanted } diff --git a/scene/main/node.h b/scene/main/node.h index 5b7bc0a587..3c4727f11c 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -174,6 +174,7 @@ private: void _propagate_after_exit_tree(); void _print_orphan_nodes(); void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification); + void _propagate_groups_dirty(); Array _get_node_and_resource(const NodePath &p_path); void _duplicate_signals(const Node *p_original, Node *p_copy) const; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 69fb5fdf07..73e8f537d9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -822,6 +822,22 @@ void Window::_notification(int p_what) { emit_signal(SceneStringNames::get_singleton()->visibility_changed); RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); } + + if (theme.is_null()) { + Control *parent_c = cast_to<Control>(get_parent()); + if (parent_c && (parent_c->data.theme_owner || parent_c->data.theme_owner_window)) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else { + Window *parent_w = cast_to<Window>(get_parent()); + if (parent_w && (parent_w->theme_owner || parent_w->theme_owner_window)) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } + } } break; case NOTIFICATION_READY: { diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 8ae4872d14..7183accc66 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -3379,17 +3379,6 @@ Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) con return bt->values[p_index].value.out_handle; } -static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; - - return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; -} - real_t Animation::bezier_track_interpolate(int p_track, double p_time) const { //this uses a different interpolation scheme ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); @@ -3438,7 +3427,7 @@ real_t Animation::bezier_track_interpolate(int p_track, double p_time) const { for (int i = 0; i < iterations; i++) { real_t middle = (low + high) / 2; - Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end); + Vector2 interp = start.bezier_interpolate(start_out, end_in, end, middle); if (interp.x < t) { low = middle; @@ -3448,8 +3437,8 @@ real_t Animation::bezier_track_interpolate(int p_track, double p_time) const { } //interpolate the result: - Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end); - Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end); + Vector2 low_pos = start.bezier_interpolate(start_out, end_in, end, low); + Vector2 high_pos = start.bezier_interpolate(start_out, end_in, end, high); real_t c = (t - low_pos.x) / (high_pos.x - low_pos.x); return low_pos.lerp(high_pos, c).y; diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 96cf7bb708..da26a0261f 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -32,18 +32,6 @@ #include "core/core_string_names.h" -template <class T> -static _FORCE_INLINE_ T _bezier_interp(real_t p_t, T p_start, T p_control_1, T p_control_2, T p_end) { - /* Formula from Wikipedia article on Bezier curves. */ - real_t omt = (1.0 - p_t); - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = p_t * p_t; - real_t t3 = t2 * p_t; - - return p_start * omt3 + p_control_1 * omt2 * p_t * 3.0 + p_control_2 * omt * t2 * 3.0 + p_end * t3; -} - const char *Curve::SIGNAL_RANGE_CHANGED = "range_changed"; Curve::Curve() { @@ -376,7 +364,7 @@ real_t Curve::interpolate_local_nocheck(int p_index, real_t p_local_offset) cons real_t yac = a.position.y + d * a.right_tangent; real_t ybc = b.position.y - d * b.left_tangent; - real_t y = _bezier_interp(p_local_offset, a.position.y, yac, ybc, b.position.y); + real_t y = Math::bezier_interpolate(a.position.y, yac, ybc, b.position.y, p_local_offset); return y; } @@ -747,7 +735,7 @@ Vector2 Curve2D::interpolate(int p_index, const real_t p_offset) const { Vector2 p3 = points[p_index + 1].position; Vector2 p2 = p3 + points[p_index + 1].in; - return _bezier_interp(p_offset, p0, p1, p2, p3); + return p0.bezier_interpolate(p1, p2, p3, p_offset); } Vector2 Curve2D::interpolatef(real_t p_findex) const { @@ -767,9 +755,9 @@ void Curve2D::mark_dirty() { void Curve2D::_bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const { real_t mp = p_begin + (p_end - p_begin) * 0.5; - Vector2 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b); - Vector2 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b); + Vector2 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin); + Vector2 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp); + Vector2 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end); Vector2 na = (mid - beg).normalized(); Vector2 nb = (end - mid).normalized(); @@ -828,7 +816,7 @@ void Curve2D::_bake() const { np = 1.0; } - Vector2 npp = _bezier_interp(np, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + Vector2 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np); real_t d = position.distance_to(npp); if (d > bake_interval) { @@ -841,7 +829,7 @@ void Curve2D::_bake() const { real_t mid = low + (hi - low) * 0.5; for (int j = 0; j < iterations; j++) { - npp = _bezier_interp(mid, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid); d = position.distance_to(npp); if (bake_interval < d) { @@ -1336,7 +1324,7 @@ Vector3 Curve3D::interpolate(int p_index, real_t p_offset) const { Vector3 p3 = points[p_index + 1].position; Vector3 p2 = p3 + points[p_index + 1].in; - return _bezier_interp(p_offset, p0, p1, p2, p3); + return p0.bezier_interpolate(p1, p2, p3, p_offset); } Vector3 Curve3D::interpolatef(real_t p_findex) const { @@ -1356,9 +1344,9 @@ void Curve3D::mark_dirty() { void Curve3D::_bake_segment3d(RBMap<real_t, Vector3> &r_bake, real_t p_begin, real_t p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, real_t p_tol) const { real_t mp = p_begin + (p_end - p_begin) * 0.5; - Vector3 beg = _bezier_interp(p_begin, p_a, p_a + p_out, p_b + p_in, p_b); - Vector3 mid = _bezier_interp(mp, p_a, p_a + p_out, p_b + p_in, p_b); - Vector3 end = _bezier_interp(p_end, p_a, p_a + p_out, p_b + p_in, p_b); + Vector3 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin); + Vector3 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp); + Vector3 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end); Vector3 na = (mid - beg).normalized(); Vector3 nb = (end - mid).normalized(); @@ -1426,7 +1414,7 @@ void Curve3D::_bake() const { np = 1.0; } - Vector3 npp = _bezier_interp(np, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + Vector3 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np); real_t d = position.distance_to(npp); if (d > bake_interval) { @@ -1439,7 +1427,7 @@ void Curve3D::_bake() const { real_t mid = low + (hi - low) * 0.5; for (int j = 0; j < iterations; j++) { - npp = _bezier_interp(mid, points[i].position, points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position); + npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid); d = position.distance_to(npp); if (bake_interval < d) { diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h index a3d3449099..2b04ead0af 100644 --- a/scene/resources/gradient.h +++ b/scene/resources/gradient.h @@ -92,10 +92,6 @@ public: void set_interpolation_mode(InterpolationMode p_interp_mode); InterpolationMode get_interpolation_mode(); - _FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) { - return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0))); - } - _FORCE_INLINE_ Color get_color_at_offset(float p_offset) { if (points.is_empty()) { return Color(0, 0, 0, 1); @@ -161,10 +157,10 @@ public: const Point &pointP3 = points[p3]; float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset); - float r = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x); - float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x); - float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x); - float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x); + float r = Math::cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x); + float g = Math::cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x); + float b = Math::cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x); + float a = Math::cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x); return Color(r, g, b, a); } break; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b90f396110..2c58aa83a9 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -35,6 +35,7 @@ #include "core/core_string_names.h" #include "core/io/missing_resource.h" #include "core/io/resource_loader.h" +#include "core/templates/local_vector.h" #include "scene/2d/node_2d.h" #include "scene/3d/node_3d.h" #include "scene/gui/control.h" @@ -43,7 +44,7 @@ #include "scene/property_utils.h" #define PACKED_SCENE_VERSION 2 - +#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_" bool SceneState::can_instantiate() const { return nodes.size() > 0; } @@ -108,6 +109,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { HashMap<Ref<Resource>, Ref<Resource>> resources_local_to_scene; + LocalVector<DeferredNodePathProperties> deferred_node_paths; + for (int i = 0; i < nc; i++) { const NodeData &n = nd[i]; @@ -230,9 +233,28 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { for (int j = 0; j < nprop_count; j++) { bool valid; - ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); + ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr); + if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { + uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); + ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, just set the metadata and be it + node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]); + } else { + // Do an actual deferred sed of the property path. + DeferredNodePathProperties dnp; + dnp.path = props[nprops[j].value]; + dnp.base = node; + dnp.property = snames[name_idx]; + deferred_node_paths.push_back(dnp); + } + continue; + } + + ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); + if (snames[nprops[j].name] == CoreStringNames::get_singleton()->_script) { //work around to avoid old script variables from disappearing, should be the proper fix to: //https://github.com/godotengine/godot/issues/2958 @@ -369,6 +391,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } + for (uint32_t i = 0; i < deferred_node_paths.size(); i++) { + const DeferredNodePathProperties &dnp = deferred_node_paths[i]; + Node *other = dnp.base->get_node_or_null(dnp.path); + dnp.base->set(dnp.property, other); + } + for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) { E.value->setup_local_to_scene(); } @@ -532,6 +560,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (E.name == META_PROPERTY_MISSING_RESOURCES) { continue; // Ignore this property when packing. } + if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) { + continue; // do not save. + } // If instance or inheriting, not saving if property requested so. if (!states_stack.is_empty()) { @@ -542,8 +573,15 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has StringName name = E.name; Variant value = p_node->get(name); + bool use_deferred_node_path_bit = false; - if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) { + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + value = p_node->get(META_POINTER_PROPERTY_BASE + E.name); + if (value.get_type() != Variant::NODE_PATH) { + continue; //was never set, ignore. + } + use_deferred_node_path_bit = true; + } else if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) { // Was this missing resource overridden? If so do not save the old value. Ref<Resource> ures = value; if (ures.is_null()) { @@ -562,6 +600,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has NodeData::Property prop; prop.name = _nm_get_string(name, name_map); prop.value = _vm_get_variant(value, variant_map); + if (use_deferred_node_path_bit) { + prop.name |= FLAG_PATH_PROPERTY_IS_NODE; + } nd.properties.push_back(prop); } @@ -1018,7 +1059,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property, const NodeData::Property *p = nodes[p_node].properties.ptr(); for (int i = 0; i < pc; i++) { - if (p_property == namep[p[i].name]) { + if (p_property == namep[p[i].name & FLAG_PROP_NAME_MASK]) { found = true; return variants[p[i].value]; } @@ -1409,7 +1450,19 @@ int SceneState::get_node_property_count(int p_idx) const { StringName SceneState::get_node_property_name(int p_idx, int p_prop) const { ERR_FAIL_INDEX_V(p_idx, nodes.size(), StringName()); ERR_FAIL_INDEX_V(p_prop, nodes[p_idx].properties.size(), StringName()); - return names[nodes[p_idx].properties[p_prop].name]; + return names[nodes[p_idx].properties[p_prop].name & FLAG_PROP_NAME_MASK]; +} + +Vector<String> SceneState::get_node_deferred_nodepath_properties(int p_idx) const { + Vector<String> ret; + ERR_FAIL_INDEX_V(p_idx, nodes.size(), ret); + for (int i = 0; i < nodes[p_idx].properties.size(); i++) { + uint32_t idx = nodes[p_idx].properties[i].name; + if (idx & FLAG_PATH_PROPERTY_IS_NODE) { + ret.push_back(names[idx & FLAG_PROP_NAME_MASK]); + } + } + return ret; } Variant SceneState::get_node_property_value(int p_idx, int p_prop) const { @@ -1555,13 +1608,16 @@ int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int return nodes.size() - 1; } -void SceneState::add_node_property(int p_node, int p_name, int p_value) { +void SceneState::add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path) { ERR_FAIL_INDEX(p_node, nodes.size()); ERR_FAIL_INDEX(p_name, names.size()); ERR_FAIL_INDEX(p_value, variants.size()); NodeData::Property prop; prop.name = p_name; + if (p_deferred_node_path) { + prop.name |= FLAG_PATH_PROPERTY_IS_NODE; + } prop.value = p_value; nodes.write[p_node].properties.push_back(prop); } @@ -1599,6 +1655,10 @@ void SceneState::add_editable_instance(const NodePath &p_path) { editable_instances.push_back(p_path); } +String SceneState::get_meta_pointer_property(const String &p_property) { + return META_POINTER_PROPERTY_BASE + p_property; +} + Vector<String> SceneState::_get_node_groups(int p_idx) const { Vector<StringName> groups = get_node_groups(p_idx); Vector<String> ret; diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 05abb23284..5f8001c871 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -69,6 +69,12 @@ class SceneState : public RefCounted { Vector<int> groups; }; + struct DeferredNodePathProperties { + Node *base = nullptr; + StringName property; + NodePath path; + }; + Vector<NodeData> nodes; struct ConnectionData { @@ -104,6 +110,8 @@ public: FLAG_ID_IS_PATH = (1 << 30), TYPE_INSTANCED = 0x7FFFFFFF, FLAG_INSTANCE_IS_PLACEHOLDER = (1 << 30), + FLAG_PATH_PROPERTY_IS_NODE = (1 << 30), + FLAG_PROP_NAME_MASK = FLAG_PATH_PROPERTY_IS_NODE - 1, FLAG_MASK = (1 << 24) - 1, }; @@ -157,6 +165,7 @@ public: int get_node_property_count(int p_idx) const; StringName get_node_property_name(int p_idx, int p_prop) const; Variant get_node_property_value(int p_idx, int p_prop) const; + Vector<String> get_node_deferred_nodepath_properties(int p_idx) const; int get_connection_count() const; NodePath get_connection_source(int p_idx) const; @@ -177,7 +186,7 @@ public: int add_value(const Variant &p_value); int add_node_path(const NodePath &p_path); int add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index); - void add_node_property(int p_node, int p_name, int p_value); + void add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path = false); void add_node_group(int p_node, int p_group); void set_base_scene(int p_idx); void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds); @@ -186,6 +195,9 @@ public: virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; } uint64_t get_last_modified_time() const { return last_modified_time; } + // Used when saving pointers (saves a path property instead). + static String get_meta_pointer_property(const String &p_property); + SceneState(); }; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 3cda2f05b2..66afb001fb 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -212,6 +212,15 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars type = SceneState::TYPE_INSTANCED; //no type? assume this was instantiated } + HashSet<StringName> path_properties; + + if (next_tag.fields.has("node_paths")) { + Vector<String> paths = next_tag.fields["node_paths"]; + for (int i = 0; i < paths.size(); i++) { + path_properties.insert(paths[i]); + } + } + if (next_tag.fields.has("instance")) { instance = packed_scene->get_state()->add_value(next_tag.fields["instance"]); @@ -276,9 +285,10 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars } if (!assign.is_empty()) { - int nameidx = packed_scene->get_state()->add_name(assign); + StringName assign_name = assign; + int nameidx = packed_scene->get_state()->add_name(assign_name); int valueidx = packed_scene->get_state()->add_value(value); - packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx); + packed_scene->get_state()->add_node_property(node_id, nameidx, valueidx, path_properties.has(assign_name)); //it's assignment } else if (!next_tag.name.is_empty()) { break; @@ -1939,6 +1949,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso Ref<PackedScene> instance = state->get_node_instance(i); String instance_placeholder = state->get_node_instance_placeholder(i); Vector<StringName> groups = state->get_node_groups(i); + Vector<String> deferred_node_paths = state->get_node_deferred_nodepath_properties(i); String header = "[node"; header += " name=\"" + String(name).c_escape() + "\""; @@ -1955,6 +1966,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso header += " index=\"" + itos(index) + "\""; } + if (deferred_node_paths.size()) { + header += " node_paths=" + Variant(deferred_node_paths).get_construct_string(); + } + if (groups.size()) { // Write all groups on the same line as they're part of a section header. // This improves readability while not impacting VCS friendliness too much, diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp index ac60dc3b9a..8560d92aa2 100644 --- a/servers/movie_writer/movie_writer.cpp +++ b/servers/movie_writer/movie_writer.cpp @@ -107,7 +107,7 @@ void MovieWriter::begin(const Size2i &p_movie_size, uint32_t p_fps, const String AudioDriverDummy::get_dummy_singleton()->set_speaker_mode(AudioDriver::SpeakerMode(get_audio_speaker_mode())); fps = p_fps; if ((mix_rate % fps) != 0) { - WARN_PRINT("Audio mix rate (" + itos(mix_rate) + ") can not be divided by fps (" + itos(fps) + "). Audio may go out of sync over time."); + WARN_PRINT("MovieWriter's audio mix rate (" + itos(mix_rate) + ") can not be divided by the recording FPS (" + itos(fps) + "). Audio may go out of sync over time."); } audio_channels = AudioDriverDummy::get_dummy_singleton()->get_channels(); @@ -128,15 +128,17 @@ void MovieWriter::_bind_methods() { GDVIRTUAL_BIND(_write_frame, "frame_image", "audio_frame_block") GDVIRTUAL_BIND(_write_end) - GLOBAL_DEF("editor/movie_writer/mix_rate_hz", 48000); + GLOBAL_DEF("editor/movie_writer/mix_rate", 48000); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/mix_rate", PropertyInfo(Variant::INT, "editor/movie_writer/mix_rate", PROPERTY_HINT_RANGE, "8000,192000,1,suffix:Hz")); GLOBAL_DEF("editor/movie_writer/speaker_mode", 0); ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/speaker_mode", PropertyInfo(Variant::INT, "editor/movie_writer/speaker_mode", PROPERTY_HINT_ENUM, "Stereo,3.1,5.1,7.1")); GLOBAL_DEF("editor/movie_writer/mjpeg_quality", 0.75); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/mjpeg_quality", PropertyInfo(Variant::FLOAT, "editor/movie_writer/mjpeg_quality", PROPERTY_HINT_RANGE, "0.01,1.0,0.01")); // used by the editor GLOBAL_DEF_BASIC("editor/movie_writer/movie_file", ""); GLOBAL_DEF_BASIC("editor/movie_writer/disable_vsync", false); GLOBAL_DEF_BASIC("editor/movie_writer/fps", 60); - ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/fps", PropertyInfo(Variant::INT, "editor/movie_writer/fps", PROPERTY_HINT_RANGE, "1,300,1")); + ProjectSettings::get_singleton()->set_custom_property_info("editor/movie_writer/fps", PropertyInfo(Variant::INT, "editor/movie_writer/fps", PROPERTY_HINT_RANGE, "1,300,1,suffix:FPS")); } void MovieWriter::set_extensions_hint() { @@ -301,6 +303,6 @@ void MovieWriterPNGWAV::write_end() { } MovieWriterPNGWAV::MovieWriterPNGWAV() { - mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate_hz"); + mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate"); speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode"))); } diff --git a/servers/movie_writer/movie_writer_mjpeg.cpp b/servers/movie_writer/movie_writer_mjpeg.cpp index b0c65e768d..7d9e6e3b87 100644 --- a/servers/movie_writer/movie_writer_mjpeg.cpp +++ b/servers/movie_writer/movie_writer_mjpeg.cpp @@ -257,7 +257,7 @@ void MovieWriterMJPEG::write_end() { } MovieWriterMJPEG::MovieWriterMJPEG() { - mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate_hz"); + mix_rate = GLOBAL_GET("editor/movie_writer/mix_rate"); speaker_mode = AudioServer::SpeakerMode(int(GLOBAL_GET("editor/movie_writer/speaker_mode"))); quality = GLOBAL_GET("editor/movie_writer/mjpeg_quality"); } |