diff options
57 files changed, 1753 insertions, 590 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 3efc0e822a..94db3612b4 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -191,11 +191,11 @@ String Engine::get_architecture_name() const { #elif defined(__i386) || defined(__i386__) || defined(_M_IX86) return "x86_32"; -#elif defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) return "arm64"; -#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7S__) - return "armv7"; +#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7S__) || defined(_M_ARM) + return "arm32"; #elif defined(__riscv) #if __riscv_xlen == 8 diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index fd20440663..cae76b182a 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -253,6 +253,35 @@ public: (-p_pre + 3.0f * p_from - 3.0f * p_to + p_post) * (p_weight * p_weight * p_weight)); } + static _ALWAYS_INLINE_ double cubic_interpolate_angle(double p_from, double p_to, double p_pre, double p_post, double p_weight) { + double from_rot = fmod(p_from, Math_TAU); + + double pre_diff = fmod(p_pre - from_rot, Math_TAU); + double pre_rot = from_rot + fmod(2.0 * pre_diff, Math_TAU) - pre_diff; + + double to_diff = fmod(p_to - from_rot, Math_TAU); + double to_rot = from_rot + fmod(2.0 * to_diff, Math_TAU) - to_diff; + + double post_diff = fmod(p_post - to_rot, Math_TAU); + double post_rot = to_rot + fmod(2.0 * post_diff, Math_TAU) - post_diff; + + return cubic_interpolate(from_rot, to_rot, pre_rot, post_rot, p_weight); + } + static _ALWAYS_INLINE_ float cubic_interpolate_angle(float p_from, float p_to, float p_pre, float p_post, float p_weight) { + float from_rot = fmod(p_from, (float)Math_TAU); + + float pre_diff = fmod(p_pre - from_rot, (float)Math_TAU); + float pre_rot = from_rot + fmod(2.0f * pre_diff, (float)Math_TAU) - pre_diff; + + float to_diff = fmod(p_to - from_rot, (float)Math_TAU); + float to_rot = from_rot + fmod(2.0f * to_diff, (float)Math_TAU) - to_diff; + + float post_diff = fmod(p_post - to_rot, (float)Math_TAU); + float post_rot = to_rot + fmod(2.0f * post_diff, (float)Math_TAU) - post_diff; + + return cubic_interpolate(from_rot, to_rot, pre_rot, post_rot, p_weight); + } + static _ALWAYS_INLINE_ double cubic_interpolate_in_time(double p_from, double p_to, double p_pre, double p_post, double p_weight, double p_to_t, double p_pre_t, double p_post_t) { /* Barry-Goldman method */ @@ -276,6 +305,37 @@ public: return Math::lerp(b1, b2, p_to_t == 0 ? 0.5f : t / p_to_t); } + static _ALWAYS_INLINE_ double cubic_interpolate_angle_in_time(double p_from, double p_to, double p_pre, double p_post, double p_weight, + double p_to_t, double p_pre_t, double p_post_t) { + double from_rot = fmod(p_from, Math_TAU); + + double pre_diff = fmod(p_pre - from_rot, Math_TAU); + double pre_rot = from_rot + fmod(2.0 * pre_diff, Math_TAU) - pre_diff; + + double to_diff = fmod(p_to - from_rot, Math_TAU); + double to_rot = from_rot + fmod(2.0 * to_diff, Math_TAU) - to_diff; + + double post_diff = fmod(p_post - to_rot, Math_TAU); + double post_rot = to_rot + fmod(2.0 * post_diff, Math_TAU) - post_diff; + + return cubic_interpolate_in_time(from_rot, to_rot, pre_rot, post_rot, p_weight, p_to_t, p_pre_t, p_post_t); + } + static _ALWAYS_INLINE_ float cubic_interpolate_angle_in_time(float p_from, float p_to, float p_pre, float p_post, float p_weight, + float p_to_t, float p_pre_t, float p_post_t) { + float from_rot = fmod(p_from, (float)Math_TAU); + + float pre_diff = fmod(p_pre - from_rot, (float)Math_TAU); + float pre_rot = from_rot + fmod(2.0f * pre_diff, (float)Math_TAU) - pre_diff; + + float to_diff = fmod(p_to - from_rot, (float)Math_TAU); + float to_rot = from_rot + fmod(2.0f * to_diff, (float)Math_TAU) - to_diff; + + float post_diff = fmod(p_post - to_rot, (float)Math_TAU); + float post_rot = to_rot + fmod(2.0f * post_diff, (float)Math_TAU) - post_diff; + + return cubic_interpolate_in_time(from_rot, to_rot, pre_rot, post_rot, p_weight, p_to_t, p_pre_t, p_post_t); + } + 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); diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 964150aa2d..1be17405c7 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -367,11 +367,20 @@ struct VariantUtilityFunctions { return Math::cubic_interpolate(from, to, pre, post, weight); } + static inline double cubic_interpolate_angle(double from, double to, double pre, double post, double weight) { + return Math::cubic_interpolate_angle(from, to, pre, post, weight); + } + static inline double cubic_interpolate_in_time(double from, double to, double pre, double post, double weight, double to_t, double pre_t, double post_t) { return Math::cubic_interpolate_in_time(from, to, pre, post, weight, to_t, pre_t, post_t); } + static inline double cubic_interpolate_angle_in_time(double from, double to, double pre, double post, double weight, + double to_t, double pre_t, double post_t) { + return Math::cubic_interpolate_angle_in_time(from, to, pre, post, weight, to_t, pre_t, post_t); + } + 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); } @@ -1419,7 +1428,9 @@ void Variant::_register_variant_utility_functions() { FUNCBINDVR3(lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(lerpf, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(cubic_interpolate, sarray("from", "to", "pre", "post", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); + FUNCBINDR(cubic_interpolate_angle, sarray("from", "to", "pre", "post", "weight"), Variant::UTILITY_FUNC_TYPE_MATH); FUNCBINDR(cubic_interpolate_in_time, sarray("from", "to", "pre", "post", "weight", "to_t", "pre_t", "post_t"), Variant::UTILITY_FUNC_TYPE_MATH); + FUNCBINDR(cubic_interpolate_angle_in_time, sarray("from", "to", "pre", "post", "weight", "to_t", "pre_t", "post_t"), 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); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 59d6f878a9..71579d5173 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -260,6 +260,32 @@ Cubic interpolates between two values by the factor defined in [param weight] with pre and post values. </description> </method> + <method name="cubic_interpolate_angle"> + <return type="float" /> + <param index="0" name="from" type="float" /> + <param index="1" name="to" type="float" /> + <param index="2" name="pre" type="float" /> + <param index="3" name="post" type="float" /> + <param index="4" name="weight" type="float" /> + <description> + Cubic interpolates between two rotation values with shortest path by the factor defined in [param weight] with pre and post values. See also [method lerp_angle]. + </description> + </method> + <method name="cubic_interpolate_angle_in_time"> + <return type="float" /> + <param index="0" name="from" type="float" /> + <param index="1" name="to" type="float" /> + <param index="2" name="pre" type="float" /> + <param index="3" name="post" type="float" /> + <param index="4" name="weight" type="float" /> + <param index="5" name="to_t" type="float" /> + <param index="6" name="pre_t" type="float" /> + <param index="7" name="post_t" type="float" /> + <description> + Cubic interpolates between two rotation values with shortest path by the factor defined in [param weight] with pre and post values. See also [method lerp_angle]. + It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values. + </description> + </method> <method name="cubic_interpolate_in_time"> <return type="float" /> <param index="0" name="from" type="float" /> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 8b97fda4d3..df0fb11ac7 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -600,6 +600,14 @@ <constant name="INTERPOLATION_CUBIC" value="2" enum="InterpolationType"> Cubic interpolation. </constant> + <constant name="INTERPOLATION_LINEAR_ANGLE" value="3" enum="InterpolationType"> + Linear interpolation with shortest path rotation. + [b]Note:[/b] The result value is always normalized and may not match the key value. + </constant> + <constant name="INTERPOLATION_CUBIC_ANGLE" value="4" enum="InterpolationType"> + Cubic interpolation with shortest path rotation. + [b]Note:[/b] The result value is always normalized and may not match the key value. + </constant> <constant name="UPDATE_CONTINUOUS" value="0" enum="UpdateMode"> Update between keyframes. </constant> diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index e5aa78971e..dc093acdcd 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -221,6 +221,9 @@ </method> </methods> <members> + <member name="arrange_nodes_button_hidden" type="bool" setter="set_arrange_nodes_button_hidden" getter="is_arrange_nodes_button_hidden" default="false"> + If [code]true[/code], the Arrange Nodes button is hidden. + </member> <member name="clip_contents" type="bool" setter="set_clip_contents" getter="is_clipping_contents" overrides="Control" default="true" /> <member name="connection_lines_antialiased" type="bool" setter="set_connection_lines_antialiased" getter="is_connection_lines_antialiased" default="true"> If [code]true[/code], the lines between nodes will use antialiasing. diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index 009c329ee2..ebd4525b19 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -156,7 +156,7 @@ <description> Sets properties of the slot with ID [param idx]. If [param enable_left]/[param enable_right], a port will appear and the slot will be able to be connected from this side. - [param type_left]/[param type_right] is an arbitrary type of the port. Only ports with the same type values can be connected. + [param type_left]/[param type_right] is an arbitrary type of the port. Only ports with the same type values can be connected and negative values will disallow all connections to be made via user inputs. [param color_left]/[param color_right] is the tint of the port's icon on this side. [param custom_left]/[param custom_right] is a custom texture for this side's port. [b]Note:[/b] This method only sets properties of the slot. To create the slot, add a [Control]-derived child to the GraphNode. @@ -208,7 +208,7 @@ <param index="0" name="idx" type="int" /> <param index="1" name="type_left" type="int" /> <description> - Sets the left (input) type of the slot [param idx] to [param type_left]. + Sets the left (input) type of the slot [param idx] to [param type_left]. If the value is negative, all connections will be disallowed to be created via user inputs. </description> </method> <method name="set_slot_type_right"> @@ -216,7 +216,7 @@ <param index="0" name="idx" type="int" /> <param index="1" name="type_right" type="int" /> <description> - Sets the right (output) type of the slot [param idx] to [param type_right]. + Sets the right (output) type of the slot [param idx] to [param type_right]. If the value is negative, all connections will be disallowed to be created via user inputs. </description> </method> </methods> @@ -224,6 +224,9 @@ <member name="comment" type="bool" setter="set_comment" getter="is_comment" default="false"> If [code]true[/code], the GraphNode is a comment node. </member> + <member name="draggable" type="bool" setter="set_draggable" getter="is_draggable" default="true"> + If [code]true[/code], the user can drag the GraphNode. + </member> <member name="language" type="String" setter="set_language" getter="get_language" default=""""> Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. </member> @@ -239,6 +242,9 @@ If [code]true[/code], the user can resize the GraphNode. [b]Note:[/b] Dragging the handle will only emit the [signal resize_request] signal, the GraphNode needs to be resized manually. </member> + <member name="selectable" type="bool" setter="set_selectable" getter="is_selectable" default="true"> + If [code]true[/code], the user can select the GraphNode. + </member> <member name="selected" type="bool" setter="set_selected" getter="is_selected" default="false"> If [code]true[/code], the GraphNode is selected. </member> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 519554e026..bc50e39812 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -878,7 +878,7 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write("----\n\n") if i == 0: - f.write(".. _class_{}_annotation_{}:\n\n".format(class_name, m.name.strip("@"))) + f.write(".. _class_{}_annotation_{}:\n\n".format(class_name, m.name)) _, signature = make_method_signature(class_def, m, "", state) f.write("- {}\n\n".format(signature)) @@ -1337,8 +1337,10 @@ def format_text_block( # Cross-references to items in this or other class documentation pages. elif is_in_tagset(cmd, RESERVED_CROSSLINK_TAGS): + link_type: str = "" link_target: str = "" if space_pos >= 0: + link_type = tag_text[:space_pos] link_target = tag_text[space_pos + 1 :].strip() if link_target == "": @@ -1350,11 +1352,13 @@ def format_text_block( else: if ( cmd.startswith("method") + or cmd.startswith("constructor") + or cmd.startswith("operator") or cmd.startswith("member") or cmd.startswith("signal") - or cmd.startswith("constant") or cmd.startswith("annotation") or cmd.startswith("theme_item") + or cmd.startswith("constant") ): if link_target.find(".") != -1: ss = link_target.split(".") @@ -1371,48 +1375,62 @@ def format_text_block( class_param = state.current_class method_param = link_target - ref_type = "" + # Default to the tag command name. This works by default for most tags, + # but member and theme_item have special cases. + ref_type = "_{}".format(link_type) + if link_type == "member": + ref_type = "_property" + if class_param in state.classes: class_def = state.classes[class_param] - if cmd.startswith("constructor"): - if method_param not in class_def.constructors: - print_error( - '{}.xml: Unresolved constructor reference "{}" in {}.'.format( - state.current_class, link_target, context_name - ), - state, - ) - ref_type = "_constructor" - elif cmd.startswith("method"): - if method_param not in class_def.methods: - print_error( - '{}.xml: Unresolved method reference "{}" in {}.'.format( - state.current_class, link_target, context_name - ), - state, - ) - ref_type = "_method" + if cmd.startswith("method") and method_param not in class_def.methods: + print_error( + '{}.xml: Unresolved method reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) - elif cmd.startswith("operator"): - if method_param not in class_def.operators: - print_error( - '{}.xml: Unresolved operator reference "{}" in {}.'.format( - state.current_class, link_target, context_name - ), - state, - ) - ref_type = "_operator" + elif cmd.startswith("constructor") and method_param not in class_def.constructors: + print_error( + '{}.xml: Unresolved constructor reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) - elif cmd.startswith("member"): - if method_param not in class_def.properties: - print_error( - '{}.xml: Unresolved member reference "{}" in {}.'.format( - state.current_class, link_target, context_name - ), - state, - ) - ref_type = "_property" + elif cmd.startswith("operator") and method_param not in class_def.operators: + print_error( + '{}.xml: Unresolved operator reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + + elif cmd.startswith("member") and method_param not in class_def.properties: + print_error( + '{}.xml: Unresolved member reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + + elif cmd.startswith("signal") and method_param not in class_def.signals: + print_error( + '{}.xml: Unresolved signal reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) + + elif cmd.startswith("annotation") and method_param not in class_def.annotations: + print_error( + '{}.xml: Unresolved annotation reference "{}" in {}.'.format( + state.current_class, link_target, context_name + ), + state, + ) elif cmd.startswith("theme_item"): if method_param not in class_def.theme_items: @@ -1422,27 +1440,9 @@ def format_text_block( ), state, ) - ref_type = "_theme_{}".format(class_def.theme_items[method_param].data_name) - - elif cmd.startswith("signal"): - if method_param not in class_def.signals: - print_error( - '{}.xml: Unresolved signal reference "{}" in {}.'.format( - state.current_class, link_target, context_name - ), - state, - ) - ref_type = "_signal" - - elif cmd.startswith("annotation"): - if method_param not in class_def.annotations: - print_error( - '{}.xml: Unresolved annotation reference "{}" in {}.'.format( - state.current_class, link_target, context_name - ), - state, - ) - ref_type = "_annotation" + else: + # Needs theme data type to be properly linked, which we cannot get without a class. + ref_type = "_theme_{}".format(class_def.theme_items[method_param].data_name) elif cmd.startswith("constant"): found = False @@ -1473,7 +1473,6 @@ def format_text_block( ), state, ) - ref_type = "_constant" else: print_error( diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index f55f970e67..cdeb1f36d9 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2121,10 +2121,12 @@ void AnimationTrackEdit::_notification(int p_what) { get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")), get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), }; - Ref<Texture2D> interp_icon[3] = { + Ref<Texture2D> interp_icon[5] = { get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), + get_theme_icon(SNAME("InterpLinearAngle"), SNAME("EditorIcons")), + get_theme_icon(SNAME("InterpCubicAngle"), SNAME("EditorIcons")), }; Ref<Texture2D> cont_icon[4] = { get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), @@ -2847,6 +2849,23 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST); menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR); menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC); + // Check is angle property. + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + AnimationPlayer *ap = ape->get_player(); + if (ap) { + NodePath path = animation->track_get_path(track); + Node *nd = ap->get_node(ap->get_root())->get_node(NodePath(path.get_concatenated_names())); + StringName prop = path.get_concatenated_subnames(); + PropertyInfo prop_info; + ClassDB::get_property_info(nd->get_class(), prop, &prop_info); + bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians") != -1; + if (is_angle) { + menu->add_icon_item(get_theme_icon(SNAME("InterpLinearAngle"), SNAME("EditorIcons")), TTR("Linear Angle"), MENU_INTERPOLATION_LINEAR_ANGLE); + menu->add_icon_item(get_theme_icon(SNAME("InterpCubicAngle"), SNAME("EditorIcons")), TTR("Cubic Angle"), MENU_INTERPOLATION_CUBIC_ANGLE); + } + } + } menu->reset_size(); Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height); @@ -3187,7 +3206,9 @@ void AnimationTrackEdit::_menu_selected(int p_index) { } break; case MENU_INTERPOLATION_NEAREST: case MENU_INTERPOLATION_LINEAR: - case MENU_INTERPOLATION_CUBIC: { + case MENU_INTERPOLATION_CUBIC: + case MENU_INTERPOLATION_LINEAR_ANGLE: + case MENU_INTERPOLATION_CUBIC_ANGLE: { Animation::InterpolationType interp_mode = Animation::InterpolationType(p_index - MENU_INTERPOLATION_NEAREST); undo_redo->create_action(TTR("Change Animation Interpolation Mode")); undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode); @@ -6041,6 +6062,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { Vector<int> keys = E->value; int len = keys.size() - 1; + // Special case for angle interpolation. + bool is_using_angle = animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_LINEAR_ANGLE || animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_CUBIC_ANGLE; + // Make insert queue. Vector<Pair<double, Variant>> insert_queue; for (int i = 0; i < len; i++) { @@ -6050,6 +6074,12 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { double to_t = animation->track_get_key_time(track, keys[i + 1]); Variant from_v = animation->track_get_key_value(track, keys[i]); Variant to_v = animation->track_get_key_value(track, keys[i + 1]); + if (is_using_angle) { + real_t a = from_v; + real_t b = to_v; + real_t to_diff = fmod(b - a, Math_TAU); + to_v = a + fmod(2.0 * to_diff, Math_TAU) - to_diff; + } Variant delta_v; Variant::sub(to_v, from_v, delta_v); double duration = to_t - from_t; @@ -6165,15 +6195,12 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { case EDIT_GOTO_PREV_STEP: { goto_prev_step(false); } break; - case EDIT_APPLY_RESET: { - AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true); - } break; - case EDIT_BAKE_ANIMATION: { + case EDIT_BAKE_TRACK: { bake_dialog->popup_centered(Size2(200, 100) * EDSCALE); } break; - case EDIT_BAKE_ANIMATION_CONFIRM: { - undo_redo->create_action(TTR("Bake Animation as Linear keys.")); + case EDIT_BAKE_TRACK_CONFIRM: { + undo_redo->create_action(TTR("Bake Track as Linear keys.")); int track_len = animation->get_track_count(); bool b_trs = bake_trs->is_pressed(); @@ -6191,10 +6218,14 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { do_bake |= b_bs && type == Animation::TYPE_BLEND_SHAPE; do_bake |= b_v && type == Animation::TYPE_VALUE; if (do_bake && !animation->track_is_compressed(i)) { - if (animation->track_get_interpolation_type(i) == Animation::INTERPOLATION_NEAREST) { - continue; // Nearest interpolation cannot be baked. + Animation::InterpolationType it = animation->track_get_interpolation_type(i); + if (it == Animation::INTERPOLATION_NEAREST) { + continue; // Nearest and Angle interpolation cannot be baked. } + // Special case for angle interpolation. + bool is_using_angle = it == Animation::INTERPOLATION_LINEAR_ANGLE || it == Animation::INTERPOLATION_CUBIC_ANGLE; + // Make insert queue. Vector<Pair<double, Variant>> insert_queue; @@ -6258,7 +6289,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } // Insert keys. - undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, Animation::INTERPOLATION_LINEAR); + undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, is_using_angle ? Animation::INTERPOLATION_LINEAR_ANGLE : Animation::INTERPOLATION_LINEAR); for (int j = insert_queue.size() - 1; j >= 0; j--) { undo_redo->add_do_method(animation.ptr(), "track_insert_key", i, insert_queue[j].first, insert_queue[j].second); undo_redo->add_undo_method(animation.ptr(), "track_remove_key", i, j); @@ -6276,6 +6307,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } break; + case EDIT_APPLY_RESET: { + AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true); + } break; + case EDIT_OPTIMIZE_ANIMATION: { optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE); @@ -6284,6 +6319,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { animation->optimize(optimize_velocity_error->get_value(), optimize_angular_error->get_value(), optimize_precision_error->get_value()); _update_tracks(); undo_redo->clear_history(true, undo_redo->get_history_for_object(animation.ptr()).id); + undo_redo->clear_history(true, undo_redo->get_history_for_object(this).id); } break; case EDIT_CLEAN_UP_ANIMATION: { @@ -6352,6 +6388,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { } undo_redo->clear_history(true, undo_redo->get_history_for_object(animation.ptr()).id); + undo_redo->clear_history(true, undo_redo->get_history_for_object(this).id); _update_tracks(); } @@ -6697,11 +6734,12 @@ AnimationTrackEditor::AnimationTrackEditor() { edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTR("Go to Next Step"), KeyModifierMask::CMD | Key::RIGHT), EDIT_GOTO_NEXT_STEP); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTR("Go to Previous Step"), KeyModifierMask::CMD | Key::LEFT), EDIT_GOTO_PREV_STEP); edit->get_popup()->add_separator(); + edit->get_popup()->add_item(TTR("Bake Track"), EDIT_BAKE_TRACK); + edit->get_popup()->add_separator(); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/apply_reset", TTR("Apply Reset")), EDIT_APPLY_RESET); edit->get_popup()->add_separator(); - edit->get_popup()->add_item(TTR("Bake Animation"), EDIT_BAKE_ANIMATION); - edit->get_popup()->add_item(TTR("Optimize Animation"), EDIT_OPTIMIZE_ANIMATION); - edit->get_popup()->add_item(TTR("Clean-Up Animation"), EDIT_CLEAN_UP_ANIMATION); + edit->get_popup()->add_item(TTR("Optimize Animation (no undo)"), EDIT_OPTIMIZE_ANIMATION); + edit->get_popup()->add_item(TTR("Clean-Up Animation (no undo)"), EDIT_CLEAN_UP_ANIMATION); edit->get_popup()->connect("id_pressed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed)); edit->get_popup()->connect("about_to_popup", callable_mp(this, &AnimationTrackEditor::_edit_menu_about_to_popup)); @@ -6869,8 +6907,8 @@ AnimationTrackEditor::AnimationTrackEditor() { // bake_dialog = memnew(ConfirmationDialog); - bake_dialog->set_title(TTR("Anim. Baker")); - bake_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_BAKE_ANIMATION_CONFIRM)); + bake_dialog->set_title(TTR("Track Baker")); + bake_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_BAKE_TRACK_CONFIRM)); add_child(bake_dialog); GridContainer *bake_grid = memnew(GridContainer); bake_grid->set_columns(2); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 025f910578..a17ee65eab 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -144,6 +144,8 @@ class AnimationTrackEdit : public Control { MENU_INTERPOLATION_NEAREST, MENU_INTERPOLATION_LINEAR, MENU_INTERPOLATION_CUBIC, + MENU_INTERPOLATION_LINEAR_ANGLE, + MENU_INTERPOLATION_CUBIC_ANGLE, MENU_LOOP_WRAP, MENU_LOOP_CLAMP, MENU_KEY_INSERT, @@ -500,7 +502,7 @@ class AnimationTrackEditor : public VBoxContainer { NodePath full_path; NodePath base_path; Animation::TrackType track_type = Animation::TYPE_ANIMATION; - Animation::InterpolationType interp_type = Animation::INTERPOLATION_CUBIC; + Animation::InterpolationType interp_type = Animation::INTERPOLATION_CUBIC_ANGLE; Animation::UpdateMode update_mode = Animation::UPDATE_CAPTURE; Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG; bool loop_wrap = false; @@ -543,9 +545,9 @@ public: EDIT_GOTO_NEXT_STEP, EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY, // Next step without updating animation. EDIT_GOTO_PREV_STEP, + EDIT_BAKE_TRACK, + EDIT_BAKE_TRACK_CONFIRM, EDIT_APPLY_RESET, - EDIT_BAKE_ANIMATION, - EDIT_BAKE_ANIMATION_CONFIRM, EDIT_OPTIMIZE_ANIMATION, EDIT_OPTIMIZE_ANIMATION_CONFIRM, EDIT_CLEAN_UP_ANIMATION, diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index dd4adbb28f..f5d3b4842d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -1477,8 +1477,11 @@ void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, c } } - // Directly override the existing shortcut. - sc->set_events(events); + // Override the existing shortcut only if it wasn't customized by the user (i.e. still "original"). + if (Shortcut::is_event_array_equal(sc->get_events(), sc->get_meta("original"))) { + sc->set_events(events); + } + sc->set_meta("original", events.duplicate(true)); } diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 8216cf66be..128d90cd62 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -149,6 +149,10 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { } void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { + if (read_only) { + return; + } + Ref<InputEventMouseButton> mb = p_event; if (grabbing_grabber) { diff --git a/editor/icons/InterpCubicAngle.svg b/editor/icons/InterpCubicAngle.svg new file mode 100644 index 0000000000..e302d556dc --- /dev/null +++ b/editor/icons/InterpCubicAngle.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 8" height="8" viewBox="0 0 16 8" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#5fff95" stroke-linecap="round"><path d="m2 6c5 0 3-4 6-4s1 4 6 4" stroke-width="2"/><circle cx="14" cy="2" r="1.5" stroke-linejoin="round"/></g></svg> diff --git a/editor/icons/InterpLinearAngle.svg b/editor/icons/InterpLinearAngle.svg new file mode 100644 index 0000000000..af4e87a6cb --- /dev/null +++ b/editor/icons/InterpLinearAngle.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 16 8" height="8" viewBox="0 0 16 8" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fd995f" stroke-linecap="round" stroke-linejoin="round"><path d="m2 6 6-4 6 4" stroke-width="2"/><circle cx="14" cy="2" r="1.5"/></g></svg> diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index 78dcbd997e..3ae48f38fc 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -48,7 +48,9 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_point != -1) { - _erase_selected(); + if (!read_only) { + _erase_selected(); + } accept_event(); } } @@ -56,62 +58,64 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) { - menu->clear(); - animations_menu->clear(); - animations_to_add.clear(); + if (!read_only) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); - List<StringName> classes; - ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); - classes.sort_custom<StringName::AlphCompare>(); + List<StringName> classes; + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + classes.sort_custom<StringName::AlphCompare>(); - menu->add_submenu_item(TTR("Add Animation"), "animations"); + menu->add_submenu_item(TTR("Add Animation"), "animations"); - AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); - ERR_FAIL_COND(!gp); + AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); + ERR_FAIL_COND(!gp); - if (gp->has_node(gp->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); - if (ap) { - List<StringName> names; - ap->get_animation_list(&names); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); - for (const StringName &E : names) { - animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); - animations_to_add.push_back(E); + for (const StringName &E : names) { + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); + animations_to_add.push_back(E); + } } } - } - for (const StringName &E : classes) { - String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation" || name == "StartState" || name == "EndState") { - continue; - } + for (const StringName &E : classes) { + String name = String(E).replace_first("AnimationNode", ""); + if (name == "Animation" || name == "StartState" || name == "EndState") { + continue; + } - int idx = menu->get_item_count(); - menu->add_item(vformat(TTR("Add %s"), name), idx); - menu->set_item_metadata(idx, E); - } + int idx = menu->get_item_count(); + menu->add_item(vformat(TTR("Add %s"), name), idx); + menu->set_item_metadata(idx, E); + } - Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); - if (clipb.is_valid()) { + Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + if (clipb.is_valid()) { + menu->add_separator(); + menu->add_item(TTR("Paste"), MENU_PASTE); + } menu->add_separator(); - menu->add_item(TTR("Paste"), MENU_PASTE); - } - menu->add_separator(); - menu->add_item(TTR("Load..."), MENU_LOAD_FILE); + menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); - menu->reset_size(); - menu->popup(); + menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); + menu->popup(); - add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x; - add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); - add_point_pos += blend_space->get_min_space(); + add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x; + add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + add_point_pos += blend_space->get_min_space(); - if (snap->is_pressed()) { - add_point_pos = Math::snapped(add_point_pos, blend_space->get_snap()); + if (snap->is_pressed()) { + add_point_pos = Math::snapped(add_point_pos, blend_space->get_snap()); + } } } @@ -138,31 +142,33 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) { - if (dragging_selected) { - // move - float point = blend_space->get_blend_point_position(selected_point); - point += drag_ofs.x; + if (!read_only) { + if (dragging_selected) { + // move + float point = blend_space->get_blend_point_position(selected_point); + point += drag_ofs.x; + + if (snap->is_pressed()) { + point = Math::snapped(point, blend_space->get_snap()); + } - if (snap->is_pressed()) { - point = Math::snapped(point, blend_space->get_snap()); + updating = true; + undo_redo->create_action(TTR("Move Node Point")); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + _update_edited_point_pos(); } - updating = true; - undo_redo->create_action(TTR("Move Node Point")); - undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); - undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); - undo_redo->add_do_method(this, "_update_space"); - undo_redo->add_undo_method(this, "_update_space"); - undo_redo->add_do_method(this, "_update_edited_point_pos"); - undo_redo->add_undo_method(this, "_update_edited_point_pos"); - undo_redo->commit_action(); - updating = false; - _update_edited_point_pos(); + dragging_selected_attempt = false; + dragging_selected = false; + blend_space_draw->update(); } - - dragging_selected_attempt = false; - dragging_selected = false; - blend_space_draw->update(); } // *set* the blend @@ -255,10 +261,12 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { for (int i = 0; i < blend_space->get_blend_point_count(); i++) { float point = blend_space->get_blend_point_position(i); - if (dragging_selected && selected_point == i) { - point += drag_ofs.x; - if (snap->is_pressed()) { - point = Math::snapped(point, blend_space->get_snap()); + if (!read_only) { + if (dragging_selected && selected_point == i) { + point += drag_ofs.x; + if (snap->is_pressed()) { + point = Math::snapped(point, blend_space->get_snap()); + } } } @@ -475,7 +483,7 @@ void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() { void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { bool point_valid = selected_point >= 0 && selected_point < blend_space->get_blend_point_count(); - tool_erase->set_disabled(!point_valid); + tool_erase->set_disabled(!point_valid || read_only); if (point_valid) { Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); @@ -486,7 +494,11 @@ void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { open_editor->hide(); } - edit_hb->show(); + if (!read_only) { + edit_hb->show(); + } else { + edit_hb->hide(); + } } else { edit_hb->hide(); } @@ -589,10 +601,20 @@ bool AnimationNodeBlendSpace1DEditor::can_edit(const Ref<AnimationNode> &p_node) void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) { blend_space = p_node; + read_only = false; if (!blend_space.is_null()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space); + _update_space(); } + + tool_create->set_disabled(read_only); + edit_value->set_editable(!read_only); + label_value->set_editable(!read_only); + min_value->set_editable(!read_only); + max_value->set_editable(!read_only); + sync->set_disabled(read_only); } AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nullptr; diff --git a/editor/plugins/animation_blend_space_1d_editor.h b/editor/plugins/animation_blend_space_1d_editor.h index 125a3382fa..c8b01cb54b 100644 --- a/editor/plugins/animation_blend_space_1d_editor.h +++ b/editor/plugins/animation_blend_space_1d_editor.h @@ -46,6 +46,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendSpace1DEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendSpace1D> blend_space; + bool read_only = false; HBoxContainer *goto_parent_hb = nullptr; Button *goto_parent = nullptr; diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index ff379ea7f7..fac0136203 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -60,11 +60,29 @@ void AnimationNodeBlendSpace2DEditor::edit(const Ref<AnimationNode> &p_node) { blend_space->disconnect("triangles_updated", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_blend_space_changed)); } blend_space = p_node; + read_only = false; if (!blend_space.is_null()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space); + blend_space->connect("triangles_updated", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_blend_space_changed)); _update_space(); } + + tool_create->set_disabled(read_only); + interpolation->set_disabled(read_only); + max_x_value->set_editable(!read_only); + min_x_value->set_editable(!read_only); + max_y_value->set_editable(!read_only); + min_y_value->set_editable(!read_only); + label_x->set_editable(!read_only); + label_y->set_editable(!read_only); + edit_x->set_editable(!read_only); + edit_y->set_editable(!read_only); + tool_triangle->set_disabled(read_only); + auto_triangles->set_disabled(read_only); + sync->set_disabled(read_only); + interpolation->set_disabled(read_only); } StringName AnimationNodeBlendSpace2DEditor::get_blend_position_path() const { @@ -76,7 +94,9 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventKey> k = p_event; if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_point != -1 || selected_triangle != -1) { - _erase_selected(); + if (!read_only) { + _erase_selected(); + } accept_event(); } } @@ -84,57 +104,59 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) { - menu->clear(); - animations_menu->clear(); - animations_to_add.clear(); - List<StringName> classes; - classes.sort_custom<StringName::AlphCompare>(); - - ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); - menu->add_submenu_item(TTR("Add Animation"), "animations"); - - AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); - ERR_FAIL_COND(!gp); - if (gp && gp->has_node(gp->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); - if (ap) { - List<StringName> names; - ap->get_animation_list(&names); - for (const StringName &E : names) { - animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); - animations_to_add.push_back(E); + if (!read_only) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); + List<StringName> classes; + classes.sort_custom<StringName::AlphCompare>(); + + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + menu->add_submenu_item(TTR("Add Animation"), "animations"); + + AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); + ERR_FAIL_COND(!gp); + if (gp && gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + for (const StringName &E : names) { + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); + animations_to_add.push_back(E); + } } } - } - for (const StringName &E : classes) { - String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation" || name == "StartState" || name == "EndState") { - continue; // nope + for (const StringName &E : classes) { + String name = String(E).replace_first("AnimationNode", ""); + if (name == "Animation" || name == "StartState" || name == "EndState") { + continue; // nope + } + int idx = menu->get_item_count(); + menu->add_item(vformat(TTR("Add %s"), name), idx); + menu->set_item_metadata(idx, E); } - int idx = menu->get_item_count(); - menu->add_item(vformat(TTR("Add %s"), name), idx); - menu->set_item_metadata(idx, E); - } - Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); - if (clipb.is_valid()) { + Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + if (clipb.is_valid()) { + menu->add_separator(); + menu->add_item(TTR("Paste"), MENU_PASTE); + } menu->add_separator(); - menu->add_item(TTR("Paste"), MENU_PASTE); - } - menu->add_separator(); - menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - - menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); - menu->reset_size(); - menu->popup(); - add_point_pos = (mb->get_position() / blend_space_draw->get_size()); - add_point_pos.y = 1.0 - add_point_pos.y; - add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); - add_point_pos += blend_space->get_min_space(); - - if (snap->is_pressed()) { - add_point_pos = add_point_pos.snapped(blend_space->get_snap()); + menu->add_item(TTR("Load..."), MENU_LOAD_FILE); + + menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); + menu->popup(); + add_point_pos = (mb->get_position() / blend_space_draw->get_size()); + add_point_pos.y = 1.0 - add_point_pos.y; + add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + add_point_pos += blend_space->get_min_space(); + + if (snap->is_pressed()) { + add_point_pos = add_point_pos.snapped(blend_space->get_snap()); + } } } @@ -222,17 +244,19 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven point = point.snapped(blend_space->get_snap()); } - updating = true; - undo_redo->create_action(TTR("Move Node Point")); - undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); - undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); - undo_redo->add_do_method(this, "_update_space"); - undo_redo->add_undo_method(this, "_update_space"); - undo_redo->add_do_method(this, "_update_edited_point_pos"); - undo_redo->add_undo_method(this, "_update_edited_point_pos"); - undo_redo->commit_action(); - updating = false; - _update_edited_point_pos(); + if (!read_only) { + updating = true; + undo_redo->create_action(TTR("Move Node Point")); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + _update_edited_point_pos(); + } } dragging_selected_attempt = false; dragging_selected = false; @@ -259,7 +283,9 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven if (mm.is_valid() && dragging_selected_attempt) { dragging_selected = true; - drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * (blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, -1); + if (!read_only) { + drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * (blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, -1); + } blend_space_draw->update(); _update_edited_point_pos(); } @@ -355,7 +381,10 @@ void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { } void AnimationNodeBlendSpace2DEditor::_update_tool_erase() { - tool_erase->set_disabled(!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count())); + tool_erase->set_disabled( + (!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count())) || + read_only); + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); if (AnimationTreeEditor::get_singleton()->can_edit(an)) { @@ -363,7 +392,11 @@ void AnimationNodeBlendSpace2DEditor::_update_tool_erase() { } else { open_editor->hide(); } - edit_hb->show(); + if (!read_only) { + edit_hb->show(); + } else { + edit_hb->hide(); + } } else { edit_hb->hide(); } @@ -503,10 +536,12 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { points.clear(); for (int i = 0; i < blend_space->get_blend_point_count(); i++) { Vector2 point = blend_space->get_blend_point_position(i); - if (dragging_selected && selected_point == i) { - point += drag_ofs; - if (snap->is_pressed()) { - point = point.snapped(blend_space->get_snap()); + if (!read_only) { + if (dragging_selected && selected_point == i) { + point += drag_ofs; + if (snap->is_pressed()) { + point = point.snapped(blend_space->get_snap()); + } } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); diff --git a/editor/plugins/animation_blend_space_2d_editor.h b/editor/plugins/animation_blend_space_2d_editor.h index df2bcf254d..1f015a1804 100644 --- a/editor/plugins/animation_blend_space_2d_editor.h +++ b/editor/plugins/animation_blend_space_2d_editor.h @@ -46,6 +46,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendSpace2DEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendSpace2D> blend_space; + bool read_only = false; PanelContainer *panel = nullptr; Button *tool_blend = nullptr; diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 325e2f548b..11054ee11e 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -134,6 +134,8 @@ void AnimationNodeBlendTreeEditor::_update_graph() { GraphNode *node = memnew(GraphNode); graph->add_child(node); + node->set_draggable(!read_only); + Ref<AnimationNode> agnode = blend_tree->get_node(E); ERR_CONTINUE(!agnode.is_valid()); @@ -146,9 +148,10 @@ void AnimationNodeBlendTreeEditor::_update_graph() { if (String(E) != "output") { LineEdit *name = memnew(LineEdit); name->set_text(E); + name->set_editable(!read_only); name->set_expand_to_text_length_enabled(true); node->add_child(name); - node->set_slot(0, false, 0, Color(), true, 0, get_theme_color(SNAME("font_color"), SNAME("Label"))); + node->set_slot(0, false, 0, Color(), true, read_only ? -1 : 0, get_theme_color(SNAME("font_color"), SNAME("Label"))); name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED); name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(name, agnode), CONNECT_DEFERRED); base = 1; @@ -160,7 +163,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { Label *in_name = memnew(Label); node->add_child(in_name); in_name->set_text(agnode->get_input_name(i)); - node->set_slot(base + i, true, 0, get_theme_color(SNAME("font_color"), SNAME("Label")), false, 0, Color()); + node->set_slot(base + i, true, read_only ? -1 : 0, get_theme_color(SNAME("font_color"), SNAME("Label")), false, 0, Color()); } List<PropertyInfo> pinfo; @@ -172,6 +175,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { String base_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E) + "/" + F.name; EditorProperty *prop = EditorInspector::instantiate_property_editor(AnimationTreeEditor::get_singleton()->get_tree(), F.type, base_path, F.hint, F.hint_string, F.usage); if (prop) { + prop->set_read_only(read_only); prop->set_object_and_property(AnimationTreeEditor::get_singleton()->get_tree(), base_path); prop->update_property(); prop->set_name_split_ratio(0); @@ -195,12 +199,16 @@ void AnimationNodeBlendTreeEditor::_update_graph() { if (agnode->has_filter()) { node->add_child(memnew(HSeparator)); - Button *edit_filters = memnew(Button); - edit_filters->set_text(TTR("Edit Filters")); - edit_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); - node->add_child(edit_filters); - edit_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_edit_filters).bind(E), CONNECT_DEFERRED); - edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER); + Button *inspect_filters = memnew(Button); + if (read_only) { + inspect_filters->set_text(TTR("Inspect Filters")); + } else { + inspect_filters->set_text(TTR("Edit Filters")); + } + inspect_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); + node->add_child(inspect_filters); + inspect_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_inspect_filters).bind(E), CONNECT_DEFERRED); + inspect_filters->set_h_size_flags(SIZE_SHRINK_CENTER); } Ref<AnimationNodeAnimation> anim = agnode; @@ -208,6 +216,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { MenuButton *mb = memnew(MenuButton); mb->set_text(anim->get_animation()); mb->set_icon(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons"))); + mb->set_disabled(read_only); Array options; node->add_child(memnew(HSeparator)); @@ -370,10 +379,18 @@ void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 } void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { + if (read_only) { + return; + } + _popup(false, graph->get_screen_position() + graph->get_local_mouse_position(), p_position); } void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { + if (read_only) { + return; + } + Ref<AnimationNode> node = blend_tree->get_node(p_from); if (node.is_valid()) { from_node = p_from; @@ -382,6 +399,10 @@ void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, in } void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position) { + if (read_only) { + return; + } + Ref<AnimationNode> node = blend_tree->get_node(p_to); if (node.is_valid()) { to_node = p_to; @@ -402,6 +423,10 @@ void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Ve } void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + if (read_only) { + return; + } + AnimationNodeBlendTree::ConnectionError err = blend_tree->can_connect_node(p_to, p_to_index, p_from); if (err != AnimationNodeBlendTree::CONNECTION_OK) { @@ -418,6 +443,10 @@ void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int } void AnimationNodeBlendTreeEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + if (read_only) { + return; + } + graph->disconnect_node(p_from, p_from_index, p_to, p_to_index); updating = true; @@ -445,6 +474,10 @@ void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, } void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { + if (read_only) { + return; + } + undo_redo->create_action(TTR("Delete Node")); undo_redo->add_do_method(blend_tree.ptr(), "remove_node", p_which); undo_redo->add_undo_method(blend_tree.ptr(), "add_node", p_which, blend_tree->get_node(p_which), blend_tree.ptr()->get_node_position(p_which)); @@ -464,6 +497,10 @@ void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { } void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<StringName> &p_nodes) { + if (read_only) { + return; + } + List<StringName> to_erase; if (p_nodes.is_empty()) { @@ -495,6 +532,10 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<String } void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) { + if (read_only) { + return; + } + GraphNode *gn = Object::cast_to<GraphNode>(p_node); ERR_FAIL_COND(!gn); @@ -679,7 +720,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano } } - ti->set_editable(0, true); + ti->set_editable(0, !read_only); ti->set_selectable(0, true); ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, concat); @@ -692,7 +733,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano ti = filters->create_item(ti); ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, concat); - ti->set_editable(0, true); + ti->set_editable(0, !read_only); ti->set_selectable(0, true); ti->set_checked(0, anode->is_path_filtered(path)); ti->set_metadata(0, path); @@ -714,7 +755,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano ti = filters->create_item(ti); ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, types_text); - ti->set_editable(0, true); + ti->set_editable(0, !read_only); ti->set_selectable(0, true); ti->set_checked(0, anode->is_path_filtered(path)); ti->set_metadata(0, path); @@ -727,7 +768,15 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano return true; } -void AnimationNodeBlendTreeEditor::_edit_filters(const String &p_which) { +void AnimationNodeBlendTreeEditor::_inspect_filters(const String &p_which) { + if (read_only) { + filter_dialog->set_title(TTR("Inspect Filtered Tracks:")); + } else { + filter_dialog->set_title(TTR("Edit Filtered Tracks:")); + } + + filter_enabled->set_disabled(read_only); + Ref<AnimationNode> anode = blend_tree->get_node(p_which); ERR_FAIL_COND(!anode.is_valid()); @@ -833,6 +882,10 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { } void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) { + if (read_only) { + return; + } + if (updating) { return; } @@ -939,13 +992,20 @@ void AnimationNodeBlendTreeEditor::edit(const Ref<AnimationNode> &p_node) { blend_tree = p_node; + read_only = false; + if (blend_tree.is_null()) { hide(); } else { + read_only = EditorNode::get_singleton()->is_resource_read_only(blend_tree); + blend_tree->connect("removed_from_graph", callable_mp(this, &AnimationNodeBlendTreeEditor::_removed_from_graph)); _update_graph(); } + + add_node->set_disabled(read_only); + graph->set_arrange_nodes_button_hidden(read_only); } AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { @@ -981,6 +1041,7 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { graph->get_zoom_hbox()->move_child(add_node, 0); add_node->get_popup()->connect("id_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_add_node)); add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu).bind(false)); + add_node->set_disabled(read_only); add_options.push_back(AddOption("Animation", "AnimationNodeAnimation")); add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2)); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index 0941051614..cdbf2975f2 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -47,6 +47,9 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendTree> blend_tree; + + bool read_only = false; + GraphEdit *graph = nullptr; MenuButton *add_node = nullptr; Vector2 position_from_popup_menu; @@ -106,7 +109,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _delete_nodes_request(const TypedArray<StringName> &p_nodes); bool _update_filters(const Ref<AnimationNode> &anode); - void _edit_filters(const String &p_which); + void _inspect_filters(const String &p_which); void _filter_edited(); void _filter_toggled(); Ref<AnimationNode> _filter_edit; diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 6e464d9e29..5d5d113262 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -56,7 +56,11 @@ bool AnimationNodeStateMachineEditor::can_edit(const Ref<AnimationNode> &p_node) void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) { state_machine = p_node; + read_only = false; + if (state_machine.is_valid()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(state_machine); + selected_transition_from = StringName(); selected_transition_to = StringName(); selected_transition_index = -1; @@ -66,6 +70,9 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) { _update_mode(); _update_graph(); } + + tool_create->set_disabled(read_only); + tool_connect->set_disabled(read_only); } void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) { @@ -77,7 +84,9 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv Ref<InputEventKey> k = p_event; if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_node != StringName() || !selected_nodes.is_empty() || selected_transition_to != StringName() || selected_transition_from != StringName()) { - _erase_selected(); + if (!read_only) { + _erase_selected(); + } accept_event(); } } @@ -95,9 +104,11 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv Ref<InputEventMouseButton> mb = p_event; // Add new node - if (mb.is_valid() && mb->is_pressed() && !box_selecting && !connecting && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MouseButton::LEFT))) { - connecting_from = StringName(); - _open_menu(mb->get_position()); + if (!read_only) { + if (mb.is_valid() && mb->is_pressed() && !box_selecting && !connecting && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MouseButton::LEFT))) { + connecting_from = StringName(); + _open_menu(mb->get_position()); + } } // Select node or push a field inside @@ -121,22 +132,24 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv return; } - if (node_rects[i].name.has_point(mb->get_position()) && state_machine->can_edit_node(node_rects[i].node_name)) { // edit name - Ref<StyleBox> line_sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); + if (!read_only) { + if (node_rects[i].name.has_point(mb->get_position()) && state_machine->can_edit_node(node_rects[i].node_name)) { // edit name + Ref<StyleBox> line_sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); - Rect2 edit_rect = node_rects[i].name; - edit_rect.position -= line_sb->get_offset(); - edit_rect.size += line_sb->get_minimum_size(); + Rect2 edit_rect = node_rects[i].name; + edit_rect.position -= line_sb->get_offset(); + edit_rect.size += line_sb->get_minimum_size(); - name_edit_popup->set_position(state_machine_draw->get_screen_position() + edit_rect.position); - name_edit_popup->set_size(edit_rect.size); - name_edit->set_text(node_rects[i].node_name); - name_edit_popup->popup(); - name_edit->grab_focus(); - name_edit->select_all(); + name_edit_popup->set_position(state_machine_draw->get_screen_position() + edit_rect.position); + name_edit_popup->set_size(edit_rect.size); + name_edit->set_text(node_rects[i].node_name); + name_edit_popup->popup(); + name_edit->grab_focus(); + name_edit->select_all(); - prev_name = node_rects[i].node_name; - return; + prev_name = node_rects[i].node_name; + return; + } } if (node_rects[i].edit.has_point(mb->get_position())) { //edit name @@ -319,7 +332,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } // Move mouse while connecting - if (mm.is_valid() && connecting) { + if (mm.is_valid() && connecting && !read_only) { connecting_to = mm->get_position(); connecting_to_node = StringName(); state_machine_draw->update(); @@ -333,7 +346,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } // Move mouse while moving a node - if (mm.is_valid() && dragging_selected_attempt) { + if (mm.is_valid() && dragging_selected_attempt && !read_only) { dragging_selected = true; drag_ofs = mm->get_position() - drag_from; snap_x = StringName(); @@ -478,17 +491,21 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } Control::CursorShape AnimationNodeStateMachineEditor::get_cursor_shape(const Point2 &p_pos) const { - // Put ibeam (text cursor) over names to make it clearer that they are editable. - Transform2D xform = panel->get_transform() * state_machine_draw->get_transform(); - Point2 pos = xform.xform_inv(p_pos); Control::CursorShape cursor_shape = get_default_cursor_shape(); - - for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. - if (node_rects[i].node.has_point(pos)) { - if (node_rects[i].name.has_point(pos)) { - cursor_shape = Control::CURSOR_IBEAM; + if (!read_only) { + // Put ibeam (text cursor) over names to make it clearer that they are editable. + Transform2D xform = panel->get_transform() * state_machine_draw->get_transform(); + Point2 pos = xform.xform_inv(p_pos); + + for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. + if (node_rects[i].node.has_point(pos)) { + if (node_rects[i].name.has_point(pos)) { + if (state_machine->can_edit_node(node_rects[i].node_name)) { + cursor_shape = Control::CURSOR_IBEAM; + } + } + break; } - break; } } return cursor_shape; @@ -1847,9 +1864,9 @@ void AnimationNodeStateMachineEditor::_update_mode() { tool_erase_hb->show(); bool nothing_selected = selected_nodes.is_empty() && selected_transition_from == StringName() && selected_transition_to == StringName(); bool start_end_selected = selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node); - tool_erase->set_disabled(nothing_selected || start_end_selected); + tool_erase->set_disabled(nothing_selected || start_end_selected || read_only); - if (selected_nodes.is_empty() || start_end_selected) { + if (selected_nodes.is_empty() || start_end_selected || read_only) { tool_group->set_disabled(true); tool_group->set_visible(true); tool_ungroup->set_visible(false); diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h index fdd1af0f6d..3a59e94a5f 100644 --- a/editor/plugins/animation_state_machine_editor.h +++ b/editor/plugins/animation_state_machine_editor.h @@ -47,6 +47,8 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { Ref<AnimationNodeStateMachine> state_machine; + bool read_only = false; + Button *tool_select = nullptr; Button *tool_create = nullptr; Button *tool_connect = nullptr; diff --git a/methods.py b/methods.py index 9b60d4251f..3a00aa12ca 100644 --- a/methods.py +++ b/methods.py @@ -705,7 +705,7 @@ def generate_vs_project(env, num_jobs): # required for Visual Studio to understand that it needs to generate an NMAKE # project. Do not modify without knowing what you are doing. PLATFORMS = ["Win32", "x64"] - PLATFORM_IDS = ["32", "64"] + PLATFORM_IDS = ["x86_32", "x86_64"] CONFIGURATIONS = ["debug", "release", "release_debug"] CONFIGURATION_IDS = ["tools", "opt", "opt.tools"] diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index b9560ea69b..61df8cdf06 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -293,7 +293,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l String word = str.substr(j, to - j); Color col = Color(); - if (keywords.has(word)) { + if (global_functions.has(word)) { + // "assert" and "preload" are reserved, so highlight even if not followed by a bracket. + if (word == "assert" || word == "preload" || str[to] == '(') { + col = global_function_color; + } + } else if (keywords.has(word)) { col = keywords[word]; } else if (member_keywords.has(word)) { col = member_keywords[word]; @@ -302,12 +307,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (col != Color()) { for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { - col = Color(); // keyword & member indexing not allowed + col = Color(); // keyword, member & global func indexing not allowed break; } else if (str[k] > 32) { break; } } + if (col != Color()) { in_keyword = true; keyword_color = col; @@ -535,6 +541,7 @@ PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const { void GDScriptSyntaxHighlighter::_update_cache() { keywords.clear(); member_keywords.clear(); + global_functions.clear(); color_regions.clear(); color_region_cache.clear(); @@ -591,6 +598,17 @@ void GDScriptSyntaxHighlighter::_update_cache() { } } + /* Global functions. */ + List<StringName> global_function_list; + GDScriptUtilityFunctions::get_function_list(&global_function_list); + Variant::get_utility_function_list(&global_function_list); + // "assert" and "preload" are not utility functions, but are global nonetheless, so insert them. + global_functions.insert(SNAME("assert")); + global_functions.insert(SNAME("preload")); + for (const StringName &E : global_function_list) { + global_functions.insert(E); + } + /* Comments */ const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); List<String> comments; @@ -643,12 +661,14 @@ void GDScriptSyntaxHighlighter::_update_cache() { if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) { function_definition_color = Color(0.4, 0.9, 1.0); + global_function_color = Color(0.6, 0.6, 0.9); node_path_color = Color(0.72, 0.77, 0.49); node_ref_color = Color(0.39, 0.76, 0.35); annotation_color = Color(1.0, 0.7, 0.45); string_name_color = Color(1.0, 0.76, 0.65); } else { function_definition_color = Color(0, 0.6, 0.6); + global_function_color = Color(0.4, 0.2, 0.8); node_path_color = Color(0.18, 0.55, 0); node_ref_color = Color(0.0, 0.5, 0); annotation_color = Color(0.8, 0.37, 0); @@ -656,6 +676,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { } EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color); + EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color); EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color); EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_reference_color", node_ref_color); EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color); @@ -666,6 +687,10 @@ void GDScriptSyntaxHighlighter::_update_cache() { function_definition_color, true); EditorSettings::get_singleton()->set_initial_value( + "text_editor/theme/highlighting/gdscript/global_function_color", + global_function_color, + true); + EditorSettings::get_singleton()->set_initial_value( "text_editor/theme/highlighting/gdscript/node_path_color", node_path_color, true); @@ -684,6 +709,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { } function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color"); + global_function_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/global_function_color"); node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color"); node_ref_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_reference_color"); annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color"); diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index f434d03a31..60b5b092d4 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -49,6 +49,7 @@ private: HashMap<StringName, Color> keywords; HashMap<StringName, Color> member_keywords; + HashSet<StringName> global_functions; enum Type { NONE, @@ -67,10 +68,11 @@ private: TYPE, }; - // colours + // Colors. Color font_color; Color symbol_color; Color function_color; + Color global_function_color; Color function_definition_color; Color built_in_type_color; Color number_color; diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 3277f9beeb..ef7dbabf66 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -153,6 +153,7 @@ def find_app_host_version(dotnet_cmd, search_version_str): from distutils.version import LooseVersion search_version = LooseVersion(search_version_str) + found_match = False try: env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") @@ -172,7 +173,10 @@ def find_app_host_version(dotnet_cmd, search_version_str): version = LooseVersion(version_str) if version >= search_version: - return version_str + search_version = version + found_match = True + if found_match: + return str(search_version) except (subprocess.CalledProcessError, OSError) as e: import sys diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 87adf9efe5..ed20067a92 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -498,6 +498,15 @@ namespace Godot ); } + internal Basis Lerp(Basis to, real_t weight) + { + Basis b = this; + b.Row0 = Row0.Lerp(to.Row0, weight); + b.Row1 = Row1.Lerp(to.Row1, weight); + b.Row2 = Row2.Lerp(to.Row2, weight); + return b; + } + /// <summary> /// Returns the orthonormalized version of the basis matrix (useful to /// call occasionally to avoid rounding errors for orthogonal matrices). diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs index bacf7c89e6..5bce66ea87 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs @@ -10,136 +10,136 @@ namespace Godot { // Color names and values are derived from core/math/color_names.inc internal static readonly Dictionary<string, Color> namedColors = new Dictionary<string, Color> { - { "ALICE_BLUE", new Color(0xF0F8FFFF) }, - { "ANTIQUE_WHITE", new Color(0xFAEBD7FF) }, + { "ALICEBLUE", new Color(0xF0F8FFFF) }, + { "ANTIQUEWHITE", new Color(0xFAEBD7FF) }, { "AQUA", new Color(0x00FFFFFF) }, { "AQUAMARINE", new Color(0x7FFFD4FF) }, { "AZURE", new Color(0xF0FFFFFF) }, { "BEIGE", new Color(0xF5F5DCFF) }, { "BISQUE", new Color(0xFFE4C4FF) }, { "BLACK", new Color(0x000000FF) }, - { "BLANCHED_ALMOND", new Color(0xFFEBCDFF) }, + { "BLANCHEDALMOND", new Color(0xFFEBCDFF) }, { "BLUE", new Color(0x0000FFFF) }, - { "BLUE_VIOLET", new Color(0x8A2BE2FF) }, + { "BLUEVIOLET", new Color(0x8A2BE2FF) }, { "BROWN", new Color(0xA52A2AFF) }, { "BURLYWOOD", new Color(0xDEB887FF) }, - { "CADET_BLUE", new Color(0x5F9EA0FF) }, + { "CADETBLUE", new Color(0x5F9EA0FF) }, { "CHARTREUSE", new Color(0x7FFF00FF) }, { "CHOCOLATE", new Color(0xD2691EFF) }, { "CORAL", new Color(0xFF7F50FF) }, - { "CORNFLOWER_BLUE", new Color(0x6495EDFF) }, + { "CORNFLOWERBLUE", new Color(0x6495EDFF) }, { "CORNSILK", new Color(0xFFF8DCFF) }, { "CRIMSON", new Color(0xDC143CFF) }, { "CYAN", new Color(0x00FFFFFF) }, - { "DARK_BLUE", new Color(0x00008BFF) }, - { "DARK_CYAN", new Color(0x008B8BFF) }, - { "DARK_GOLDENROD", new Color(0xB8860BFF) }, - { "DARK_GRAY", new Color(0xA9A9A9FF) }, - { "DARK_GREEN", new Color(0x006400FF) }, - { "DARK_KHAKI", new Color(0xBDB76BFF) }, - { "DARK_MAGENTA", new Color(0x8B008BFF) }, - { "DARK_OLIVE_GREEN", new Color(0x556B2FFF) }, - { "DARK_ORANGE", new Color(0xFF8C00FF) }, - { "DARK_ORCHID", new Color(0x9932CCFF) }, - { "DARK_RED", new Color(0x8B0000FF) }, - { "DARK_SALMON", new Color(0xE9967AFF) }, - { "DARK_SEA_GREEN", new Color(0x8FBC8FFF) }, - { "DARK_SLATE_BLUE", new Color(0x483D8BFF) }, - { "DARK_SLATE_GRAY", new Color(0x2F4F4FFF) }, - { "DARK_TURQUOISE", new Color(0x00CED1FF) }, - { "DARK_VIOLET", new Color(0x9400D3FF) }, - { "DEEP_PINK", new Color(0xFF1493FF) }, - { "DEEP_SKY_BLUE", new Color(0x00BFFFFF) }, - { "DIM_GRAY", new Color(0x696969FF) }, - { "DODGER_BLUE", new Color(0x1E90FFFF) }, + { "DARKBLUE", new Color(0x00008BFF) }, + { "DARKCYAN", new Color(0x008B8BFF) }, + { "DARKGOLDENROD", new Color(0xB8860BFF) }, + { "DARKGRAY", new Color(0xA9A9A9FF) }, + { "DARKGREEN", new Color(0x006400FF) }, + { "DARKKHAKI", new Color(0xBDB76BFF) }, + { "DARKMAGENTA", new Color(0x8B008BFF) }, + { "DARKOLIVEGREEN", new Color(0x556B2FFF) }, + { "DARKORANGE", new Color(0xFF8C00FF) }, + { "DARKORCHID", new Color(0x9932CCFF) }, + { "DARKRED", new Color(0x8B0000FF) }, + { "DARKSALMON", new Color(0xE9967AFF) }, + { "DARKSEAGREEN", new Color(0x8FBC8FFF) }, + { "DARKSLATEBLUE", new Color(0x483D8BFF) }, + { "DARKSLATEGRAY", new Color(0x2F4F4FFF) }, + { "DARKTURQUOISE", new Color(0x00CED1FF) }, + { "DARKVIOLET", new Color(0x9400D3FF) }, + { "DEEPPINK", new Color(0xFF1493FF) }, + { "DEEPSKYBLUE", new Color(0x00BFFFFF) }, + { "DIMGRAY", new Color(0x696969FF) }, + { "DODGERBLUE", new Color(0x1E90FFFF) }, { "FIREBRICK", new Color(0xB22222FF) }, - { "FLORAL_WHITE", new Color(0xFFFAF0FF) }, - { "FOREST_GREEN", new Color(0x228B22FF) }, + { "FLORALWHITE", new Color(0xFFFAF0FF) }, + { "FORESTGREEN", new Color(0x228B22FF) }, { "FUCHSIA", new Color(0xFF00FFFF) }, { "GAINSBORO", new Color(0xDCDCDCFF) }, - { "GHOST_WHITE", new Color(0xF8F8FFFF) }, + { "GHOSTWHITE", new Color(0xF8F8FFFF) }, { "GOLD", new Color(0xFFD700FF) }, { "GOLDENROD", new Color(0xDAA520FF) }, { "GRAY", new Color(0xBEBEBEFF) }, { "GREEN", new Color(0x00FF00FF) }, - { "GREEN_YELLOW", new Color(0xADFF2FFF) }, + { "GREENYELLOW", new Color(0xADFF2FFF) }, { "HONEYDEW", new Color(0xF0FFF0FF) }, - { "HOT_PINK", new Color(0xFF69B4FF) }, - { "INDIAN_RED", new Color(0xCD5C5CFF) }, + { "HOTPINK", new Color(0xFF69B4FF) }, + { "INDIANRED", new Color(0xCD5C5CFF) }, { "INDIGO", new Color(0x4B0082FF) }, { "IVORY", new Color(0xFFFFF0FF) }, { "KHAKI", new Color(0xF0E68CFF) }, { "LAVENDER", new Color(0xE6E6FAFF) }, - { "LAVENDER_BLUSH", new Color(0xFFF0F5FF) }, - { "LAWN_GREEN", new Color(0x7CFC00FF) }, - { "LEMON_CHIFFON", new Color(0xFFFACDFF) }, - { "LIGHT_BLUE", new Color(0xADD8E6FF) }, - { "LIGHT_CORAL", new Color(0xF08080FF) }, - { "LIGHT_CYAN", new Color(0xE0FFFFFF) }, - { "LIGHT_GOLDENROD", new Color(0xFAFAD2FF) }, - { "LIGHT_GRAY", new Color(0xD3D3D3FF) }, - { "LIGHT_GREEN", new Color(0x90EE90FF) }, - { "LIGHT_PINK", new Color(0xFFB6C1FF) }, - { "LIGHT_SALMON", new Color(0xFFA07AFF) }, - { "LIGHT_SEA_GREEN", new Color(0x20B2AAFF) }, - { "LIGHT_SKY_BLUE", new Color(0x87CEFAFF) }, - { "LIGHT_SLATE_GRAY", new Color(0x778899FF) }, - { "LIGHT_STEEL_BLUE", new Color(0xB0C4DEFF) }, - { "LIGHT_YELLOW", new Color(0xFFFFE0FF) }, + { "LAVENDERBLUSH", new Color(0xFFF0F5FF) }, + { "LAWNGREEN", new Color(0x7CFC00FF) }, + { "LEMONCHIFFON", new Color(0xFFFACDFF) }, + { "LIGHTBLUE", new Color(0xADD8E6FF) }, + { "LIGHTCORAL", new Color(0xF08080FF) }, + { "LIGHTCYAN", new Color(0xE0FFFFFF) }, + { "LIGHTGOLDENROD", new Color(0xFAFAD2FF) }, + { "LIGHTGRAY", new Color(0xD3D3D3FF) }, + { "LIGHTGREEN", new Color(0x90EE90FF) }, + { "LIGHTPINK", new Color(0xFFB6C1FF) }, + { "LIGHTSALMON", new Color(0xFFA07AFF) }, + { "LIGHTSEAGREEN", new Color(0x20B2AAFF) }, + { "LIGHTSKYBLUE", new Color(0x87CEFAFF) }, + { "LIGHTSLATEGRAY", new Color(0x778899FF) }, + { "LIGHTSTEELBLUE", new Color(0xB0C4DEFF) }, + { "LIGHTYELLOW", new Color(0xFFFFE0FF) }, { "LIME", new Color(0x00FF00FF) }, - { "LIME_GREEN", new Color(0x32CD32FF) }, + { "LIMEGREEN", new Color(0x32CD32FF) }, { "LINEN", new Color(0xFAF0E6FF) }, { "MAGENTA", new Color(0xFF00FFFF) }, { "MAROON", new Color(0xB03060FF) }, - { "MEDIUM_AQUAMARINE", new Color(0x66CDAAFF) }, - { "MEDIUM_BLUE", new Color(0x0000CDFF) }, - { "MEDIUM_ORCHID", new Color(0xBA55D3FF) }, - { "MEDIUM_PURPLE", new Color(0x9370DBFF) }, - { "MEDIUM_SEA_GREEN", new Color(0x3CB371FF) }, - { "MEDIUM_SLATE_BLUE", new Color(0x7B68EEFF) }, - { "MEDIUM_SPRING_GREEN", new Color(0x00FA9AFF) }, - { "MEDIUM_TURQUOISE", new Color(0x48D1CCFF) }, - { "MEDIUM_VIOLET_RED", new Color(0xC71585FF) }, - { "MIDNIGHT_BLUE", new Color(0x191970FF) }, - { "MINT_CREAM", new Color(0xF5FFFAFF) }, - { "MISTY_ROSE", new Color(0xFFE4E1FF) }, + { "MEDIUMAQUAMARINE", new Color(0x66CDAAFF) }, + { "MEDIUMBLUE", new Color(0x0000CDFF) }, + { "MEDIUMORCHID", new Color(0xBA55D3FF) }, + { "MEDIUMPURPLE", new Color(0x9370DBFF) }, + { "MEDIUMSEAGREEN", new Color(0x3CB371FF) }, + { "MEDIUMSLATEBLUE", new Color(0x7B68EEFF) }, + { "MEDIUMSPRINGGREEN", new Color(0x00FA9AFF) }, + { "MEDIUMTURQUOISE", new Color(0x48D1CCFF) }, + { "MEDIUMVIOLETRED", new Color(0xC71585FF) }, + { "MIDNIGHTBLUE", new Color(0x191970FF) }, + { "MINTCREAM", new Color(0xF5FFFAFF) }, + { "MISTYROSE", new Color(0xFFE4E1FF) }, { "MOCCASIN", new Color(0xFFE4B5FF) }, - { "NAVAJO_WHITE", new Color(0xFFDEADFF) }, - { "NAVY_BLUE", new Color(0x000080FF) }, - { "OLD_LACE", new Color(0xFDF5E6FF) }, + { "NAVAJOWHITE", new Color(0xFFDEADFF) }, + { "NAVYBLUE", new Color(0x000080FF) }, + { "OLDLACE", new Color(0xFDF5E6FF) }, { "OLIVE", new Color(0x808000FF) }, - { "OLIVE_DRAB", new Color(0x6B8E23FF) }, + { "OLIVEDRAB", new Color(0x6B8E23FF) }, { "ORANGE", new Color(0xFFA500FF) }, - { "ORANGE_RED", new Color(0xFF4500FF) }, + { "ORANGERED", new Color(0xFF4500FF) }, { "ORCHID", new Color(0xDA70D6FF) }, - { "PALE_GOLDENROD", new Color(0xEEE8AAFF) }, - { "PALE_GREEN", new Color(0x98FB98FF) }, - { "PALE_TURQUOISE", new Color(0xAFEEEEFF) }, - { "PALE_VIOLET_RED", new Color(0xDB7093FF) }, - { "PAPAYA_WHIP", new Color(0xFFEFD5FF) }, - { "PEACH_PUFF", new Color(0xFFDAB9FF) }, + { "PALEGOLDENROD", new Color(0xEEE8AAFF) }, + { "PALEGREEN", new Color(0x98FB98FF) }, + { "PALETURQUOISE", new Color(0xAFEEEEFF) }, + { "PALEVIOLETRED", new Color(0xDB7093FF) }, + { "PAPAYAWHIP", new Color(0xFFEFD5FF) }, + { "PEACHPUFF", new Color(0xFFDAB9FF) }, { "PERU", new Color(0xCD853FFF) }, { "PINK", new Color(0xFFC0CBFF) }, { "PLUM", new Color(0xDDA0DDFF) }, - { "POWDER_BLUE", new Color(0xB0E0E6FF) }, + { "POWDERBLUE", new Color(0xB0E0E6FF) }, { "PURPLE", new Color(0xA020F0FF) }, - { "REBECCA_PURPLE", new Color(0x663399FF) }, + { "REBECCAPURPLE", new Color(0x663399FF) }, { "RED", new Color(0xFF0000FF) }, - { "ROSY_BROWN", new Color(0xBC8F8FFF) }, - { "ROYAL_BLUE", new Color(0x4169E1FF) }, - { "SADDLE_BROWN", new Color(0x8B4513FF) }, + { "ROSYBROWN", new Color(0xBC8F8FFF) }, + { "ROYALBLUE", new Color(0x4169E1FF) }, + { "SADDLEBROWN", new Color(0x8B4513FF) }, { "SALMON", new Color(0xFA8072FF) }, - { "SANDY_BROWN", new Color(0xF4A460FF) }, - { "SEA_GREEN", new Color(0x2E8B57FF) }, + { "SANDYBROWN", new Color(0xF4A460FF) }, + { "SEAGREEN", new Color(0x2E8B57FF) }, { "SEASHELL", new Color(0xFFF5EEFF) }, { "SIENNA", new Color(0xA0522DFF) }, { "SILVER", new Color(0xC0C0C0FF) }, - { "SKY_BLUE", new Color(0x87CEEBFF) }, - { "SLATE_BLUE", new Color(0x6A5ACDFF) }, - { "SLATE_GRAY", new Color(0x708090FF) }, + { "SKYBLUE", new Color(0x87CEEBFF) }, + { "SLATEBLUE", new Color(0x6A5ACDFF) }, + { "SLATEGRAY", new Color(0x708090FF) }, { "SNOW", new Color(0xFFFAFAFF) }, - { "SPRING_GREEN", new Color(0x00FF7FFF) }, - { "STEEL_BLUE", new Color(0x4682B4FF) }, + { "SPRINGGREEN", new Color(0x00FF7FFF) }, + { "STEELBLUE", new Color(0x4682B4FF) }, { "TAN", new Color(0xD2B48CFF) }, { "TEAL", new Color(0x008080FF) }, { "THISTLE", new Color(0xD8BFD8FF) }, @@ -147,15 +147,15 @@ namespace Godot { "TRANSPARENT", new Color(0xFFFFFF00) }, { "TURQUOISE", new Color(0x40E0D0FF) }, { "VIOLET", new Color(0xEE82EEFF) }, - { "WEB_GRAY", new Color(0x808080FF) }, - { "WEB_GREEN", new Color(0x008000FF) }, - { "WEB_MAROON", new Color(0x800000FF) }, - { "WEB_PURPLE", new Color(0x800080FF) }, + { "WEBGRAY", new Color(0x808080FF) }, + { "WEBGREEN", new Color(0x008000FF) }, + { "WEBMAROON", new Color(0x800000FF) }, + { "WEBPURPLE", new Color(0x800080FF) }, { "WHEAT", new Color(0xF5DEB3FF) }, { "WHITE", new Color(0xFFFFFFFF) }, - { "WHITE_SMOKE", new Color(0xF5F5F5FF) }, + { "WHITESMOKE", new Color(0xF5F5F5FF) }, { "YELLOW", new Color(0xFFFF00FF) }, - { "YELLOW_GREEN", new Color(0x9ACD32FF) }, + { "YELLOWGREEN", new Color(0x9ACD32FF) }, }; #pragma warning disable CS1591 // Disable warning: "Missing XML comment for publicly visible type or member" diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 00e775e6ad..b30012d214 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -175,7 +175,8 @@ namespace Godot } /// <summary> - /// Cubic interpolates between two values by a normalized value with pre and post values. + /// Cubic interpolates between two values by the factor defined in <paramref name="weight"/> + /// with pre and post values. /// </summary> /// <param name="from">The start value for interpolation.</param> /// <param name="to">The destination value for interpolation.</param> @@ -193,6 +194,93 @@ namespace Godot } /// <summary> + /// Cubic interpolates between two rotation values with shortest path + /// by the factor defined in <paramref name="weight"/> with pre and post values. + /// See also <see cref="LerpAngle"/>. + /// </summary> + /// <param name="from">The start value for interpolation.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="pre">The value which before "from" value for interpolation.</param> + /// <param name="post">The value which after "to" value for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public static real_t CubicInterpolateAngle(real_t from, real_t to, real_t pre, real_t post, real_t weight) + { + real_t fromRot = from % Mathf.Tau; + + real_t preDiff = (pre - fromRot) % Mathf.Tau; + real_t preRot = fromRot + (2.0f * preDiff) % Mathf.Tau - preDiff; + + real_t toDiff = (to - fromRot) % Mathf.Tau; + real_t toRot = fromRot + (2.0f * toDiff) % Mathf.Tau - toDiff; + + real_t postDiff = (post - toRot) % Mathf.Tau; + real_t postRot = toRot + (2.0f * postDiff) % Mathf.Tau - postDiff; + + return CubicInterpolate(fromRot, toRot, preRot, postRot, weight); + } + + /// <summary> + /// Cubic interpolates between two values by the factor defined in <paramref name="weight"/> + /// with pre and post values. + /// It can perform smoother interpolation than <see cref="CubicInterpolate"/> + /// by the time values. + /// </summary> + /// <param name="from">The start value for interpolation.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="pre">The value which before "from" value for interpolation.</param> + /// <param name="post">The value which after "to" value for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="toT"></param> + /// <param name="preT"></param> + /// <param name="postT"></param> + /// <returns>The resulting value of the interpolation.</returns> + public static real_t CubicInterpolateInTime(real_t from, real_t to, real_t pre, real_t post, real_t weight, real_t toT, real_t preT, real_t postT) + { + /* Barry-Goldman method */ + real_t t = Lerp(0.0f, toT, weight); + real_t a1 = Lerp(pre, from, preT == 0 ? 0.0f : (t - preT) / -preT); + real_t a2 = Lerp(from, to, toT == 0 ? 0.5f : t / toT); + real_t a3 = Lerp(to, post, postT - toT == 0 ? 1.0f : (t - toT) / (postT - toT)); + real_t b1 = Lerp(a1, a2, toT - preT == 0 ? 0.0f : (t - preT) / (toT - preT)); + real_t b2 = Lerp(a2, a3, postT == 0 ? 1.0f : t / postT); + return Lerp(b1, b2, toT == 0 ? 0.5f : t / toT); + } + + /// <summary> + /// Cubic interpolates between two rotation values with shortest path + /// by the factor defined in <paramref name="weight"/> with pre and post values. + /// See also <see cref="LerpAngle"/>. + /// It can perform smoother interpolation than <see cref="CubicInterpolateAngle"/> + /// by the time values. + /// </summary> + /// <param name="from">The start value for interpolation.</param> + /// <param name="to">The destination value for interpolation.</param> + /// <param name="pre">The value which before "from" value for interpolation.</param> + /// <param name="post">The value which after "to" value for interpolation.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="toT"></param> + /// <param name="preT"></param> + /// <param name="postT"></param> + /// <returns>The resulting value of the interpolation.</returns> + public static real_t CubicInterpolateAngleInTime(real_t from, real_t to, real_t pre, real_t post, real_t weight, + real_t toT, real_t preT, real_t postT) + { + real_t fromRot = from % Mathf.Tau; + + real_t preDiff = (pre - fromRot) % Mathf.Tau; + real_t preRot = fromRot + (2.0f * preDiff) % Mathf.Tau - preDiff; + + real_t toDiff = (to - fromRot) % Mathf.Tau; + real_t toRot = fromRot + (2.0f * toDiff) % Mathf.Tau - toDiff; + + real_t postDiff = (post - toRot) % Mathf.Tau; + real_t postRot = toRot + (2.0f * postDiff) % Mathf.Tau - postDiff; + + return CubicInterpolateInTime(fromRot, toRot, preRot, postRot, weight, toT, preT, postT); + } + + /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by /// the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 999500ca13..5cc478ca71 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -132,7 +132,7 @@ namespace Godot } /// <summary> - /// Performs a cubic spherical interpolation between quaternions <paramref name="preA"/>, this quaternion, + /// Performs a spherical cubic interpolation between quaternions <paramref name="preA"/>, this quaternion, /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. /// </summary> /// <param name="b">The destination quaternion.</param> @@ -140,12 +140,128 @@ namespace Godot /// <param name="postB">A quaternion after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated quaternion.</returns> - public Quaternion CubicSlerp(Quaternion b, Quaternion preA, Quaternion postB, real_t weight) + public Quaternion SphericalCubicInterpolate(Quaternion b, Quaternion preA, Quaternion postB, real_t weight) { - real_t t2 = (1.0f - weight) * weight * 2f; - Quaternion sp = Slerp(b, weight); - Quaternion sq = preA.Slerpni(postB, weight); - return sp.Slerpni(sq, t2); +#if DEBUG + if (!IsNormalized()) + { + throw new InvalidOperationException("Quaternion is not normalized"); + } + if (!b.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(b)); + } +#endif + + // Align flip phases. + Quaternion fromQ = new Basis(this).GetRotationQuaternion(); + Quaternion preQ = new Basis(preA).GetRotationQuaternion(); + Quaternion toQ = new Basis(b).GetRotationQuaternion(); + Quaternion postQ = new Basis(postB).GetRotationQuaternion(); + + // Flip quaternions to shortest path if necessary. + bool flip1 = Math.Sign(fromQ.Dot(preQ)) < 0; + preQ = flip1 ? -preQ : preQ; + bool flip2 = Math.Sign(fromQ.Dot(toQ)) < 0; + toQ = flip2 ? -toQ : toQ; + bool flip3 = flip2 ? toQ.Dot(postQ) <= 0 : Math.Sign(toQ.Dot(postQ)) < 0; + postQ = flip3 ? -postQ : postQ; + + // Calc by Expmap in fromQ space. + Quaternion lnFrom = new Quaternion(0, 0, 0, 0); + Quaternion lnTo = (fromQ.Inverse() * toQ).Log(); + Quaternion lnPre = (fromQ.Inverse() * preQ).Log(); + Quaternion lnPost = (fromQ.Inverse() * postQ).Log(); + Quaternion ln = new Quaternion( + Mathf.CubicInterpolate(lnFrom.x, lnTo.x, lnPre.x, lnPost.x, weight), + Mathf.CubicInterpolate(lnFrom.y, lnTo.y, lnPre.y, lnPost.y, weight), + Mathf.CubicInterpolate(lnFrom.z, lnTo.z, lnPre.z, lnPost.z, weight), + 0); + Quaternion q1 = fromQ * ln.Exp(); + + // Calc by Expmap in toQ space. + lnFrom = (toQ.Inverse() * fromQ).Log(); + lnTo = new Quaternion(0, 0, 0, 0); + lnPre = (toQ.Inverse() * preQ).Log(); + lnPost = (toQ.Inverse() * postQ).Log(); + ln = new Quaternion( + Mathf.CubicInterpolate(lnFrom.x, lnTo.x, lnPre.x, lnPost.x, weight), + Mathf.CubicInterpolate(lnFrom.y, lnTo.y, lnPre.y, lnPost.y, weight), + Mathf.CubicInterpolate(lnFrom.z, lnTo.z, lnPre.z, lnPost.z, weight), + 0); + Quaternion q2 = toQ * ln.Exp(); + + // To cancel error made by Expmap ambiguity, do blends. + return q1.Slerp(q2, weight); + } + + /// <summary> + /// Performs a spherical cubic interpolation between quaternions <paramref name="preA"/>, this quaternion, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. + /// It can perform smoother interpolation than <see cref="SphericalCubicInterpolate"/> + /// by the time values. + /// </summary> + /// <param name="b">The destination quaternion.</param> + /// <param name="preA">A quaternion before this quaternion.</param> + /// <param name="postB">A quaternion after <paramref name="b"/>.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="bT"></param> + /// <param name="preAT"></param> + /// <param name="postBT"></param> + /// <returns>The interpolated quaternion.</returns> + public Quaternion SphericalCubicInterpolateInTime(Quaternion b, Quaternion preA, Quaternion postB, real_t weight, real_t bT, real_t preAT, real_t postBT) + { +#if DEBUG + if (!IsNormalized()) + { + throw new InvalidOperationException("Quaternion is not normalized"); + } + if (!b.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(b)); + } +#endif + + // Align flip phases. + Quaternion fromQ = new Basis(this).GetRotationQuaternion(); + Quaternion preQ = new Basis(preA).GetRotationQuaternion(); + Quaternion toQ = new Basis(b).GetRotationQuaternion(); + Quaternion postQ = new Basis(postB).GetRotationQuaternion(); + + // Flip quaternions to shortest path if necessary. + bool flip1 = Math.Sign(fromQ.Dot(preQ)) < 0; + preQ = flip1 ? -preQ : preQ; + bool flip2 = Math.Sign(fromQ.Dot(toQ)) < 0; + toQ = flip2 ? -toQ : toQ; + bool flip3 = flip2 ? toQ.Dot(postQ) <= 0 : Math.Sign(toQ.Dot(postQ)) < 0; + postQ = flip3 ? -postQ : postQ; + + // Calc by Expmap in fromQ space. + Quaternion lnFrom = new Quaternion(0, 0, 0, 0); + Quaternion lnTo = (fromQ.Inverse() * toQ).Log(); + Quaternion lnPre = (fromQ.Inverse() * preQ).Log(); + Quaternion lnPost = (fromQ.Inverse() * postQ).Log(); + Quaternion ln = new Quaternion( + Mathf.CubicInterpolateInTime(lnFrom.x, lnTo.x, lnPre.x, lnPost.x, weight, bT, preAT, postBT), + Mathf.CubicInterpolateInTime(lnFrom.y, lnTo.y, lnPre.y, lnPost.y, weight, bT, preAT, postBT), + Mathf.CubicInterpolateInTime(lnFrom.z, lnTo.z, lnPre.z, lnPost.z, weight, bT, preAT, postBT), + 0); + Quaternion q1 = fromQ * ln.Exp(); + + // Calc by Expmap in toQ space. + lnFrom = (toQ.Inverse() * fromQ).Log(); + lnTo = new Quaternion(0, 0, 0, 0); + lnPre = (toQ.Inverse() * preQ).Log(); + lnPost = (toQ.Inverse() * postQ).Log(); + ln = new Quaternion( + Mathf.CubicInterpolateInTime(lnFrom.x, lnTo.x, lnPre.x, lnPost.x, weight, bT, preAT, postBT), + Mathf.CubicInterpolateInTime(lnFrom.y, lnTo.y, lnPre.y, lnPost.y, weight, bT, preAT, postBT), + Mathf.CubicInterpolateInTime(lnFrom.z, lnTo.z, lnPre.z, lnPost.z, weight, bT, preAT, postBT), + 0); + Quaternion q2 = toQ * ln.Exp(); + + // To cancel error made by Expmap ambiguity, do blends. + return q1.Slerp(q2, weight); } /// <summary> @@ -158,6 +274,34 @@ namespace Godot return (x * b.x) + (y * b.y) + (z * b.z) + (w * b.w); } + public Quaternion Exp() + { + Vector3 v = new Vector3(x, y, z); + real_t theta = v.Length(); + v = v.Normalized(); + if (theta < Mathf.Epsilon || !v.IsNormalized()) + { + return new Quaternion(0, 0, 0, 1); + } + return new Quaternion(v, theta); + } + + public real_t GetAngle() + { + return 2 * Mathf.Acos(w); + } + + public Vector3 GetAxis() + { + if (Mathf.Abs(w) > 1 - Mathf.Epsilon) + { + return new Vector3(x, y, z); + } + + real_t r = 1 / Mathf.Sqrt(1 - w * w); + return new Vector3(x * r, y * r, z * r); + } + /// <summary> /// Returns Euler angles (in the YXZ convention: when decomposing, /// first Z, then X, and Y last) corresponding to the rotation @@ -201,6 +345,12 @@ namespace Godot return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; } + public Quaternion Log() + { + Vector3 v = GetAxis() * GetAngle(); + return new Quaternion(v.x, v.y, v.z, 0); + } + /// <summary> /// Returns a copy of the quaternion, normalized to unit length. /// </summary> @@ -233,7 +383,7 @@ namespace Godot #endif // Calculate cosine. - real_t cosom = x * to.x + y * to.y + z * to.z + w * to.w; + real_t cosom = Dot(to); var to1 = new Quaternion(); @@ -241,17 +391,11 @@ namespace Godot if (cosom < 0.0) { cosom = -cosom; - to1.x = -to.x; - to1.y = -to.y; - to1.z = -to.z; - to1.w = -to.w; + to1 = -to; } else { - to1.x = to.x; - to1.y = to.y; - to1.z = to.z; - to1.w = to.w; + to1 = to; } real_t sinom, scale0, scale1; @@ -292,6 +436,17 @@ namespace Godot /// <returns>The resulting quaternion of the interpolation.</returns> public Quaternion Slerpni(Quaternion to, real_t weight) { +#if DEBUG + if (!IsNormalized()) + { + throw new InvalidOperationException("Quaternion is not normalized"); + } + if (!to.IsNormalized()) + { + throw new ArgumentException("Argument is not normalized", nameof(to)); + } +#endif + real_t dot = Dot(to); if (Mathf.Abs(dot) > 0.9999f) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 4b739bb86b..3c017ecc9f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -119,23 +119,9 @@ namespace Godot /// <returns>The interpolated transform.</returns> public Transform3D InterpolateWith(Transform3D transform, real_t weight) { - /* not sure if very "efficient" but good enough? */ - - Vector3 sourceScale = basis.Scale; - Quaternion sourceRotation = basis.GetRotationQuaternion(); - Vector3 sourceLocation = origin; - - Vector3 destinationScale = transform.basis.Scale; - Quaternion destinationRotation = transform.basis.GetRotationQuaternion(); - Vector3 destinationLocation = transform.origin; - - var interpolated = new Transform3D(); - Quaternion quaternion = sourceRotation.Slerp(destinationRotation, weight).Normalized(); - Vector3 scale = sourceScale.Lerp(destinationScale, weight); - interpolated.basis.SetQuaternionScale(quaternion, scale); - interpolated.origin = sourceLocation.Lerp(destinationLocation, weight); - - return interpolated; + Basis retBasis = basis.Lerp(transform.basis, weight); + Vector3 retOrigin = origin.Lerp(transform.origin, weight); + return new Transform3D(retBasis, retOrigin); } /// <summary> @@ -234,6 +220,34 @@ namespace Godot return new Transform3D(basis * tmpBasis, origin); } + /// <summary> + /// Returns a transform spherically interpolated between this transform and + /// another <paramref name="transform"/> by <paramref name="weight"/>. + /// </summary> + /// <param name="transform">The other transform.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The interpolated transform.</returns> + public Transform3D SphericalInterpolateWith(Transform3D transform, real_t weight) + { + /* not sure if very "efficient" but good enough? */ + + Vector3 sourceScale = basis.Scale; + Quaternion sourceRotation = basis.GetRotationQuaternion(); + Vector3 sourceLocation = origin; + + Vector3 destinationScale = transform.basis.Scale; + Quaternion destinationRotation = transform.basis.GetRotationQuaternion(); + Vector3 destinationLocation = transform.origin; + + var interpolated = new Transform3D(); + Quaternion quaternion = sourceRotation.Slerp(destinationRotation, weight).Normalized(); + Vector3 scale = sourceScale.Lerp(destinationScale, weight); + interpolated.basis.SetQuaternionScale(quaternion, scale); + interpolated.origin = sourceLocation.Lerp(destinationLocation, weight); + + return interpolated; + } + private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) { // Make rotation matrix diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 03ee12884b..b2964db8cd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -216,6 +216,29 @@ namespace Godot } /// <summary> + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. + /// It can perform smoother interpolation than <see cref="CubicInterpolate"/> + /// by the time values. + /// </summary> + /// <param name="b">The destination vector.</param> + /// <param name="preA">A vector before this vector.</param> + /// <param name="postB">A vector after <paramref name="b"/>.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="t"></param> + /// <param name="preAT"></param> + /// <param name="postBT"></param> + /// <returns>The interpolated vector.</returns> + public Vector2 CubicInterpolateInTime(Vector2 b, Vector2 preA, Vector2 postB, real_t weight, real_t t, real_t preAT, real_t postBT) + { + return new Vector2 + ( + Mathf.CubicInterpolateInTime(x, b.x, preA.x, postB.x, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(y, b.y, preA.y, postB.y, weight, t, preAT, postBT) + ); + } + + /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by this vector /// and the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index cdba06c089..b53ca5e45a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -209,6 +209,30 @@ namespace Godot } /// <summary> + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. + /// It can perform smoother interpolation than <see cref="CubicInterpolate"/> + /// by the time values. + /// </summary> + /// <param name="b">The destination vector.</param> + /// <param name="preA">A vector before this vector.</param> + /// <param name="postB">A vector after <paramref name="b"/>.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="t"></param> + /// <param name="preAT"></param> + /// <param name="postBT"></param> + /// <returns>The interpolated vector.</returns> + public Vector3 CubicInterpolateInTime(Vector3 b, Vector3 preA, Vector3 postB, real_t weight, real_t t, real_t preAT, real_t postBT) + { + return new Vector3 + ( + Mathf.CubicInterpolateInTime(x, b.x, preA.x, postB.x, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(y, b.y, preA.y, postB.y, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(z, b.z, preA.z, postB.z, weight, t, preAT, postBT) + ); + } + + /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by this vector /// and the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 705da04692..b6f243dfb4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -193,6 +193,31 @@ namespace Godot } /// <summary> + /// Performs a cubic interpolation between vectors <paramref name="preA"/>, this vector, + /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>. + /// It can perform smoother interpolation than <see cref="CubicInterpolate"/> + /// by the time values. + /// </summary> + /// <param name="b">The destination vector.</param> + /// <param name="preA">A vector before this vector.</param> + /// <param name="postB">A vector after <paramref name="b"/>.</param> + /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <param name="t"></param> + /// <param name="preAT"></param> + /// <param name="postBT"></param> + /// <returns>The interpolated vector.</returns> + public Vector4 CubicInterpolateInTime(Vector4 b, Vector4 preA, Vector4 postB, real_t weight, real_t t, real_t preAT, real_t postBT) + { + return new Vector4 + ( + Mathf.CubicInterpolateInTime(x, b.x, preA.x, postB.x, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(y, b.y, preA.y, postB.y, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(y, b.z, preA.z, postB.z, weight, t, preAT, postBT), + Mathf.CubicInterpolateInTime(w, b.w, preA.w, postB.w, weight, t, preAT, postBT) + ); + } + + /// <summary> /// Returns the normalized vector pointing from this vector to <paramref name="to"/>. /// </summary> /// <param name="to">The other vector to point towards.</param> diff --git a/modules/raycast/config.py b/modules/raycast/config.py index 438779343e..833ad50018 100644 --- a/modules/raycast/config.py +++ b/modules/raycast/config.py @@ -1,5 +1,8 @@ def can_build(env, platform): # Depends on Embree library, which only supports x86_64 and arm64. + if platform == "windows": + return env["arch"] == "x86_64" # TODO build for Windows on ARM + return env["arch"] in ["x86_64", "arm64"] diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 197d31dc81..a74ca155a4 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -519,8 +519,6 @@ String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const } void OS_LinuxBSD::run() { - force_quit = false; - if (!main_loop) { return; } @@ -532,7 +530,7 @@ void OS_LinuxBSD::run() { //int frames=0; //uint64_t frame=0; - while (!force_quit) { + while (true) { DisplayServer::get_singleton()->process_events(); // get rid of pending events #ifdef JOYDEV_ENABLED joypad->process_joypads(); @@ -730,7 +728,6 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { OS_LinuxBSD::OS_LinuxBSD() { main_loop = nullptr; - force_quit = false; #ifdef PULSEAUDIO_ENABLED AudioDriverManager::add_driver(&driver_pulseaudio); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index cc4e91e885..d5b2321316 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -43,8 +43,6 @@ class OS_LinuxBSD : public OS_Unix { virtual void delete_main_loop() override; - bool force_quit; - #ifdef FONTCONFIG_ENABLED bool font_config_initialized = false; #endif diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index a1eb0f7f69..61db99689c 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -40,8 +40,6 @@ #include "servers/audio_server.h" class OS_MacOS : public OS_Unix { - bool force_quit = false; - JoypadMacOS *joypad_macos = nullptr; #ifdef COREAUDIO_ENABLED diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index cc550043de..47b53bba69 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -512,8 +512,6 @@ Error OS_MacOS::move_to_trash(const String &p_path) { } void OS_MacOS::run() { - force_quit = false; - if (!main_loop) { return; } @@ -521,7 +519,7 @@ void OS_MacOS::run() { main_loop->initialize(); bool quit = false; - while (!force_quit && !quit) { + while (!quit) { @try { if (DisplayServer::get_singleton()) { DisplayServer::get_singleton()->process_events(); // Get rid of pending events. @@ -541,7 +539,6 @@ void OS_MacOS::run() { OS_MacOS::OS_MacOS() { main_loop = nullptr; - force_quit = false; Vector<Logger *> loggers; loggers.push_back(memnew(MacOSTerminalLogger)); diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 40f93bb18b..494f5ec4b9 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -781,7 +781,7 @@ void OS_UWP::run() { int frames = 0; uint64_t frame = 0; - while (!force_quit) { + while (true) { CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); if (managed_object->alert_close_handle) { continue; @@ -811,7 +811,6 @@ bool OS_UWP::_check_internal_feature_support(const String &p_feature) { OS_UWP::OS_UWP() { key_event_pos = 0; - force_quit = false; alt_mem = false; gr_mem = false; shift_mem = false; diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index fe61f60548..5a58486ee7 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -106,7 +106,6 @@ private: bool shift_mem; bool control_mem; bool meta_mem; - bool force_quit; MouseButton last_button_state = MouseButton::NONE; CursorShape cursor_shape; diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 6ce10e6f0f..b501ee78db 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -173,10 +173,18 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; -#ifdef _M_X64 +#if defined(_M_X64) frame.AddrPC.Offset = context->Rip; frame.AddrStack.Offset = context->Rsp; frame.AddrFrame.Offset = context->Rbp; +#elif defined(_M_ARM64) || defined(_M_ARM64EC) + frame.AddrPC.Offset = context->Pc; + frame.AddrStack.Offset = context->Sp; + frame.AddrFrame.Offset = context->Fp; +#elif defined(_M_ARM) + frame.AddrPC.Offset = context->Pc; + frame.AddrStack.Offset = context->Sp; + frame.AddrFrame.Offset = context->R11; #else frame.AddrPC.Offset = context->Eip; frame.AddrStack.Offset = context->Esp; diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 6dd6892757..5607eab342 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -1,5 +1,7 @@ import methods import os +import subprocess +import sys from platform_methods import detect_arch # To match other platforms @@ -14,6 +16,38 @@ def get_name(): return "Windows" +def try_cmd(test, prefix, arch): + if arch: + try: + out = subprocess.Popen( + get_mingw_bin_prefix(prefix, arch) + test, + shell=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + out.communicate() + if out.returncode == 0: + return True + except Exception: + pass + else: + for a in ["x86_64", "x86_32", "arm64", "arm32"]: + try: + out = subprocess.Popen( + get_mingw_bin_prefix(prefix, a) + test, + shell=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + out.communicate() + if out.returncode == 0: + return True + except Exception: + pass + + return False + + def can_build(): if os.name == "nt": # Building natively on Windows @@ -21,7 +55,7 @@ def can_build(): if os.getenv("VCINSTALLDIR"): # MSVC, manual setup return True - # Otherwise, let SCons find MSVC if installed, or else Mingw. + # Otherwise, let SCons find MSVC if installed, or else MinGW. # Since we're just returning True here, if there's no compiler # installed, we'll get errors when it tries to build with the # null compiler. @@ -29,47 +63,132 @@ def can_build(): if os.name == "posix": # Cross-compiling with MinGW-w64 (old MinGW32 is not supported) - mingw32 = "i686-w64-mingw32-" - mingw64 = "x86_64-w64-mingw32-" + prefix = os.getenv("MINGW_PREFIX", "") - if os.getenv("MINGW32_PREFIX"): - mingw32 = os.getenv("MINGW32_PREFIX") - if os.getenv("MINGW64_PREFIX"): - mingw64 = os.getenv("MINGW64_PREFIX") - - test = "gcc --version > /dev/null 2>&1" - if os.system(mingw64 + test) == 0 or os.system(mingw32 + test) == 0: + if try_cmd("gcc --version", prefix, "") or try_cmd("clang --version", prefix, ""): return True return False +def get_mingw_bin_prefix(prefix, arch): + if not prefix: + mingw_bin_prefix = "" + elif prefix[-1] != "/": + mingw_bin_prefix = prefix + "/bin/" + else: + mingw_bin_prefix = prefix + "bin/" + + if arch == "x86_64": + mingw_bin_prefix += "x86_64-w64-mingw32-" + elif arch == "x86_32": + mingw_bin_prefix += "i686-w64-mingw32-" + elif arch == "arm32": + mingw_bin_prefix += "armv7-w64-mingw32-" + elif arch == "arm64": + mingw_bin_prefix += "aarch64-w64-mingw32-" + + return mingw_bin_prefix + + +def detect_build_env_arch(): + msvc_target_aliases = { + "amd64": "x86_64", + "i386": "x86_32", + "i486": "x86_32", + "i586": "x86_32", + "i686": "x86_32", + "x86": "x86_32", + "x64": "x86_64", + "x86_64": "x86_64", + "arm": "arm32", + "arm64": "arm64", + "aarch64": "arm64", + } + if os.getenv("VCINSTALLDIR") or os.getenv("VCTOOLSINSTALLDIR"): + if os.getenv("Platform"): + msvc_arch = os.getenv("Platform").lower() + if msvc_arch in msvc_target_aliases.keys(): + return msvc_target_aliases[msvc_arch] + + if os.getenv("VSCMD_ARG_TGT_ARCH"): + msvc_arch = os.getenv("VSCMD_ARG_TGT_ARCH").lower() + if msvc_arch in msvc_target_aliases.keys(): + return msvc_target_aliases[msvc_arch] + + # Pre VS 2017 checks. + if os.getenv("VCINSTALLDIR"): + PATH = os.getenv("PATH").upper() + VCINSTALLDIR = os.getenv("VCINSTALLDIR").upper() + path_arch = { + "BIN\\x86_ARM;": "arm32", + "BIN\\amd64_ARM;": "arm32", + "BIN\\x86_ARM64;": "arm64", + "BIN\\amd64_ARM64;": "arm64", + "BIN\\x86_amd64;": "a86_64", + "BIN\\amd64;": "x86_64", + "BIN\\amd64_x86;": "x86_32", + "BIN;": "x86_32", + } + for path, arch in path_arch.items(): + final_path = VCINSTALLDIR + path + if final_path in PATH: + return arch + + # VS 2017 and newer. + if os.getenv("VCTOOLSINSTALLDIR"): + host_path_index = os.getenv("PATH").upper().find(os.getenv("VCTOOLSINSTALLDIR").upper() + "BIN\\HOST") + if host_path_index > -1: + first_path_arch = os.getenv("PATH").split(";")[0].rsplit("\\", 1)[-1].lower() + return msvc_target_aliases[first_path_arch] + + msys_target_aliases = { + "mingw32": "x86_32", + "mingw64": "x86_64", + "ucrt64": "x86_64", + "clang64": "x86_64", + "clang32": "x86_32", + "clangarm64": "arm64", + } + if os.getenv("MSYSTEM"): + msys_arch = os.getenv("MSYSTEM").lower() + if msys_arch in msys_target_aliases.keys(): + return msys_target_aliases[msys_arch] + + return "" + + def get_opts(): from SCons.Variables import BoolVariable, EnumVariable - # TODO: These shouldn't be hard-coded for x86. - mingw32 = "" - mingw64 = "" - if os.name == "posix": - mingw32 = "i686-w64-mingw32-" - mingw64 = "x86_64-w64-mingw32-" - - if os.getenv("MINGW32_PREFIX"): - mingw32 = os.getenv("MINGW32_PREFIX") - if os.getenv("MINGW64_PREFIX"): - mingw64 = os.getenv("MINGW64_PREFIX") + mingw = os.getenv("MINGW_PREFIX", "") return [ - ("mingw_prefix_32", "MinGW prefix (Win32)", mingw32), - ("mingw_prefix_64", "MinGW prefix (Win64)", mingw64), + ("mingw_prefix", "MinGW prefix", mingw), # Targeted Windows version: 7 (and later), minimum supported version # XP support dropped after EOL due to missing API for IPv6 and other issues # Vista support dropped after EOL due to GH-10243 - ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"), - BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), + ( + "target_win_version", + "Targeted Windows version, >= 0x0601 (Windows 7)", + "0x0601", + ), + BoolVariable( + "debug_symbols", + "Add debugging symbols to release/release_debug builds", + True, + ), EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("gui", "console")), - BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), - ("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None), + BoolVariable( + "separate_debug_symbols", + "Create a separate file containing debugging symbols", + False, + ), + ( + "msvc_version", + "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", + None, + ), BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed.", False), BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_thinlto", "Use ThinLTO", False), @@ -79,73 +198,63 @@ def get_opts(): def get_flags(): + arch = detect_build_env_arch() or detect_arch() + return [ - ("arch", detect_arch()), + ("arch", arch), ] def build_res_file(target, source, env): - # TODO: This shouldn't be hard-coded for x86. - if env["arch"] == "x86_32": - cmdbase = env["mingw_prefix_32"] - else: - cmdbase = env["mingw_prefix_64"] - cmdbase = cmdbase + "windres --include-dir . " - import subprocess + arch_aliases = { + "x86_32": "pe-i386", + "x86_64": "pe-x86-64", + "arm32": "armv7-w64-mingw32", + "arm64": "aarch64-w64-mingw32", + } + cmdbase = "windres --include-dir . --target=" + arch_aliases[env["arch"]] + + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) for x in range(len(source)): - cmd = cmdbase + "-i " + str(source[x]) + " -o " + str(target[x]) + ok = True + # Try prefixed executable (MinGW on Linux). + cmd = mingw_bin_prefix + cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): - return 1 + ok = False except Exception: - return 1 + ok = False + + # Try generic executable (MSYS2). + if not ok: + cmd = cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) + try: + out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() + if len(out[1]): + return -1 + except Exception: + return -1 + return 0 def setup_msvc_manual(env): - # FIXME: This is super hacky, and probably obsolete. - # Won't work with detect_arch() used for `arch` by default. + """Running from VCVARS environment""" - """Set up env to use MSVC manually, using VCINSTALLDIR""" - if env["arch"] != "auto": + env_arch = detect_build_env_arch() + if env["arch"] != env_arch: print( """ - Arch argument is not supported for MSVC manual configuration (VCINSTALLDIR configured). - Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console (or Visual Studio settings) that is being used to run SCons. - As a consequence, the arch argument is disabled. Run scons again without arch argument (example: scons p=windows) - and SCons will attempt to detect what MSVC compiler will be executed and inform you. + Arch argument (%s) is not matching Native/Cross Compile Tools Prompt/Developer Console (or Visual Studio settings) that is being used to run SCons (%s). + Run SCons again without arch argument (example: scons p=windows) and SCons will attempt to detect what MSVC compiler will be executed and inform you. """ + % (env["arch"], env_arch) ) - raise SCons.Errors.UserError("Arch argument should not be used when using VCINSTALLDIR") - - # Force ARCH arg - # (Actually msys2 mingw can support 64-bit, we could detect that) - # TODO: This is wrong, but not sure what to do about it. - # We want to determine the arch and bitness in the SConstruct only. - # We can check if it's correct in here, but if it's not, it should - # just fail with an error message instead of trying to force it. - - # find compiler manually - compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"]) - print("Found MSVC compiler: " + compiler_version_str) - - # If building for 64bit architecture, disable assembly optimisations for 32 bit builds (theora as of writing)... vc compiler for 64bit can not compile _asm - if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64": - env["arch"] = "x86_64" - env["x86_libtheora_opt_vc"] = False - print("Compiled program architecture will be a 64 bit executable (forcing arch=x86_64).") - elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86": - env["arch"] = "x86_32" - env["x86_libtheora_opt_vc"] = True - print("Compiled program architecture will be a 32 bit executable (forcing arch=x86_32).") - else: - print( - "Failed to manually detect MSVC compiler architecture version.\n" - "You should check your settings/compilation setup, or avoid setting VCINSTALLDIR." - ) - sys.exit() + sys.exit(200) + + print("Found MSVC, arch %s" % (env_arch)) def setup_msvc_auto(env): @@ -179,23 +288,50 @@ def setup_msvc_auto(env): env["MSVC_VERSION"] = env["msvc_version"] env.Tool("msvc") env.Tool("mssdk") # we want the MS SDK + # Note: actual compiler version can be found in env['MSVC_VERSION'], e.g. "14.1" for VS2015 - print("Found MSVC version %s, arch %s" % (env["MSVC_VERSION"], env["TARGET_ARCH"])) - if env["arch"] == "x86_32": - env["x86_libtheora_opt_vc"] = False + print("Found MSVC version %s, arch %s" % (env["MSVC_VERSION"], env["arch"])) def setup_mingw(env): """Set up env for use with mingw""" - # Nothing to do here - print("Using MinGW") + + env_arch = detect_build_env_arch() + if os.getenv("MSYSTEM") == "MSYS": + print( + """ + Running from base MSYS2 console/environment, use target specific environment instead (e.g., mingw32, mingw64, clang32, clang64). + """ + ) + sys.exit(201) + + if env_arch != "" and env["arch"] != env_arch: + print( + """ + Arch argument (%s) is not matching MSYS2 console/environment that is being used to run SCons (%s). + Run SCons again without arch argument (example: scons p=windows) and SCons will attempt to detect what MSYS2 compiler will be executed and inform you. + """ + % (env["arch"], env_arch) + ) + sys.exit(202) + + if not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]) and not try_cmd( + "clang --version", env["mingw_prefix"], env["arch"] + ): + print( + """ + No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path. + """ + ) + sys.exit(202) + + print("Using MinGW, arch %s" % (env["arch"])) -def configure_msvc(env, manual_msvc_config): +def configure_msvc(env, vcvars_msvc_config): """Configure env to work with MSVC""" # Build type - if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) @@ -235,6 +371,9 @@ def configure_msvc(env, manual_msvc_config): else: env.AppendUnique(CCFLAGS=["/MD"]) + if env["arch"] == "x86_32": + env["x86_libtheora_opt_vc"] = True + env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"]) env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++ @@ -243,7 +382,7 @@ def configure_msvc(env, manual_msvc_config): # for notes on why this shouldn't be enabled for gcc env.AppendUnique(CCFLAGS=["/bigobj"]) - if manual_msvc_config: # should be automatic if SCons found it + if vcvars_msvc_config: # should be automatic if SCons found it if os.getenv("WindowsSdkDir") is not None: env.Prepend(CPPPATH=[os.getenv("WindowsSdkDir") + "/Include"]) else: @@ -302,7 +441,7 @@ def configure_msvc(env, manual_msvc_config): env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) - if manual_msvc_config: + if vcvars_msvc_config: if os.getenv("WindowsSdkDir") is not None: env.Append(LIBPATH=[os.getenv("WindowsSdkDir") + "/Lib"]) else: @@ -318,7 +457,7 @@ def configure_msvc(env, manual_msvc_config): else: env.AppendUnique(LINKFLAGS=["/LTCG"]) - if manual_msvc_config: + if vcvars_msvc_config: env.Prepend(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")]) env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")]) @@ -342,6 +481,12 @@ def configure_mingw(env): ## Build type + if not env["use_llvm"] and not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]): + env["use_llvm"] = True + + if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]): + env["use_llvm"] = False + if env["target"] == "release": env.Append(CCFLAGS=["-msse2"]) @@ -383,36 +528,39 @@ def configure_mingw(env): if os.name != "nt": env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".exe" # for linux cross-compilation - mingw_prefix = "" - - # TODO: This doesn't seem to be working, or maybe I just have - # MinGW set up incorrectly. It always gives me x86_64 builds, - # even though arch == "x86_32" and the file name has x86_32 in it. if env["arch"] == "x86_32": if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static"]) env.Append(LINKFLAGS=["-static-libgcc"]) env.Append(LINKFLAGS=["-static-libstdc++"]) - mingw_prefix = env["mingw_prefix_32"] else: if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static"]) - mingw_prefix = env["mingw_prefix_64"] + + if env["arch"] in ["x86_32", "x86_64"]: + env["x86_libtheora_opt_gcc"] = True + + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) if env["use_llvm"]: - env["CC"] = mingw_prefix + "clang" - env["CXX"] = mingw_prefix + "clang++" - env["AS"] = mingw_prefix + "as" - env["AR"] = mingw_prefix + "ar" - env["RANLIB"] = mingw_prefix + "ranlib" + env["CC"] = mingw_bin_prefix + "clang" + env["CXX"] = mingw_bin_prefix + "clang++" + if try_cmd("as --version", env["mingw_prefix"], env["arch"]): + env["AS"] = mingw_bin_prefix + "as" + if try_cmd("ar --version", env["mingw_prefix"], env["arch"]): + env["AR"] = mingw_bin_prefix + "ar" + if try_cmd("ranlib --version", env["mingw_prefix"], env["arch"]): + env["RANLIB"] = mingw_bin_prefix + "ranlib" + env.extra_suffix = ".llvm" + env.extra_suffix else: - env["CC"] = mingw_prefix + "gcc" - env["CXX"] = mingw_prefix + "g++" - env["AS"] = mingw_prefix + "as" - env["AR"] = mingw_prefix + "gcc-ar" - env["RANLIB"] = mingw_prefix + "gcc-ranlib" - - env["x86_libtheora_opt_gcc"] = True + env["CC"] = mingw_bin_prefix + "gcc" + env["CXX"] = mingw_bin_prefix + "g++" + if try_cmd("as --version", env["mingw_prefix"], env["arch"]): + env["AS"] = mingw_bin_prefix + "as" + if try_cmd("gcc-ar --version", env["mingw_prefix"], env["arch"]): + env["AR"] = mingw_bin_prefix + "gcc-ar" + if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]): + env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib" if env["use_lto"]: if not env["use_llvm"] and env.GetOption("num_jobs") > 1: @@ -430,10 +578,16 @@ def configure_mingw(env): ## Compile flags - env.Append(CCFLAGS=["-mwindows"]) + if not env["use_llvm"]: + env.Append(CCFLAGS=["-mwindows"]) env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"]) - env.Append(CPPDEFINES=[("WINVER", env["target_win_version"]), ("_WIN32_WINNT", env["target_win_version"])]) + env.Append( + CPPDEFINES=[ + ("WINVER", env["target_win_version"]), + ("_WIN32_WINNT", env["target_win_version"]), + ] + ) env.Append( LIBS=[ "mingw32", @@ -492,22 +646,21 @@ def configure(env): env["ENV"]["TMP"] = os.environ["TMP"] # First figure out which compiler, version, and target arch we're using - if os.getenv("VCINSTALLDIR") and not env["use_mingw"]: - # Manual setup of MSVC + if os.getenv("VCINSTALLDIR") and detect_build_env_arch() and not env["use_mingw"]: setup_msvc_manual(env) env.msvc = True - manual_msvc_config = True + vcvars_msvc_config = True elif env.get("MSVC_VERSION", "") and not env["use_mingw"]: setup_msvc_auto(env) env.msvc = True - manual_msvc_config = False + vcvars_msvc_config = False else: setup_mingw(env) env.msvc = False # Now set compiler/linker flags if env.msvc: - configure_msvc(env, manual_msvc_config) + configure_msvc(env, vcvars_msvc_config) else: # MinGW configure_mingw(env) diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index cb0bd44cfb..016d201f2c 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -123,7 +123,7 @@ bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExpor void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) { EditorExportPlatformPC::get_export_options(r_options); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32"), "x86_64")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64"), "x86_64")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0)); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index ad4be950cc..0e4d5f79b9 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -902,7 +902,7 @@ void OS_Windows::run() { main_loop->initialize(); - while (!force_quit) { + while (true) { DisplayServer::get_singleton()->process_events(); // get rid of pending events if (Main::iteration()) { break; @@ -1132,8 +1132,6 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) { main_loop = nullptr; process_map = nullptr; - force_quit = false; - hInstance = _hInstance; #ifdef STDOUT_FILE stdo = fopen("stdout.txt", "wb"); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 80fc860738..3e054c068c 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -110,7 +110,6 @@ class OS_Windows : public OS { ErrorHandlerList error_handlers; #endif - bool force_quit; HWND main_window; // functions used by main to initialize/deinitialize the OS diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index ee552e695e..d06324f0aa 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -602,6 +602,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_value->object = child; } + track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; + track_value->subpath = leftover_path; track_value->object_id = track_value->object->get_instance_id(); @@ -804,6 +806,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { default: { } } + } else if (track_cache_type == Animation::TYPE_VALUE) { + // If it has at least one angle interpolation, it also uses angle interpolation for blending. + TrackCacheValue *track_value = memnew(TrackCacheValue); + track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; } track->setup_pass = setup_pass; @@ -1353,8 +1359,28 @@ void AnimationTree::_process_graph(double p_delta) { t->value = t->init_value; } - Variant::sub(value, t->init_value, value); - Variant::blend(t->value, value, blend, t->value); + // Special case for angle interpolation. + if (t->is_using_angle) { + // For blending consistency, it prevents rotation of more than 180 degrees from init_value. + // This is the same as for Quaternion blends. + float rot_a = t->value; + float rot_b = value; + float rot_init = t->init_value; + rot_a = Math::fposmod(rot_a, (float)Math_TAU); + rot_b = Math::fposmod(rot_b, (float)Math_TAU); + rot_init = Math::fposmod(rot_init, (float)Math_TAU); + if (rot_init < Math_PI) { + rot_a = rot_a > rot_init + Math_PI ? rot_a - Math_TAU : rot_a; + rot_b = rot_b > rot_init + Math_PI ? rot_b - Math_TAU : rot_b; + } else { + rot_a = rot_a < rot_init - Math_PI ? rot_a + Math_TAU : rot_a; + rot_b = rot_b < rot_init - Math_PI ? rot_b + Math_TAU : rot_b; + } + t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU); + } else { + Variant::sub(value, t->init_value, value); + Variant::blend(t->value, value, blend, t->value); + } } else { if (blend < CMP_EPSILON) { continue; //nothing to blend diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index ee51a54557..ee0c0303dc 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -233,6 +233,7 @@ private: Variant init_value; Variant value; Vector<StringName> subpath; + bool is_using_angle = false; TrackCacheValue() { type = Animation::TYPE_VALUE; } }; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index add9b8b172..256d4b3d11 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -606,13 +606,16 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = Object::cast_to<GraphNode>(to)->get_connection_input_color(E.to_port); connecting_target = false; connecting_to = pos; - just_disconnected = true; - emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); - to = get_node(String(connecting_from)); //maybe it was erased - if (Object::cast_to<GraphNode>(to)) { - connecting = true; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + if (connecting_type >= 0) { + just_disconnected = true; + + emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); + to = get_node(String(connecting_from)); //maybe it was erased + if (Object::cast_to<GraphNode>(to)) { + connecting = true; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + } } return; } @@ -620,7 +623,6 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting = true; connecting_from = gn->get_name(); connecting_index = j; connecting_out = true; @@ -628,8 +630,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = gn->get_connection_output_color(j); connecting_target = false; connecting_to = pos; - just_disconnected = false; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + if (connecting_type >= 0) { + connecting = true; + just_disconnected = false; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + } return; } } @@ -656,11 +661,13 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_to = pos; just_disconnected = true; - emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); - fr = get_node(String(connecting_from)); //maybe it was erased - if (Object::cast_to<GraphNode>(fr)) { - connecting = true; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + if (connecting_type >= 0) { + emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); + fr = get_node(String(connecting_from)); //maybe it was erased + if (Object::cast_to<GraphNode>(fr)) { + connecting = true; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + } } return; } @@ -668,7 +675,6 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting = true; connecting_from = gn->get_name(); connecting_index = j; connecting_out = false; @@ -676,8 +682,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = gn->get_connection_input_color(j); connecting_target = false; connecting_to = pos; - just_disconnected = false; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + if (connecting_type >= 0) { + connecting = true; + just_disconnected = false; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + } return; } } @@ -1126,7 +1135,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { drag_accum += mm->get_relative(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (gn && gn->is_selected()) { + if (gn && gn->is_selected() && gn->is_draggable()) { Vector2 pos = (gn->get_drag_from() * zoom + drag_accum) / zoom; // Snapping can be toggled temporarily by holding down Ctrl. @@ -1165,7 +1174,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } else if (gn->is_selected() && !box_selection_mode_additive) { emit_signal(SNAME("node_deselected"), gn); } - gn->set_selected(box_selection_mode_additive); + if (gn->is_selectable()) { + gn->set_selected(box_selection_mode_additive); + } } else { bool select = (previous_selected.find(gn) != nullptr); if (gn->is_selected() && !select) { @@ -1173,7 +1184,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } else if (!gn->is_selected() && select) { emit_signal(SNAME("node_selected"), gn); } - gn->set_selected(select); + if (gn->is_selectable()) { + gn->set_selected(select); + } } } @@ -1192,7 +1205,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { continue; } - bool select = (previous_selected.find(gn) != nullptr); + bool select = (gn->is_selectable() && previous_selected.find(gn) != nullptr); if (gn->is_selected() && !select) { emit_signal(SNAME("node_deselected"), gn); } else if (!gn->is_selected() && select) { @@ -1284,7 +1297,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); if (o_gn) { if (o_gn == gn) { - o_gn->set_selected(true); + o_gn->set_selected(o_gn->is_selectable()); } else { if (o_gn->is_selected()) { emit_signal(SNAME("node_deselected"), o_gn); @@ -1295,7 +1308,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } - gn->set_selected(true); + gn->set_selected(gn->is_selectable()); for (int i = 0; i < get_child_count(); i++) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); if (!o_gn) { @@ -1710,6 +1723,19 @@ bool GraphEdit::is_minimap_enabled() const { return minimap_button->is_pressed(); } +void GraphEdit::set_arrange_nodes_button_hidden(bool p_enable) { + arrange_nodes_button_hidden = p_enable; + if (arrange_nodes_button_hidden) { + layout_button->hide(); + } else { + layout_button->show(); + } +} + +bool GraphEdit::is_arrange_nodes_button_hidden() const { + return arrange_nodes_button_hidden; +} + void GraphEdit::_minimap_toggled() { if (is_minimap_enabled()) { minimap->set_visible(true); @@ -2342,6 +2368,9 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_minimap_enabled", "enable"), &GraphEdit::set_minimap_enabled); ClassDB::bind_method(D_METHOD("is_minimap_enabled"), &GraphEdit::is_minimap_enabled); + ClassDB::bind_method(D_METHOD("set_arrange_nodes_button_hidden", "enable"), &GraphEdit::set_arrange_nodes_button_hidden); + ClassDB::bind_method(D_METHOD("is_arrange_nodes_button_hidden"), &GraphEdit::is_arrange_nodes_button_hidden); + ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects); ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); @@ -2381,6 +2410,9 @@ void GraphEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_size", "get_minimap_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity"); + ADD_GROUP("UI", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arrange_nodes_button_hidden"), "set_arrange_nodes_button_hidden", "is_arrange_nodes_button_hidden"); + ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position"))); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index b8c9be9983..0a0676699f 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -133,6 +133,8 @@ private: void _pan_callback(Vector2 p_scroll_vec); void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); + bool arrange_nodes_button_hidden = false; + bool connecting = false; String connecting_from; bool connecting_out = false; @@ -323,6 +325,9 @@ public: void set_minimap_enabled(bool p_enable); bool is_minimap_enabled() const; + void set_arrange_nodes_button_hidden(bool p_enable); + bool is_arrange_nodes_button_hidden() const; + GraphEditFilter *get_top_layer() const { return top_layer; } GraphEditMinimap *get_minimap() const { return minimap; } void get_connection_list(List<Connection> *r_connections) const; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 7d31e929dc..b07d401e6b 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -1003,6 +1003,22 @@ bool GraphNode::is_resizable() const { return resizable; } +void GraphNode::set_draggable(bool p_draggable) { + draggable = p_draggable; +} + +bool GraphNode::is_draggable() { + return draggable; +} + +void GraphNode::set_selectable(bool p_selectable) { + selectable = p_selectable; +} + +bool GraphNode::is_selectable() { + return selectable; +} + Vector<int> GraphNode::get_allowed_size_flags_horizontal() const { Vector<int> flags; flags.append(SIZE_FILL); @@ -1064,6 +1080,12 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_resizable", "resizable"), &GraphNode::set_resizable); ClassDB::bind_method(D_METHOD("is_resizable"), &GraphNode::is_resizable); + ClassDB::bind_method(D_METHOD("set_draggable", "draggable"), &GraphNode::set_draggable); + ClassDB::bind_method(D_METHOD("is_draggable"), &GraphNode::is_draggable); + + ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &GraphNode::set_selectable); + ClassDB::bind_method(D_METHOD("is_selectable"), &GraphNode::is_selectable); + ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected); ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected); @@ -1089,6 +1111,8 @@ void GraphNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_position_offset", "get_position_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draggable"), "set_draggable", "is_draggable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "comment"), "set_comment", "is_comment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "overlay", PROPERTY_HINT_ENUM, "Disabled,Breakpoint,Position"), "set_overlay", "get_overlay"); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index d575b6ceed..9c8f926403 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -67,6 +67,8 @@ private: Vector2 position_offset; bool comment = false; bool resizable = false; + bool draggable = true; + bool selectable = true; bool resizing = false; Vector2 resizing_from; @@ -183,6 +185,12 @@ public: void set_resizable(bool p_enable); bool is_resizable() const; + void set_draggable(bool p_draggable); + bool is_draggable(); + + void set_selectable(bool p_selectable); + bool is_selectable(); + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 980968497d..823617328e 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -2323,7 +2323,20 @@ Variant Animation::_interpolate(const Variant &p_a, const Variant &p_b, real_t p } real_t Animation::_interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const { - return p_a * (1.0 - p_c) + p_b * p_c; + return Math::lerp(p_a, p_b, p_c); +} + +Variant Animation::_interpolate_angle(const Variant &p_a, const Variant &p_b, real_t p_c) const { + Variant::Type type_a = p_a.get_type(); + Variant::Type type_b = p_b.get_type(); + uint32_t vformat = 1 << type_a; + vformat |= 1 << type_b; + if (vformat == ((1 << Variant::INT) | (1 << Variant::FLOAT)) || vformat == (1 << Variant::FLOAT)) { + real_t a = p_a; + real_t b = p_b; + return Math::fposmod((float)Math::lerp_angle(a, b, p_c), (float)Math_TAU); + } + return _interpolate(p_a, p_b, p_c); } // Cubic interpolation for anytype. @@ -2413,6 +2426,25 @@ Variant Animation::_cubic_interpolate_in_time(const Variant &p_pre_a, const Vari } real_t Animation::_cubic_interpolate_in_time(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const { + return Math::cubic_interpolate_in_time(p_a, p_b, p_pre_a, p_post_b, p_c, p_b_t, p_pre_a_t, p_post_b_t); +} + +Variant Animation::_cubic_interpolate_angle_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const { + Variant::Type type_a = p_a.get_type(); + Variant::Type type_b = p_b.get_type(); + Variant::Type type_pa = p_pre_a.get_type(); + Variant::Type type_pb = p_post_b.get_type(); + uint32_t vformat = 1 << type_a; + vformat |= 1 << type_b; + vformat |= 1 << type_pa; + vformat |= 1 << type_pb; + if (vformat == ((1 << Variant::INT) | (1 << Variant::FLOAT)) || vformat == (1 << Variant::FLOAT)) { + real_t a = p_a; + real_t b = p_b; + real_t pa = p_pre_a; + real_t pb = p_post_b; + return Math::fposmod((float)Math::cubic_interpolate_angle_in_time(a, b, pa, pb, p_c, p_b_t, p_pre_a_t, p_post_b_t), (float)Math_TAU); + } return _interpolate(p_a, p_b, p_c); } @@ -2595,7 +2627,11 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol case INTERPOLATION_LINEAR: { return _interpolate(p_keys[idx].value, p_keys[next].value, c); } break; - case INTERPOLATION_CUBIC: { + case INTERPOLATION_LINEAR_ANGLE: { + return _interpolate_angle(p_keys[idx].value, p_keys[next].value, c); + } break; + case INTERPOLATION_CUBIC: + case INTERPOLATION_CUBIC_ANGLE: { int pre = 0; int post = 0; if (!p_backward) { @@ -2634,19 +2670,27 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol } } + real_t pre_t = 0.0; + real_t to_t = 0.0; + real_t post_t = 0.0; if (loop_mode == LOOP_LINEAR && p_loop_wrap) { - return _cubic_interpolate_in_time( - p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c, - pre > idx ? -length + p_keys[pre].time - p_keys[idx].time : p_keys[pre].time - p_keys[idx].time, - next < idx ? length + p_keys[next].time - p_keys[idx].time : p_keys[next].time - p_keys[idx].time, - next < idx || post <= idx ? length + p_keys[post].time - p_keys[idx].time : p_keys[post].time - p_keys[idx].time); + pre_t = pre > idx ? -length + p_keys[pre].time - p_keys[idx].time : p_keys[pre].time - p_keys[idx].time; + to_t = next < idx ? length + p_keys[next].time - p_keys[idx].time : p_keys[next].time - p_keys[idx].time; + post_t = next < idx || post <= idx ? length + p_keys[post].time - p_keys[idx].time : p_keys[post].time - p_keys[idx].time; + } else { + pre_t = p_keys[pre].time - p_keys[idx].time; + to_t = p_keys[next].time - p_keys[idx].time; + post_t = p_keys[post].time - p_keys[idx].time; } + if (p_interp == INTERPOLATION_CUBIC_ANGLE) { + return _cubic_interpolate_angle_in_time( + p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c, + pre_t, to_t, post_t); + } return _cubic_interpolate_in_time( p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c, - p_keys[pre].time - p_keys[idx].time, - p_keys[next].time - p_keys[idx].time, - p_keys[post].time - p_keys[idx].time); + pre_t, to_t, post_t); } break; default: return p_keys[idx].value; @@ -3976,6 +4020,8 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST); BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR); BIND_ENUM_CONSTANT(INTERPOLATION_CUBIC); + BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR_ANGLE); + BIND_ENUM_CONSTANT(INTERPOLATION_CUBIC_ANGLE); BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS); BIND_ENUM_CONSTANT(UPDATE_DISCRETE); @@ -4002,28 +4048,27 @@ void Animation::clear() { emit_signal(SceneStringNames::get_singleton()->tracks_changed); } -bool Animation::_vector3_track_optimize_key(const TKey<Vector3> t0, const TKey<Vector3> t1, const TKey<Vector3> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { +bool Animation::_float_track_optimize_key(const TKey<float> t0, const TKey<float> t1, const TKey<float> t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error) { // Remove overlapping keys. if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { return true; } - if ((t0.value - t1.value).length() < p_allowed_precision_error && (t1.value - t2.value).length() < p_allowed_precision_error) { + if (abs(t0.value - t1.value) < p_allowed_precision_error && abs(t1.value - t2.value) < p_allowed_precision_error) { return true; } // Calc velocities. - Vector3 vc0 = (t1.value - t0.value) / (t1.time - t0.time); - Vector3 vc1 = (t2.value - t1.value) / (t2.time - t1.time); - real_t v0 = vc0.length(); - real_t v1 = vc1.length(); + double v0 = (t1.value - t0.value) / (t1.time - t0.time); + double v1 = (t2.value - t1.value) / (t2.time - t1.time); // Avoid zero div but check equality. if (abs(v0 - v1) < p_allowed_precision_error) { return true; } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { return false; } - // Check axis. - if (vc0.normalized().dot(vc1.normalized()) >= 1.0 - p_allowed_angular_error * 2.0) { - real_t ratio = v0 < v1 ? v0 / v1 : v1 / v0; + if (!signbit(v0 * v1)) { + v0 = abs(v0); + v1 = abs(v1); + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; if (ratio >= 1.0 - p_allowed_velocity_err) { return true; } @@ -4031,7 +4076,7 @@ bool Animation::_vector3_track_optimize_key(const TKey<Vector3> t0, const TKey<V return false; } -bool Animation::_quaternion_track_optimize_key(const TKey<Quaternion> t0, const TKey<Quaternion> t1, const TKey<Quaternion> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { +bool Animation::_vector2_track_optimize_key(const TKey<Vector2> t0, const TKey<Vector2> t1, const TKey<Vector2> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { // Remove overlapping keys. if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { return true; @@ -4039,20 +4084,22 @@ bool Animation::_quaternion_track_optimize_key(const TKey<Quaternion> t0, const if ((t0.value - t1.value).length() < p_allowed_precision_error && (t1.value - t2.value).length() < p_allowed_precision_error) { return true; } + // Calc velocities. + Vector2 vc0 = (t1.value - t0.value) / (t1.time - t0.time); + Vector2 vc1 = (t2.value - t1.value) / (t2.time - t1.time); + double v0 = vc0.length(); + double v1 = vc1.length(); + // Avoid zero div but check equality. + if (abs(v0 - v1) < p_allowed_precision_error) { + return true; + } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { + return false; + } // Check axis. - Quaternion q0 = t0.value * t1.value * t0.value.inverse(); - Quaternion q1 = t1.value * t2.value * t1.value.inverse(); - if (q0.get_axis().dot(q1.get_axis()) >= 1.0 - p_allowed_angular_error * 2.0) { - // Calc velocities. - real_t v0 = Math::acos(t0.value.dot(t1.value)) / (t1.time - t0.time); - real_t v1 = Math::acos(t1.value.dot(t2.value)) / (t2.time - t1.time); - // Avoid zero div but check equality. - if (abs(v0 - v1) < p_allowed_precision_error) { - return true; - } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { - return false; - } - real_t ratio = v0 < v1 ? v0 / v1 : v1 / v0; + if (vc0.normalized().dot(vc1.normalized()) >= 1.0 - p_allowed_angular_error * 2.0) { + v0 = abs(v0); + v1 = abs(v1); + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; if (ratio >= 1.0 - p_allowed_velocity_err) { return true; } @@ -4060,25 +4107,64 @@ bool Animation::_quaternion_track_optimize_key(const TKey<Quaternion> t0, const return false; } -bool Animation::_float_track_optimize_key(const TKey<float> t0, const TKey<float> t1, const TKey<float> t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error) { +bool Animation::_vector3_track_optimize_key(const TKey<Vector3> t0, const TKey<Vector3> t1, const TKey<Vector3> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { // Remove overlapping keys. if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { return true; } - if (abs(t0.value - t1.value) < p_allowed_precision_error && abs(t1.value - t2.value) < p_allowed_precision_error) { + if ((t0.value - t1.value).length() < p_allowed_precision_error && (t1.value - t2.value).length() < p_allowed_precision_error) { return true; } // Calc velocities. - real_t v0 = (t1.value - t0.value) / (t1.time - t0.time); - real_t v1 = (t2.value - t1.value) / (t2.time - t1.time); + Vector3 vc0 = (t1.value - t0.value) / (t1.time - t0.time); + Vector3 vc1 = (t2.value - t1.value) / (t2.time - t1.time); + double v0 = vc0.length(); + double v1 = vc1.length(); // Avoid zero div but check equality. if (abs(v0 - v1) < p_allowed_precision_error) { return true; } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { return false; } - if (!signbit(v0 * v1)) { - real_t ratio = v0 < v1 ? v0 / v1 : v1 / v0; + // Check axis. + if (vc0.normalized().dot(vc1.normalized()) >= 1.0 - p_allowed_angular_error * 2.0) { + v0 = abs(v0); + v1 = abs(v1); + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; + if (ratio >= 1.0 - p_allowed_velocity_err) { + return true; + } + } + return false; +} + +bool Animation::_quaternion_track_optimize_key(const TKey<Quaternion> t0, const TKey<Quaternion> t1, const TKey<Quaternion> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { + // Remove overlapping keys. + if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { + return true; + } + if ((t0.value - t1.value).length() < p_allowed_precision_error && (t1.value - t2.value).length() < p_allowed_precision_error) { + return true; + } + // Check axis. + Quaternion q0 = t0.value * t1.value * t0.value.inverse(); + Quaternion q1 = t1.value * t2.value * t1.value.inverse(); + if (q0.get_axis().dot(q1.get_axis()) >= 1.0 - p_allowed_angular_error * 2.0) { + double a0 = Math::acos(t0.value.dot(t1.value)); + double a1 = Math::acos(t1.value.dot(t2.value)); + if (a0 + a1 >= Math_PI) { + return false; // Rotation is more than 180 deg, keep key. + } + // Calc velocities. + double v0 = a0 / (t1.time - t0.time); + double v1 = a1 / (t2.time - t1.time); + // Avoid zero div but check equality. + if (abs(v0 - v1) < p_allowed_precision_error) { + return true; + } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { + return false; + } + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; if (ratio >= 1.0 - p_allowed_velocity_err) { return true; } @@ -4190,6 +4276,125 @@ void Animation::_blend_shape_track_optimize(int p_idx, real_t p_allowed_velocity } } +void Animation::_value_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error) { + ERR_FAIL_INDEX(p_idx, tracks.size()); + ERR_FAIL_COND(tracks[p_idx]->type != TYPE_VALUE); + ValueTrack *tt = static_cast<ValueTrack *>(tracks[p_idx]); + if (tt->values.size() == 0) { + return; + } + Variant::Type type = tt->values[0].value.get_type(); + + // Special case for angle interpolation. + bool is_using_angle = tt->interpolation == Animation::INTERPOLATION_LINEAR_ANGLE || tt->interpolation == Animation::INTERPOLATION_CUBIC_ANGLE; + int i = 0; + while (i < tt->values.size() - 2) { + bool erase = false; + switch (type) { + case Variant::FLOAT: { + TKey<float> t0; + TKey<float> t1; + TKey<float> t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + if (is_using_angle) { + float diff1 = fmod(t1.value - t0.value, Math_TAU); + t1.value = t0.value + fmod(2.0 * diff1, Math_TAU) - diff1; + float diff2 = fmod(t2.value - t1.value, Math_TAU); + t2.value = t1.value + fmod(2.0 * diff2, Math_TAU) - diff2; + if (abs(abs(diff1) + abs(diff2)) >= Math_PI) { + break; // Rotation is more than 180 deg, keep key. + } + } + erase = _float_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_precision_error); + } break; + case Variant::VECTOR2: { + TKey<Vector2> t0; + TKey<Vector2> t1; + TKey<Vector2> t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + erase = _vector2_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_angular_err, p_allowed_precision_error); + } break; + case Variant::VECTOR3: { + TKey<Vector3> t0; + TKey<Vector3> t1; + TKey<Vector3> t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + erase = _vector3_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_angular_err, p_allowed_precision_error); + } break; + case Variant::QUATERNION: { + TKey<Quaternion> t0; + TKey<Quaternion> t1; + TKey<Quaternion> t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + erase = _quaternion_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_angular_err, p_allowed_precision_error); + } break; + default: { + } break; + } + + if (erase) { + tt->values.remove_at(i + 1); + } else { + i++; + } + } + + if (tt->values.size() == 2) { + bool single_key = false; + switch (type) { + case Variant::FLOAT: { + float val_0 = tt->values[0].value; + float val_1 = tt->values[1].value; + if (is_using_angle) { + float diff1 = fmod(val_1 - val_0, Math_TAU); + val_1 = val_0 + fmod(2.0 * diff1, Math_TAU) - diff1; + } + single_key = abs(val_0 - val_1) < p_allowed_precision_error; + } break; + case Variant::VECTOR2: { + Vector2 val_0 = tt->values[0].value; + Vector2 val_1 = tt->values[1].value; + single_key = (val_0 - val_1).length() < p_allowed_precision_error; + } break; + case Variant::VECTOR3: { + Vector3 val_0 = tt->values[0].value; + Vector3 val_1 = tt->values[1].value; + single_key = (val_0 - val_1).length() < p_allowed_precision_error; + } break; + case Variant::QUATERNION: { + Quaternion val_0 = tt->values[0].value; + Quaternion val_1 = tt->values[1].value; + single_key = (val_0 - val_1).length() < p_allowed_precision_error; + } break; + default: { + } break; + } + if (single_key) { + tt->values.remove_at(1); + } + } +} + void Animation::optimize(real_t p_allowed_velocity_err, real_t p_allowed_angular_err, int p_precision) { real_t precision = Math::pow(0.1, p_precision); for (int i = 0; i < tracks.size(); i++) { @@ -4204,6 +4409,8 @@ void Animation::optimize(real_t p_allowed_velocity_err, real_t p_allowed_angular _scale_track_optimize(i, p_allowed_velocity_err, p_allowed_angular_err, precision); } else if (tracks[i]->type == TYPE_BLEND_SHAPE) { _blend_shape_track_optimize(i, p_allowed_velocity_err, precision); + } else if (tracks[i]->type == TYPE_VALUE) { + _value_track_optimize(i, p_allowed_velocity_err, p_allowed_angular_err, precision); } } } diff --git a/scene/resources/animation.h b/scene/resources/animation.h index f5eadd2646..46a88df130 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -57,6 +57,8 @@ public: INTERPOLATION_NEAREST, INTERPOLATION_LINEAR, INTERPOLATION_CUBIC, + INTERPOLATION_LINEAR_ANGLE, + INTERPOLATION_CUBIC_ANGLE, }; enum UpdateMode { @@ -236,11 +238,13 @@ private: _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const; _FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const; _FORCE_INLINE_ real_t _interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const; + _FORCE_INLINE_ Variant _interpolate_angle(const Variant &p_a, const Variant &p_b, real_t p_c) const; _FORCE_INLINE_ Vector3 _cubic_interpolate_in_time(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; _FORCE_INLINE_ Quaternion _cubic_interpolate_in_time(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; _FORCE_INLINE_ Variant _cubic_interpolate_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; _FORCE_INLINE_ real_t _cubic_interpolate_in_time(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; + _FORCE_INLINE_ Variant _cubic_interpolate_angle_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; template <class T> _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const; @@ -362,14 +366,16 @@ private: return idxr; } + bool _float_track_optimize_key(const TKey<float> t0, const TKey<float> t1, const TKey<float> t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error); + bool _vector2_track_optimize_key(const TKey<Vector2> t0, const TKey<Vector2> t1, const TKey<Vector2> t2, real_t p_alowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); bool _vector3_track_optimize_key(const TKey<Vector3> t0, const TKey<Vector3> t1, const TKey<Vector3> t2, real_t p_alowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); bool _quaternion_track_optimize_key(const TKey<Quaternion> t0, const TKey<Quaternion> t1, const TKey<Quaternion> t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); - bool _float_track_optimize_key(const TKey<float> t0, const TKey<float> t1, const TKey<float> t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error); void _position_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error); void _rotation_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); void _scale_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error); void _blend_shape_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_precision_error); + void _value_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error); protected: bool _set(const StringName &p_name, const Variant &p_value); diff --git a/thirdparty/README.md b/thirdparty/README.md index 4514217dd8..ffc8137819 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -345,6 +345,9 @@ File extracted from upstream release tarball: - Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h` providing configuration for light bundling with core. +Some changes have been made in order to fix Windows on ARM build errors. +They are marked with `// -- GODOT start --` and `// -- GODOT end --` + ## meshoptimizer @@ -375,6 +378,9 @@ Files extracted from upstream repository: - `minimp3_ex.h` - `LICENSE` +Some changes have been made in order to fix Windows on ARM build errors. +They are marked with `// -- GODOT start --` and `// -- GODOT end --` + ## miniupnpc diff --git a/thirdparty/mbedtls/library/timing.c b/thirdparty/mbedtls/library/timing.c index 57bc9bcc12..78bfa10cfb 100644 --- a/thirdparty/mbedtls/library/timing.c +++ b/thirdparty/mbedtls/library/timing.c @@ -195,8 +195,10 @@ unsigned long mbedtls_timing_hardclock( void ) #endif /* !HAVE_HARDCLOCK && MBEDTLS_HAVE_ASM && __GNUC__ && __ia64__ */ -#if !defined(HAVE_HARDCLOCK) && defined(_MSC_VER) && \ +// -- GODOT start -- +#if !defined(HAVE_HARDCLOCK) && defined(_WIN32) && \ !defined(EFIX64) && !defined(EFI32) +// -- GODOT end -- #define HAVE_HARDCLOCK diff --git a/thirdparty/minimp3/minimp3.h b/thirdparty/minimp3/minimp3.h index 3220ae1a85..2a9975cc86 100644 --- a/thirdparty/minimp3/minimp3.h +++ b/thirdparty/minimp3/minimp3.h @@ -1566,7 +1566,18 @@ static void mp3d_synth(float *xl, mp3d_sample_t *dstl, int nch, float *lins) #else /* MINIMP3_FLOAT_OUTPUT */ +// -- GODOT start -- +#if defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC) || defined(_M_ARM)) + static f4 g_scale; + g_scale = vsetq_lane_f32(1.0f/32768.0f, g_scale, 0); + g_scale = vsetq_lane_f32(1.0f/32768.0f, g_scale, 1); + g_scale = vsetq_lane_f32(1.0f/32768.0f, g_scale, 2); + g_scale = vsetq_lane_f32(1.0f/32768.0f, g_scale, 3); +#else static const f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; +#endif +// -- GODOT end -- + a = VMUL(a, g_scale); b = VMUL(b, g_scale); #if HAVE_SSE @@ -1813,7 +1824,19 @@ void mp3dec_f32_to_s16(const float *in, int16_t *out, int num_samples) int aligned_count = num_samples & ~7; for(; i < aligned_count; i += 8) { + +// -- GODOT start -- +#if defined(_MSC_VER) && (defined(_M_ARM64) || defined(_M_ARM64EC) || defined(_M_ARM)) + static f4 g_scale; + g_scale = vsetq_lane_f32(32768.0f, g_scale, 0); + g_scale = vsetq_lane_f32(32768.0f, g_scale, 1); + g_scale = vsetq_lane_f32(32768.0f, g_scale, 2); + g_scale = vsetq_lane_f32(32768.0f, g_scale, 3); +#else static const f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f }; +#endif +// -- GODOT end -- + f4 a = VMUL(VLD(&in[i ]), g_scale); f4 b = VMUL(VLD(&in[i+4]), g_scale); #if HAVE_SSE |