diff options
49 files changed, 851 insertions, 636 deletions
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml index a77e9e28c6..c635eba2ab 100644 --- a/doc/classes/AnimationMixer.xml +++ b/doc/classes/AnimationMixer.xml @@ -273,7 +273,7 @@ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers. For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each. </member> - <member name="callback_mode_discrete" type="int" setter="set_callback_mode_discrete" getter="get_callback_mode_discrete" enum="AnimationMixer.AnimationCallbackModeDiscrete" default="0"> + <member name="callback_mode_discrete" type="int" setter="set_callback_mode_discrete" getter="get_callback_mode_discrete" enum="AnimationMixer.AnimationCallbackModeDiscrete" default="1"> Ordinarily, tracks can be set to [constant Animation.UPDATE_DISCRETE] to update infrequently, usually when using nearest interpolation. However, when blending with [constant Animation.UPDATE_CONTINUOUS] several results are considered. The [member callback_mode_discrete] specify it explicitly. See also [enum AnimationCallbackModeDiscrete]. To make the blended results look good, it is recommended to set this to [constant ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS] to update every frame during blending. Other values exist for compatibility and they are fine if there is no blending, but not so, may produce artifacts. @@ -361,14 +361,14 @@ Make method calls immediately when reached in the animation. </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT" value="0" enum="AnimationCallbackModeDiscrete"> - An [constant Animation.UPDATE_DISCRETE] track value takes precedence when blending [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and [constant Animation.UPDATE_DISCRETE] track values. This is the default behavior for [AnimationPlayer]. - [b]Note:[/b] If a value track has non-numeric type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT] with [constant Animation.UPDATE_DISCRETE]. + An [constant Animation.UPDATE_DISCRETE] track value takes precedence when blending [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and [constant Animation.UPDATE_DISCRETE] track values. </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE" value="1" enum="AnimationCallbackModeDiscrete"> - An [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track value takes precedence when blending the [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and the [constant Animation.UPDATE_DISCRETE] track values. + An [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track value takes precedence when blending the [constant Animation.UPDATE_CONTINUOUS] or [constant Animation.UPDATE_CAPTURE] track values and the [constant Animation.UPDATE_DISCRETE] track values. This is the default behavior for [AnimationPlayer]. </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS" value="2" enum="AnimationCallbackModeDiscrete"> Always treat the [constant Animation.UPDATE_DISCRETE] track value as [constant Animation.UPDATE_CONTINUOUS] with [constant Animation.INTERPOLATION_NEAREST]. This is the default behavior for [AnimationTree]. + If a value track has non-numeric type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE]. </constant> </constants> </class> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 3e3d2205f2..7ee239415f 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -925,14 +925,14 @@ <member name="run/auto_save/save_before_running" type="bool" setter="" getter=""> If [code]true[/code], saves all scenes and scripts automatically before running the project. Setting this to [code]false[/code] prevents the editor from saving if there are no changes which can speed up the project startup slightly, but it makes it possible to run a project that has unsaved changes. (Unsaved changes will not be visible in the running project.) </member> - <member name="run/output/always_clear_output_on_play" type="bool" setter="" getter=""> - If [code]true[/code], the editor will clear the Output panel when running the project. + <member name="run/bottom_panel/action_on_play" type="int" setter="" getter=""> + The action to execute on the bottom panel when running the project. </member> - <member name="run/output/always_close_output_on_stop" type="bool" setter="" getter=""> - If [code]true[/code], the editor will collapse the Output panel when stopping the project. + <member name="run/bottom_panel/action_on_stop" type="int" setter="" getter=""> + The action to execute on the bottom panel when stopping the project. </member> - <member name="run/output/always_open_output_on_play" type="bool" setter="" getter=""> - If [code]true[/code], the editor will expand the Output panel when running the project. + <member name="run/output/always_clear_output_on_play" type="bool" setter="" getter=""> + If [code]true[/code], the editor will clear the Output panel when running the project. </member> <member name="run/output/font_size" type="int" setter="" getter=""> The size of the font in the [b]Output[/b] panel at the bottom of the editor. This setting does not impact the font size of the script editor (see [member interface/editor/code_font_size]). diff --git a/doc/classes/Joint2D.xml b/doc/classes/Joint2D.xml index af0a54815f..0099c76d08 100644 --- a/doc/classes/Joint2D.xml +++ b/doc/classes/Joint2D.xml @@ -4,7 +4,7 @@ Abstract base class for all 2D physics joints. </brief_description> <description> - Abstract base class for all joints in 2D physics. 2D joints bind together two physics bodies and apply a constraint. + Abstract base class for all joints in 2D physics. 2D joints bind together two physics bodies ([member node_a] and [member node_b]) and apply a constraint. </description> <tutorials> </tutorials> @@ -12,7 +12,7 @@ <method name="get_rid" qualifiers="const"> <return type="RID" /> <description> - Returns the joint's [RID]. + Returns the joint's internal [RID] from the [PhysicsServer2D]. </description> </method> </methods> @@ -22,13 +22,13 @@ When set to [code]0[/code], the default value from [member ProjectSettings.physics/2d/solver/default_constraint_bias] is used. </member> <member name="disable_collision" type="bool" setter="set_exclude_nodes_from_collision" getter="get_exclude_nodes_from_collision" default="true"> - If [code]true[/code], [member node_a] and [member node_b] can not collide. + If [code]true[/code], the two bodies bound together do not collide with each other. </member> <member name="node_a" type="NodePath" setter="set_node_a" getter="get_node_a" default="NodePath("")"> - The first body attached to the joint. Must derive from [PhysicsBody2D]. + Path to the first body (A) attached to the joint. The node must inherit [PhysicsBody2D]. </member> <member name="node_b" type="NodePath" setter="set_node_b" getter="get_node_b" default="NodePath("")"> - The second body attached to the joint. Must derive from [PhysicsBody2D]. + Path to the second body (B) attached to the joint. The node must inherit [PhysicsBody2D]. </member> </members> </class> diff --git a/doc/classes/Joint3D.xml b/doc/classes/Joint3D.xml index ea0dda881a..950129806a 100644 --- a/doc/classes/Joint3D.xml +++ b/doc/classes/Joint3D.xml @@ -4,7 +4,7 @@ Abstract base class for all 3D physics joints. </brief_description> <description> - Abstract base class for all joints in 3D physics. 3D joints bind together two physics bodies and apply a constraint. + Abstract base class for all joints in 3D physics. 3D joints bind together two physics bodies ([member node_a] and [member node_b]) and apply a constraint. If only one body is defined, it is attached to a fixed [StaticBody3D] without collision shapes. </description> <tutorials> <link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/2752</link> @@ -13,19 +13,21 @@ <method name="get_rid" qualifiers="const"> <return type="RID" /> <description> - Returns the joint's [RID]. + Returns the joint's internal [RID] from the [PhysicsServer3D]. </description> </method> </methods> <members> <member name="exclude_nodes_from_collision" type="bool" setter="set_exclude_nodes_from_collision" getter="get_exclude_nodes_from_collision" default="true"> - If [code]true[/code], the two bodies of the nodes are not able to collide with each other. + If [code]true[/code], the two bodies bound together do not collide with each other. </member> <member name="node_a" type="NodePath" setter="set_node_a" getter="get_node_a" default="NodePath("")"> - The node attached to the first side (A) of the joint. + Path to the first node (A) attached to the joint. The node must inherit [PhysicsBody3D]. + If left empty and [member node_b] is set, the body is attached to a fixed [StaticBody3D] without collision shapes. </member> <member name="node_b" type="NodePath" setter="set_node_b" getter="get_node_b" default="NodePath("")"> - The node attached to the second side (B) of the joint. + Path to the second node (B) attached to the joint. The node must inherit [PhysicsBody3D]. + If left empty and [member node_a] is set, the body is attached to a fixed [StaticBody3D] without collision shapes. </member> <member name="solver_priority" type="int" setter="set_solver_priority" getter="get_solver_priority" default="1"> The priority used to define which solver is executed first for multiple joints. The lower the value, the higher the priority. diff --git a/doc/classes/XRServer.xml b/doc/classes/XRServer.xml index 4179ba821c..49b13986ca 100644 --- a/doc/classes/XRServer.xml +++ b/doc/classes/XRServer.xml @@ -135,6 +135,11 @@ Emitted when an interface is removed. </description> </signal> + <signal name="reference_frame_changed"> + <description> + Emitted when the reference frame transform changes. + </description> + </signal> <signal name="tracker_added"> <param index="0" name="tracker_name" type="StringName" /> <param index="1" name="type" type="int" /> diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index 3023c5907a..f70730d540 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -428,6 +428,7 @@ void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_info // Update Tree... TreeItem *action_item = action_tree->create_item(root); + ERR_FAIL_NULL(action_item); action_item->set_meta("__action", action_info.action); action_item->set_meta("__name", action_info.name); @@ -604,7 +605,7 @@ ActionMapEditor::ActionMapEditor() { action_tree->set_column_custom_minimum_width(1, 80 * EDSCALE); action_tree->set_column_expand(2, false); action_tree->set_column_custom_minimum_width(2, 50 * EDSCALE); - action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited)); + action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited), CONNECT_DEFERRED); action_tree->connect("item_activated", callable_mp(this, &ActionMapEditor::_tree_item_activated)); action_tree->connect("button_clicked", callable_mp(this, &ActionMapEditor::_tree_button_pressed)); main_vbox->add_child(action_tree); diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 2f7183b883..2a98f50a3a 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -268,11 +268,7 @@ Error EditorDebuggerNode::start(const String &p_uri) { } stop(true); current_uri = p_uri; - if (EDITOR_GET("run/output/always_open_output_on_play")) { - EditorNode::get_bottom_panel()->make_item_visible(EditorNode::get_log()); - } else { - EditorNode::get_bottom_panel()->make_item_visible(this); - } + server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_uri.substr(0, p_uri.find("://") + 3))); const Error err = server->start(p_uri); if (err != OK) { diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 156e740509..37bb048b19 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1009,7 +1009,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { set_process(true); camera_override = CameraOverride::OVERRIDE_NONE; - tabs->set_current_tab(0); _set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS); _update_buttons_state(); emit_signal(SNAME("started")); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 48c0c7ac06..fa5cdd185f 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -37,7 +37,7 @@ #include "core/object/script_language.h" #include "core/os/keyboard.h" #include "core/string/string_builder.h" -#include "core/version.h" +#include "core/version_generated.gen.h" #include "editor/doc_data_compressed.gen.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" @@ -2340,6 +2340,9 @@ void EditorHelp::_help_callback(const String &p_topic) { if (class_desc->is_ready()) { // call_deferred() is not enough. + if (class_desc->is_connected(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph))) { + class_desc->disconnect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph)); + } class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(line), CONNECT_ONE_SHOT | CONNECT_DEFERRED); } else { scroll_to = line; @@ -3380,6 +3383,7 @@ EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringNam if (theme_item.name == p_theme_item_name) { result = current; found = true; + if (!is_native) { break; } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index f4dcc8bd4a..fafefa7771 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2869,15 +2869,6 @@ void EditorInspector::update_tree() { // Otherwise the category was probably added via `@export_category` or `_get_property_list()`. const bool is_custom_category = p.hint_string.is_empty(); - if ((is_custom_category && !show_custom_categories) || (!is_custom_category && !show_standard_categories)) { - continue; - } - - // Hide the "MultiNodeEdit" category for MultiNodeEdit. - if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") { - continue; - } - // Iterate over remaining properties. If no properties in category, skip the category. List<PropertyInfo>::Element *N = E_property->next(); bool valid = true; @@ -2898,22 +2889,20 @@ void EditorInspector::update_tree() { continue; // Empty, ignore it. } - // Create an EditorInspectorCategory and add it to the inspector. - EditorInspectorCategory *category = memnew(EditorInspectorCategory); - main_vbox->add_child(category); - category_vbox = nullptr; //reset + String category_label; + String category_tooltip; + Ref<Texture> category_icon; // Do not add an icon, do not change the current class (`doc_name`) for custom categories. if (is_custom_category) { - category->label = p.name; - category->set_tooltip_text(p.name); + category_label = p.name; + category_tooltip = p.name; } else { - String type = p.name; - String label = p.name; doc_name = p.name; + category_label = p.name; // Use category's owner script to update some of its information. - if (!EditorNode::get_editor_data().is_type_recognized(type) && ResourceLoader::exists(p.hint_string)) { + if (!EditorNode::get_editor_data().is_type_recognized(p.name) && ResourceLoader::exists(p.hint_string)) { Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script"); if (scr.is_valid()) { StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); @@ -2926,32 +2915,50 @@ void EditorInspector::update_tree() { doc_name = docs[docs.size() - 1].name; } if (script_name != StringName()) { - label = script_name; + category_label = script_name; } // Find the icon corresponding to the script. if (script_name != StringName()) { - category->icon = EditorNode::get_singleton()->get_class_icon(script_name); + category_icon = EditorNode::get_singleton()->get_class_icon(script_name); } else { - category->icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object"); + category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object"); } } } - if (category->icon.is_null() && !type.is_empty()) { - category->icon = EditorNode::get_singleton()->get_class_icon(type); + if (category_icon.is_null() && !p.name.is_empty()) { + category_icon = EditorNode::get_singleton()->get_class_icon(p.name); } - // Set the category label. - category->label = label; - category->doc_class_name = doc_name; - if (use_doc_hints) { // `|` separators used in `EditorHelpBit`. - category->set_tooltip_text("class|" + doc_name + "|"); + category_tooltip = "class|" + doc_name + "|"; } } + if ((is_custom_category && !show_custom_categories) || (!is_custom_category && !show_standard_categories)) { + continue; + } + + // Hide the "MultiNodeEdit" category for MultiNodeEdit. + if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") { + continue; + } + + // Create an EditorInspectorCategory and add it to the inspector. + EditorInspectorCategory *category = memnew(EditorInspectorCategory); + main_vbox->add_child(category); + category_vbox = nullptr; // Reset. + + // Set the category info. + category->label = category_label; + category->set_tooltip_text(category_tooltip); + category->icon = category_icon; + if (!is_custom_category) { + category->doc_class_name = doc_name; + } + // Add editors at the start of a category. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { ped->parse_category(object, p.name); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6599a58da4..0df4df36bc 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4107,7 +4107,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b return OK; } -HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) { +HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node, bool p_node_references_only) { HashMap<StringName, Variant> modified_property_map; List<PropertyInfo> pinfo; @@ -4119,7 +4119,17 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node * Variant current_value = p_node->get(E.name); if (is_valid_revert) { if (PropertyUtils::is_property_value_different(current_value, revert_value)) { - modified_property_map[E.name] = current_value; + // If this property is a direct node reference, save a NodePath instead to prevent corrupted references. + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + Node *target_node = Object::cast_to<Node>(current_value); + if (target_node) { + modified_property_map[E.name] = p_node->get_path_to(target_node); + } + } else { + if (!p_node_references_only) { + modified_property_map[E.name] = current_value; + } + } } } } @@ -4137,10 +4147,118 @@ void EditorNode::update_ownership_table_for_addition_node_ancestors(Node *p_curr } } -void EditorNode::update_diff_data_for_node( - Node *p_edited_scene, +void EditorNode::update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification) { + if (p_node) { + // First, attempt to restore the script property since it may affect the get_property_list method. + Variant *script_property_table_entry = p_node_modification.property_table.getptr(CoreStringName(script)); + if (script_property_table_entry) { + p_node->set_script(*script_property_table_entry); + } + + // Get properties for this node. + List<PropertyInfo> pinfo; + p_node->get_property_list(&pinfo); + + // Get names of all valid property names. + HashMap<StringName, bool> property_node_reference_table; + for (const PropertyInfo &E : pinfo) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { + property_node_reference_table[E.name] = true; + } else { + property_node_reference_table[E.name] = false; + } + } + } + + // Restore the modified properties for this node. + for (const KeyValue<StringName, Variant> &E : p_node_modification.property_table) { + bool *property_node_reference_table_entry = property_node_reference_table.getptr(E.key); + if (property_node_reference_table_entry) { + // If the property is a node reference, attempt to restore from the node path instead. + bool is_node_reference = *property_node_reference_table_entry; + if (is_node_reference) { + if (E.value.get_type() == Variant::NODE_PATH) { + p_node->set(E.key, p_node->get_node_or_null(E.value)); + } + } else { + p_node->set(E.key, E.value); + } + } + } + + // Restore the connections to other nodes. + for (const ConnectionWithNodePath &E : p_node_modification.connections_to) { + Connection conn = E.connection; + + // Get the node the callable is targeting. + Node *target_node = Object::cast_to<Node>(conn.callable.get_object()); + + // If the callable object no longer exists or is marked for deletion, + // attempt to reaccquire the closest match by using the node path + // we saved earlier. + if (!target_node || !target_node->is_queued_for_deletion()) { + target_node = p_node->get_node_or_null(E.node_path); + } + + if (target_node) { + // Reconstruct the callable. + Callable new_callable = Callable(target_node, conn.callable.get_method()); + + if (!p_node->is_connected(conn.signal.get_name(), new_callable)) { + ERR_FAIL_COND(p_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK); + } + } + } + + // Restore the connections from other nodes. + for (const Connection &E : p_node_modification.connections_from) { + Connection conn = E; + + bool valid = p_node->has_method(conn.callable.get_method()) || Ref<Script>(p_node->get_script()).is_null() || Ref<Script>(p_node->get_script())->has_method(conn.callable.get_method()); + ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method())); + + // Get the object which the signal is connected from. + Object *source_object = conn.signal.get_object(); + + if (source_object) { + ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(p_node, conn.callable.get_method()), conn.flags) != OK); + } + } + + // Re-add the groups. + for (const Node::GroupInfo &E : p_node_modification.groups) { + p_node->add_to_group(E.name, E.persistent); + } + } +} + +void EditorNode::update_node_reference_modification_table_for_node( Node *p_root, Node *p_node, + List<Node *> p_excluded_nodes, + HashMap<NodePath, ModificationNodeEntry> &p_modification_table) { + if (!p_excluded_nodes.find(p_node)) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false); + + if (!modified_properties.is_empty()) { + ModificationNodeEntry modification_node_entry; + modification_node_entry.property_table = modified_properties; + + p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry; + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + update_node_reference_modification_table_for_node(p_root, child, p_excluded_nodes, p_modification_table); + } + } +} + +void EditorNode::update_reimported_diff_data_for_node( + Node *p_edited_scene, + Node *p_reimported_root, + Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table, List<AdditiveNodeEntry> &p_addition_list) { bool node_part_of_subscene = p_node != p_edited_scene && @@ -4150,14 +4268,14 @@ void EditorNode::update_diff_data_for_node( // Loop through the owners until either we reach the root node or nullptr Node *valid_node_owner = p_node->get_owner(); while (valid_node_owner) { - if (valid_node_owner == p_root) { + if (valid_node_owner == p_reimported_root) { break; } valid_node_owner = valid_node_owner->get_owner(); } - if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) { - HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node); + if ((valid_node_owner == p_reimported_root && (p_reimported_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_reimported_root) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false); // Find all valid connections to other nodes. List<Connection> connections_to; @@ -4189,7 +4307,7 @@ void EditorNode::update_diff_data_for_node( if (source_node) { valid_source_owner = source_node->get_owner(); while (valid_source_owner) { - if (valid_source_owner == p_root) { + if (valid_source_owner == p_reimported_root) { break; } valid_source_owner = valid_source_owner->get_owner(); @@ -4215,41 +4333,55 @@ void EditorNode::update_diff_data_for_node( modification_node_entry.connections_from = valid_connections_from; modification_node_entry.groups = groups; - p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry; + p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry; } } else { - AdditiveNodeEntry new_additive_node_entry; - new_additive_node_entry.node = p_node; - new_additive_node_entry.parent = p_root->get_path_to(p_node->get_parent()); - new_additive_node_entry.owner = p_node->get_owner(); - new_additive_node_entry.index = p_node->get_index(); + // Only save additional nodes which have an owner since this was causing issues transient ownerless nodes + // which get recreated upon scene tree entry. + // For now instead, assume all ownerless nodes are transient and will have to be recreated. + if (p_node->get_owner()) { + HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, true); + + if (p_node->get_parent()->get_owner() != nullptr && p_node->get_parent()->get_owner() != p_edited_scene) { + AdditiveNodeEntry new_additive_node_entry; + new_additive_node_entry.node = p_node; + new_additive_node_entry.parent = p_reimported_root->get_path_to(p_node->get_parent()); + new_additive_node_entry.owner = p_node->get_owner(); + new_additive_node_entry.index = p_node->get_index(); + + Node2D *node_2d = Object::cast_to<Node2D>(p_node); + if (node_2d) { + new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent()); + } + Node3D *node_3d = Object::cast_to<Node3D>(p_node); + if (node_3d) { + new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent()); + } - Node2D *node_2d = Object::cast_to<Node2D>(p_node); - if (node_2d) { - new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent()); - } - Node3D *node_3d = Object::cast_to<Node3D>(p_node); - if (node_3d) { - new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent()); - } + // Gathers the ownership of all ancestor nodes for later use. + HashMap<Node *, Node *> ownership_table; + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + update_ownership_table_for_addition_node_ancestors(child, ownership_table); + } - // Gathers the ownership of all ancestor nodes for later use. - HashMap<Node *, Node *> ownership_table; - for (int i = 0; i < p_node->get_child_count(); i++) { - Node *child = p_node->get_child(i); - update_ownership_table_for_addition_node_ancestors(child, ownership_table); - } + new_additive_node_entry.ownership_table = ownership_table; - new_additive_node_entry.ownership_table = ownership_table; + p_addition_list.push_back(new_additive_node_entry); + } - p_addition_list.push_back(new_additive_node_entry); + if (!modified_properties.is_empty()) { + ModificationNodeEntry modification_node_entry; + modification_node_entry.property_table = modified_properties; - return; + p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry; + } + } } for (int i = 0; i < p_node->get_child_count(); i++) { Node *child = p_node->get_child(i); - update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list); + update_reimported_diff_data_for_node(p_edited_scene, p_reimported_root, child, p_modification_table, p_addition_list); } } // @@ -4411,17 +4543,19 @@ void EditorNode::_project_run_started() { log->clear(); } - if (bool(EDITOR_GET("run/output/always_open_output_on_play"))) { + int action_on_play = EDITOR_GET("run/bottom_panel/action_on_play"); + if (action_on_play == ACTION_ON_PLAY_OPEN_OUTPUT) { bottom_panel->make_item_visible(log); + } else if (action_on_play == ACTION_ON_PLAY_OPEN_DEBUGGER) { + bottom_panel->make_item_visible(EditorDebuggerNode::get_singleton()); } } void EditorNode::_project_run_stopped() { - if (!bool(EDITOR_GET("run/output/always_close_output_on_stop"))) { - return; + int action_on_stop = EDITOR_GET("run/bottom_panel/action_on_stop"); + if (action_on_stop == ACTION_ON_STOP_CLOSE_BUTTOM_PANEL) { + bottom_panel->hide_bottom_panel(); } - - bottom_panel->make_item_visible(log, false); } void EditorNode::notify_all_debug_sessions_exited() { @@ -5548,12 +5682,11 @@ void EditorNode::_file_access_close_error_notify_impl(const String &p_str) { add_io_error(vformat(TTR("Unable to write to file '%s', file in use, locked or lacking permissions."), p_str)); } -// Since we felt that a bespoke NOTIFICATION might not be desirable, this function -// provides the hardcoded callbacks to address known bugs which occur on certain -// nodes during reimport. -// Ideally, we should probably agree on a standardized method name which could be -// called from here instead. -void EditorNode::_notify_scene_updated(Node *p_node) { +// Recursive function to inform nodes that an array of nodes have had their scene reimported. +// It will attempt to call a method named '_nodes_scene_reimported' on every node in the +// tree so that editor scripts which create transient nodes will have the opportunity +// to recreate them. +void EditorNode::_notify_nodes_scene_reimported(Node *p_node, Array p_reimported_nodes) { Skeleton3D *skel_3d = Object::cast_to<Skeleton3D>(p_node); if (skel_3d) { skel_3d->reset_bone_poses(); @@ -5564,8 +5697,12 @@ void EditorNode::_notify_scene_updated(Node *p_node) { } } + if (p_node->has_method("_nodes_scene_reimported")) { + p_node->call("_nodes_scene_reimported", p_reimported_nodes); + } + for (int i = 0; i < p_node->get_child_count(); i++) { - _notify_scene_updated(p_node->get_child(i)); + _notify_nodes_scene_reimported(p_node->get_child(i), p_reimported_nodes); } } @@ -5635,6 +5772,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) { int original_edited_scene_idx = editor_data.get_edited_scene(); HashMap<int, List<Node *>> edited_scene_map; + Array replaced_nodes; // Walk through each opened scene to get a global list of all instances which match // the current reimported scenes. @@ -5675,12 +5813,16 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // Update the version editor_data.is_scene_changed(current_scene_idx); + // Contains modifications in the edited scene which reference nodes inside of any nodes we will be reimporting. + HashMap<NodePath, ModificationNodeEntry> edited_scene_global_modification_table; + update_node_reference_modification_table_for_node(current_edited_scene, current_edited_scene, edited_scene_map_elem.value, edited_scene_global_modification_table); + for (Node *original_node : edited_scene_map_elem.value) { // Walk the tree for the current node and extract relevant diff data, storing it in the modification table. // For additional nodes which are part of the current scene, they get added to the addition table. HashMap<NodePath, ModificationNodeEntry> modification_table; List<AdditiveNodeEntry> addition_list; - update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list); + update_reimported_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list); // Disconnect all relevant connections, all connections from and persistent connections to. for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) { @@ -5762,7 +5904,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // be properly updated. for (String path : required_load_paths) { if (!local_scene_cache.find(path)) { - current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); + current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err); local_scene_cache[path] = current_packed_scene; } else { current_packed_scene = local_scene_cache[path]; @@ -5780,6 +5922,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins ERR_FAIL_NULL(instantiated_node); + // For clear instance state for path recaching. + instantiated_node->set_scene_instance_state(Ref<SceneState>()); + bool original_node_is_displayed_folded = original_node->is_displayed_folded(); bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); @@ -5885,77 +6030,30 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins NodePath new_current_path = E.key; Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path); - if (modifiable_node) { - // Get properties for this node. - List<PropertyInfo> pinfo; - modifiable_node->get_property_list(&pinfo); - - // Get names of all valid property names (TODO: make this more efficient). - List<String> property_names; - for (const PropertyInfo &E2 : pinfo) { - if (E2.usage & PROPERTY_USAGE_STORAGE) { - property_names.push_back(E2.name); - } - } - - // Restore the modified properties for this node. - for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) { - if (property_names.find(E2.key)) { - modifiable_node->set(E2.key, E2.value); - } - } - // Restore the connections to other nodes. - for (const ConnectionWithNodePath &E2 : E.value.connections_to) { - Connection conn = E2.connection; - - // Get the node the callable is targeting. - Node *target_node = cast_to<Node>(conn.callable.get_object()); - - // If the callable object no longer exists or is marked for deletion, - // attempt to reaccquire the closest match by using the node path - // we saved earlier. - if (!target_node || !target_node->is_queued_for_deletion()) { - target_node = modifiable_node->get_node_or_null(E2.node_path); - } - - if (target_node) { - // Reconstruct the callable. - Callable new_callable = Callable(target_node, conn.callable.get_method()); - - if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) { - ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK); - } - } - } - - // Restore the connections from other nodes. - for (const Connection &E2 : E.value.connections_from) { - Connection conn = E2; - - bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method()); - ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method())); - - // Get the object which the signal is connected from. - Object *source_object = conn.signal.get_object(); + update_node_from_node_modification_entry(modifiable_node, E.value); + } + // Add the newly instantiated node to the edited scene's replaced node list. + replaced_nodes.push_back(instantiated_node); + } - if (source_object) { - ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK); - } - } + // Attempt to restore the modified properties and signals for the instantitated node and all its owned children. + for (KeyValue<NodePath, ModificationNodeEntry> &E : edited_scene_global_modification_table) { + NodePath new_current_path = E.key; + Node *modifiable_node = current_edited_scene->get_node_or_null(new_current_path); - // Re-add the groups. - for (const Node::GroupInfo &E2 : E.value.groups) { - modifiable_node->add_to_group(E2.name, E2.persistent); - } - } + if (modifiable_node) { + update_node_from_node_modification_entry(modifiable_node, E.value); } } // Cleanup the history of the changes. editor_history.cleanup_history(); - - _notify_scene_updated(current_edited_scene); } + + // For the whole editor, call the _notify_nodes_scene_reimported with a list of replaced nodes. + // To inform anything that depends on them that they should update as appropriate. + _notify_nodes_scene_reimported(this, replaced_nodes); + edited_scene_map.clear(); } editor_data.set_edited_scene(original_edited_scene_idx); @@ -7228,6 +7326,8 @@ EditorNode::EditorNode() { disk_changed = memnew(ConfirmationDialog); { + disk_changed->set_title(TTR("Files have been modified on disk")); + VBoxContainer *vbc = memnew(VBoxContainer); disk_changed->add_child(vbc); @@ -7241,9 +7341,9 @@ EditorNode::EditorNode() { disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_modified_scenes)); disk_changed->connect("confirmed", callable_mp(this, &EditorNode::_reload_project_settings)); - disk_changed->set_ok_button_text(TTR("Reload")); + disk_changed->set_ok_button_text(TTR("Discard local changes and reload")); - disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &EditorNode::_resave_scenes)); } diff --git a/editor/editor_node.h b/editor/editor_node.h index 6b3359eaee..5d7bd5b4f8 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -140,6 +140,17 @@ public: SCENE_NAME_CASING_KEBAB_CASE, }; + enum ActionOnPlay { + ACTION_ON_PLAY_DO_NOTHING, + ACTION_ON_PLAY_OPEN_OUTPUT, + ACTION_ON_PLAY_OPEN_DEBUGGER, + }; + + enum ActionOnStop { + ACTION_ON_STOP_DO_NOTHING, + ACTION_ON_STOP_CLOSE_BUTTOM_PANEL, + }; + struct ExecuteThreadArgs { String path; List<String> args; @@ -662,7 +673,7 @@ private: void _begin_first_scan(); - void _notify_scene_updated(Node *p_node); + void _notify_nodes_scene_reimported(Node *p_node, Array p_reimported_nodes); protected: friend class FileSystemDock; @@ -779,7 +790,7 @@ public: Error load_scene(const String &p_scene, bool p_ignore_broken_deps = false, bool p_set_inherited = false, bool p_clear_errors = true, bool p_force_open_imported = false, bool p_silent_change_tab = false); Error load_resource(const String &p_resource, bool p_ignore_broken_deps = false); - HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node); + HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node, bool p_node_references_only); struct AdditiveNodeEntry { Node *node = nullptr; @@ -806,11 +817,18 @@ public: }; void update_ownership_table_for_addition_node_ancestors(Node *p_current_node, HashMap<Node *, Node *> &p_ownership_table); + void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification); - void update_diff_data_for_node( - Node *p_edited_scene, + void update_node_reference_modification_table_for_node( Node *p_root, Node *p_node, + List<Node *> p_excluded_nodes, + HashMap<NodePath, ModificationNodeEntry> &p_modification_table); + + void update_reimported_diff_data_for_node( + Node *p_edited_scene, + Node *p_reimported_root, + Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table, List<AdditiveNodeEntry> &p_addition_list); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index d7bc3502ce..737bec352d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -818,11 +818,13 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // Auto save _initial_set("run/auto_save/save_before_running", true); + // Bottom panel + EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/bottom_panel/action_on_play", EditorNode::ACTION_ON_PLAY_OPEN_OUTPUT, "Do Nothing,Open Output,Open Debugger") + EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/bottom_panel/action_on_stop", EditorNode::ACTION_ON_STOP_DO_NOTHING, "Do Nothing,Close Bottom Panel") + // Output EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "run/output/font_size", 13, "8,48,1") _initial_set("run/output/always_clear_output_on_play", true); - _initial_set("run/output/always_open_output_on_play", true); - _initial_set("run/output/always_close_output_on_stop", false); EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "run/output/max_lines", 10000, "100,100000,1") diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2e88540fc4..c07667ac12 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -3310,15 +3310,15 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect // Opening the system file manager is not supported on the Android and web editors. const bool is_directory = fpath.ends_with("/"); - p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); - p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager")); + p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); + p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal")); if (!is_directory) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ExternalLink")), ED_GET_SHORTCUT("filesystem_dock/open_in_external_program"), FILE_OPEN_EXTERNAL); } - p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); - p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal")); + p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); + p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager")); #endif current_path = fpath; @@ -3362,8 +3362,8 @@ void FileSystemDock::_tree_empty_click(const Vector2 &p_pos, MouseButton p_butto #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED) // Opening the system file manager is not supported on the Android and web editors. tree_popup->add_separator(); - tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); + tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); #endif tree_popup->set_position(tree->get_screen_position() + p_pos); @@ -3425,8 +3425,8 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton file_list_popup->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("New Resource..."), FILE_NEW_RESOURCE); file_list_popup->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("New TextFile..."), FILE_NEW_TEXTFILE); file_list_popup->add_separator(); - file_list_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); file_list_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); + file_list_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); file_list_popup->set_position(files->get_screen_position() + p_pos); file_list_popup->reset_size(); diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 865729c7c3..2ecce2f739 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -446,6 +446,11 @@ void InputEventConfigurationDialog::_key_location_selected(int p_location) { _set_event(k, original_event); } +void InputEventConfigurationDialog::_input_list_item_activated() { + TreeItem *selected = input_list_tree->get_selected(); + selected->set_collapsed(!selected->is_collapsed()); +} + void InputEventConfigurationDialog::_input_list_item_selected() { TreeItem *selected = input_list_tree->get_selected(); @@ -670,6 +675,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { input_list_tree = memnew(Tree); input_list_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); input_list_tree->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); // Min height for tree + input_list_tree->connect("item_activated", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_activated)); input_list_tree->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected)); input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); manual_vbox->add_child(input_list_tree); diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h index 1d2cc8ba36..b27f25a5b7 100644 --- a/editor/input_event_configuration_dialog.h +++ b/editor/input_event_configuration_dialog.h @@ -107,6 +107,7 @@ private: void _search_term_updated(const String &p_term); void _update_input_list(); + void _input_list_item_activated(); void _input_list_item_selected(); void _mod_toggled(bool p_checked, int p_index); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 62369cc2c1..1cf11f2a43 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -547,13 +547,18 @@ void AnimationPlayerEditor::_animation_name_edited() { } break; case TOOL_NEW_ANIM: { - String current = animation->get_item_text(animation->get_selected()); - Ref<Animation> current_anim = player->get_animation(current); Ref<Animation> new_anim = Ref<Animation>(memnew(Animation)); new_anim->set_name(new_name); - if (current_anim.is_valid()) { - new_anim->set_step(current_anim->get_step()); + + if (animation->get_item_count() > 0) { + String current = animation->get_item_text(animation->get_selected()); + Ref<Animation> current_anim = player->get_animation(current); + + if (current_anim.is_valid()) { + new_anim->set_step(current_anim->get_step()); + } } + String library_name; Ref<AnimationLibrary> al; library_name = library->get_item_metadata(library->get_selected()); @@ -881,6 +886,7 @@ void AnimationPlayerEditor::_update_player() { tool_anim->set_disabled(player == nullptr); pin->set_disabled(player == nullptr); + _set_controls_disabled(player == nullptr); if (!player) { AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); @@ -931,17 +937,6 @@ void AnimationPlayerEditor::_update_player() { ITEM_CHECK_DISABLED(TOOL_NEW_ANIM); #undef ITEM_CHECK_DISABLED - stop->set_disabled(no_anims_found); - play->set_disabled(no_anims_found); - play_bw->set_disabled(no_anims_found); - play_bw_from->set_disabled(no_anims_found); - play_from->set_disabled(no_anims_found); - frame->set_editable(!no_anims_found); - animation->set_disabled(no_anims_found); - autoplay->set_disabled(no_anims_found); - onion_toggle->set_disabled(no_anims_found); - onion_skinning->set_disabled(no_anims_found); - _update_animation_list_icons(); updating = false; @@ -958,7 +953,9 @@ void AnimationPlayerEditor::_update_player() { _animation_selected(0); } - if (!no_anims_found) { + if (no_anims_found) { + _set_controls_disabled(true); + } else { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); @@ -974,6 +971,20 @@ void AnimationPlayerEditor::_update_player() { _update_animation(); } +void AnimationPlayerEditor::_set_controls_disabled(bool p_disabled) { + frame->set_editable(!p_disabled); + + stop->set_disabled(p_disabled); + play->set_disabled(p_disabled); + play_bw->set_disabled(p_disabled); + play_bw_from->set_disabled(p_disabled); + play_from->set_disabled(p_disabled); + animation->set_disabled(p_disabled); + autoplay->set_disabled(p_disabled); + onion_toggle->set_disabled(p_disabled); + onion_skinning->set_disabled(p_disabled); +} + void AnimationPlayerEditor::_update_animation_list_icons() { for (int i = 0; i < animation->get_item_count(); i++) { String anim_name = animation->get_item_text(i); @@ -1076,9 +1087,6 @@ void AnimationPlayerEditor::_ensure_dummy_player() { } } - // Make some options disabled. - onion_toggle->set_disabled(dummy_exists); - onion_skinning->set_disabled(dummy_exists); int selected = animation->get_selected(); autoplay->set_disabled(selected != -1 ? (animation->get_item_text(selected).is_empty() ? true : dummy_exists) : true); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 70b31759fc..e624522566 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -206,6 +206,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _current_animation_changed(const String &p_name); void _update_animation(); void _update_player(); + void _set_controls_disabled(bool p_disabled); void _update_animation_list_icons(); void _update_name_dialog_library_dropdown(); void _blend_edited(); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 22dbb6e9f2..0a0909ec9f 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -4249,12 +4249,18 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { disk_changed = memnew(ConfirmationDialog); { + disk_changed->set_title(TTR("Files have been modified on disk")); + VBoxContainer *vbc = memnew(VBoxContainer); disk_changed->add_child(vbc); - Label *dl = memnew(Label); - dl->set_text(TTR("The following files are newer on disk.\nWhat action should be taken?:")); - vbc->add_child(dl); + Label *files_are_newer_label = memnew(Label); + files_are_newer_label->set_text(TTR("The following files are newer on disk.")); + vbc->add_child(files_are_newer_label); + + Label *what_action_label = memnew(Label); + what_action_label->set_text(TTR("What action should be taken?:")); + vbc->add_child(what_action_label); disk_changed_list = memnew(Tree); vbc->add_child(disk_changed_list); @@ -4262,9 +4268,9 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL); disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::reload_scripts).bind(false)); - disk_changed->set_ok_button_text(TTR("Reload")); + disk_changed->set_ok_button_text(TTR("Discard local changes and reload")); - disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->add_button(TTR("Keep local changes and overwrite"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts)); } diff --git a/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist b/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist index ee5f1d35ae..3d2ae6b52b 100644 --- a/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist +++ b/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist @@ -54,7 +54,7 @@ </array> <key>UISupportedInterfaceOrientations~ipad</key> <array> - $interface_orientations + $ipad_interface_orientations </array> $additional_plist_content $plist_launch_screen_name diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 3a1a2f4e60..42428231e6 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5119,7 +5119,11 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshIn ERR_FAIL_COND_V_MSG(p_mesh_instance->get_mesh().is_null(), -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but it has no mesh. This node will be exported without a mesh."); Ref<Mesh> mesh_resource = p_mesh_instance->get_mesh(); ERR_FAIL_COND_V_MSG(mesh_resource->get_surface_count() == 0, -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but its mesh has no surfaces. This node will be exported without a mesh."); - + TypedArray<Material> instance_materials; + for (int32_t surface_i = 0; surface_i < mesh_resource->get_surface_count(); surface_i++) { + Ref<Material> mat = p_mesh_instance->get_active_material(surface_i); + instance_materials.append(mat); + } Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(mesh_resource); Vector<float> blend_weights; int32_t blend_count = mesh_resource->get_blend_shape_count(); @@ -5130,17 +5134,6 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshIn Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); - TypedArray<Material> instance_materials; - for (int32_t surface_i = 0; surface_i < current_mesh->get_surface_count(); surface_i++) { - Ref<Material> mat = current_mesh->get_surface_material(surface_i); - if (p_mesh_instance->get_surface_override_material(surface_i).is_valid()) { - mat = p_mesh_instance->get_surface_override_material(surface_i); - } - if (p_mesh_instance->get_material_override().is_valid()) { - mat = p_mesh_instance->get_material_override(); - } - instance_materials.append(mat); - } gltf_mesh->set_instance_materials(instance_materials); gltf_mesh->set_mesh(current_mesh); gltf_mesh->set_blend_weights(blend_weights); @@ -5309,11 +5302,22 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd Ref<ImporterMesh> mesh; mesh.instantiate(); { - Ref<Mesh> csg_mesh = csg->get_meshes()[1]; - + Ref<ArrayMesh> csg_mesh = csg->get_meshes()[1]; for (int32_t surface_i = 0; surface_i < csg_mesh->get_surface_count(); surface_i++) { Array array = csg_mesh->surface_get_arrays(surface_i); - Ref<Material> mat = csg_mesh->surface_get_material(surface_i); + + Ref<Material> mat; + + Ref<Material> mat_override = csg->get_material_override(); + if (mat_override.is_valid()) { + mat = mat_override; + } + + Ref<Material> mat_surface_override = csg_mesh->surface_get_material(surface_i); + if (mat_surface_override.is_valid() && mat.is_null()) { + mat = mat_surface_override; + } + String mat_name; if (mat.is_valid()) { mat_name = mat->get_name(); @@ -5321,6 +5325,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd // Assign default material when no material is assigned. mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); } + mesh->add_surface(csg_mesh->surface_get_primitive_type(surface_i), array, csg_mesh->surface_get_blend_shape_arrays(surface_i), csg_mesh->surface_get_lods(surface_i), mat, mat_name, csg_mesh->surface_get_format(surface_i)); @@ -5334,7 +5339,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd GLTFMeshIndex mesh_i = p_state->meshes.size(); p_state->meshes.push_back(gltf_mesh); p_gltf_node->mesh = mesh_i; - p_gltf_node->transform = csg->get_meshes()[0]; + p_gltf_node->transform = csg->get_transform(); p_gltf_node->set_original_name(csg->get_name()); p_gltf_node->set_name(_gen_unique_name(p_state, csg->get_name())); } diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp index b02f3082ab..f69a907be9 100644 --- a/modules/openxr/scene/openxr_composition_layer.cpp +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -151,6 +151,16 @@ void OpenXRCompositionLayer::update_fallback_mesh() { should_update_fallback_mesh = true; } +XrPosef OpenXRCompositionLayer::get_openxr_pose() { + Transform3D reference_frame = XRServer::get_singleton()->get_reference_frame(); + Transform3D transform = reference_frame.inverse() * get_transform(); + Quaternion quat(transform.basis.orthonormalized()); + return { + { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }, + { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z } + }; +} + bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) { for (const OpenXRCompositionLayer *other_composition_layer : composition_layer_nodes) { if (other_composition_layer != this && other_composition_layer->is_inside_tree() && other_composition_layer->get_layer_viewport() == p_viewport) { diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h index 6792364295..55cae27d23 100644 --- a/modules/openxr/scene/openxr_composition_layer.h +++ b/modules/openxr/scene/openxr_composition_layer.h @@ -77,6 +77,8 @@ protected: void update_fallback_mesh(); + XrPosef get_openxr_pose(); + static Vector<OpenXRCompositionLayer *> composition_layer_nodes; bool is_viewport_in_use(SubViewport *p_viewport); diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp index 728ba71006..6c8d2fc885 100644 --- a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp @@ -52,6 +52,7 @@ OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() { aspect_ratio, // aspectRatio }; openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); + XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerCylinder::update_transform)); } OpenXRCompositionLayerCylinder::~OpenXRCompositionLayerCylinder() { @@ -131,14 +132,15 @@ Ref<Mesh> OpenXRCompositionLayerCylinder::_create_fallback_mesh() { void OpenXRCompositionLayerCylinder::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - Transform3D transform = get_transform(); - Quaternion quat(transform.basis.orthonormalized()); - composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; - composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + update_transform(); } break; } } +void OpenXRCompositionLayerCylinder::update_transform() { + composition_layer.pose = get_openxr_pose(); +} + void OpenXRCompositionLayerCylinder::set_radius(float p_radius) { ERR_FAIL_COND(p_radius <= 0); radius = p_radius; diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.h b/modules/openxr/scene/openxr_composition_layer_cylinder.h index bb1d242267..9bd5a42d36 100644 --- a/modules/openxr/scene/openxr_composition_layer_cylinder.h +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.h @@ -50,6 +50,8 @@ protected: void _notification(int p_what); + void update_transform(); + virtual Ref<Mesh> _create_fallback_mesh() override; public: diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.cpp b/modules/openxr/scene/openxr_composition_layer_equirect.cpp index 14cfea8da6..b6f5d27ffe 100644 --- a/modules/openxr/scene/openxr_composition_layer_equirect.cpp +++ b/modules/openxr/scene/openxr_composition_layer_equirect.cpp @@ -53,6 +53,7 @@ OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() { -lower_vertical_angle, // lowerVerticalAngle }; openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); + XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerEquirect::update_transform)); } OpenXRCompositionLayerEquirect::~OpenXRCompositionLayerEquirect() { @@ -139,14 +140,15 @@ Ref<Mesh> OpenXRCompositionLayerEquirect::_create_fallback_mesh() { void OpenXRCompositionLayerEquirect::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - Transform3D transform = get_transform(); - Quaternion quat(transform.basis.orthonormalized()); - composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; - composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + update_transform(); } break; } } +void OpenXRCompositionLayerEquirect::update_transform() { + composition_layer.pose = get_openxr_pose(); +} + void OpenXRCompositionLayerEquirect::set_radius(float p_radius) { ERR_FAIL_COND(p_radius <= 0); radius = p_radius; diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.h b/modules/openxr/scene/openxr_composition_layer_equirect.h index 66f8b0a91c..af6cd92cbe 100644 --- a/modules/openxr/scene/openxr_composition_layer_equirect.h +++ b/modules/openxr/scene/openxr_composition_layer_equirect.h @@ -51,6 +51,8 @@ protected: void _notification(int p_what); + void update_transform(); + virtual Ref<Mesh> _create_fallback_mesh() override; public: diff --git a/modules/openxr/scene/openxr_composition_layer_quad.cpp b/modules/openxr/scene/openxr_composition_layer_quad.cpp index 8c5b8ec26b..21919496d6 100644 --- a/modules/openxr/scene/openxr_composition_layer_quad.cpp +++ b/modules/openxr/scene/openxr_composition_layer_quad.cpp @@ -50,6 +50,7 @@ OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() { { (float)quad_size.x, (float)quad_size.y }, // size }; openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); + XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerQuad::update_transform)); } OpenXRCompositionLayerQuad::~OpenXRCompositionLayerQuad() { @@ -72,14 +73,15 @@ Ref<Mesh> OpenXRCompositionLayerQuad::_create_fallback_mesh() { void OpenXRCompositionLayerQuad::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - Transform3D transform = get_transform(); - Quaternion quat(transform.basis.orthonormalized()); - composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; - composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + update_transform(); } break; } } +void OpenXRCompositionLayerQuad::update_transform() { + composition_layer.pose = get_openxr_pose(); +} + void OpenXRCompositionLayerQuad::set_quad_size(const Size2 &p_size) { quad_size = p_size; composition_layer.size = { (float)quad_size.x, (float)quad_size.y }; diff --git a/modules/openxr/scene/openxr_composition_layer_quad.h b/modules/openxr/scene/openxr_composition_layer_quad.h index 21bb9b2d85..0c3172dbb2 100644 --- a/modules/openxr/scene/openxr_composition_layer_quad.h +++ b/modules/openxr/scene/openxr_composition_layer_quad.h @@ -47,6 +47,8 @@ protected: void _notification(int p_what); + void update_transform(); + virtual Ref<Mesh> _create_fallback_mesh() override; public: diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 6a452f08fa..769d97694a 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -541,6 +541,44 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ } strnew += lines[i].replace("$interface_orientations", orientations); + } else if (lines[i].contains("$ipad_interface_orientations")) { + String orientations; + const DisplayServer::ScreenOrientation screen_orientation = + DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))); + + switch (screen_orientation) { + case DisplayServer::SCREEN_LANDSCAPE: + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + break; + case DisplayServer::SCREEN_PORTRAIT: + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + break; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + // Allow both landscape orientations depending on sensor direction. + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + break; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + // Allow both portrait orientations depending on sensor direction. + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + break; + case DisplayServer::SCREEN_SENSOR: + // Allow all screen orientations depending on sensor direction. + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + break; + } + + strnew += lines[i].replace("$ipad_interface_orientations", orientations); } else if (lines[i].contains("$camera_usage_description")) { String description = p_preset->get("privacy/camera_usage_description"); strnew += lines[i].replace("$camera_usage_description", description) + "\n"; diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm index 6f6c04c2c8..787e767109 100644 --- a/platform/ios/view_controller.mm +++ b/platform/ios/view_controller.mm @@ -258,7 +258,11 @@ case DisplayServer::SCREEN_PORTRAIT: return UIInterfaceOrientationMaskPortrait; case DisplayServer::SCREEN_REVERSE_LANDSCAPE: - return UIInterfaceOrientationMaskLandscapeRight; + if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { + return UIInterfaceOrientationMaskLandscapeLeft; + } else { + return UIInterfaceOrientationMaskLandscapeRight; + } case DisplayServer::SCREEN_REVERSE_PORTRAIT: return UIInterfaceOrientationMaskPortraitUpsideDown; case DisplayServer::SCREEN_SENSOR_LANDSCAPE: @@ -268,7 +272,11 @@ case DisplayServer::SCREEN_SENSOR: return UIInterfaceOrientationMaskAll; case DisplayServer::SCREEN_LANDSCAPE: - return UIInterfaceOrientationMaskLandscapeLeft; + if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { + return UIInterfaceOrientationMaskLandscapeRight; + } else { + return UIInterfaceOrientationMaskLandscapeLeft; + } } } diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index ab44e57d05..bad9de5daa 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -162,8 +162,19 @@ void NavigationRegion2D::_notification(int p_what) { set_physics_process_internal(true); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { +#ifdef DEBUG_ENABLED + if (debug_instance_rid.is_valid()) { + RS::get_singleton()->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree()); + } +#endif // DEBUG_ENABLED + } break; + case NOTIFICATION_EXIT_TREE: { _region_exit_navigation_map(); +#ifdef DEBUG_ENABLED + _free_debug(); +#endif // DEBUG_ENABLED } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -189,6 +200,9 @@ void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_ } navigation_polygon = p_navigation_polygon; +#ifdef DEBUG_ENABLED + debug_mesh_dirty = true; +#endif // DEBUG_ENABLED NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, p_navigation_polygon); if (navigation_polygon.is_valid()) { @@ -420,12 +434,42 @@ void NavigationRegion2D::_region_update_transform() { #ifdef DEBUG_ENABLED void NavigationRegion2D::_update_debug_mesh() { - Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); - if (navigation_polygon_vertices.size() < 3) { + if (!is_inside_tree()) { + _free_debug(); return; } const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); + RenderingServer *rs = RenderingServer::get_singleton(); + + if (!debug_instance_rid.is_valid()) { + debug_instance_rid = rs->canvas_item_create(); + } + if (!debug_mesh_rid.is_valid()) { + debug_mesh_rid = rs->mesh_create(); + } + + const Transform2D region_gt = get_global_transform(); + + rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas()); + rs->canvas_item_set_transform(debug_instance_rid, region_gt); + + if (!debug_mesh_dirty) { + return; + } + + rs->mesh_clear(debug_mesh_rid); + debug_mesh_dirty = false; + + const Vector<Vector2> &vertices = navigation_polygon->get_vertices(); + if (vertices.size() < 3) { + return; + } + + int polygon_count = navigation_polygon->get_polygon_count(); + if (polygon_count == 0) { + return; + } bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color(); bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines(); @@ -438,39 +482,109 @@ void NavigationRegion2D::_update_debug_mesh() { debug_edge_color = ns2d->get_debug_navigation_geometry_edge_disabled_color(); } + int vertex_count = 0; + int line_count = 0; + + for (int i = 0; i < polygon_count; i++) { + const Vector<int> &polygon = navigation_polygon->get_polygon(i); + int polygon_size = polygon.size(); + if (polygon_size < 3) { + continue; + } + line_count += polygon_size * 2; + vertex_count += (polygon_size - 2) * 3; + } + + Vector<Vector2> face_vertex_array; + face_vertex_array.resize(vertex_count); + + Vector<Color> face_color_array; + if (enabled_geometry_face_random_color) { + face_color_array.resize(vertex_count); + } + + Vector<Vector2> line_vertex_array; + if (enabled_edge_lines) { + line_vertex_array.resize(line_count); + } + RandomPCG rand; + Color polygon_color = debug_face_color; - for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { - // An array of vertices for this polygon. - Vector<int> polygon = navigation_polygon->get_polygon(i); - Vector<Vector2> debug_polygon_vertices; - debug_polygon_vertices.resize(polygon.size()); - for (int j = 0; j < polygon.size(); j++) { - ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); - debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]]; + int face_vertex_index = 0; + int line_vertex_index = 0; + + Vector2 *face_vertex_array_ptrw = face_vertex_array.ptrw(); + Color *face_color_array_ptrw = face_color_array.ptrw(); + Vector2 *line_vertex_array_ptrw = line_vertex_array.ptrw(); + + for (int polygon_index = 0; polygon_index < polygon_count; polygon_index++) { + const Vector<int> &polygon_indices = navigation_polygon->get_polygon(polygon_index); + int polygon_indices_size = polygon_indices.size(); + if (polygon_indices_size < 3) { + continue; } - // Generate the polygon color, slightly randomly modified from the settings one. - Color random_variation_color = debug_face_color; if (enabled_geometry_face_random_color) { - random_variation_color.set_hsv( - debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, - debug_face_color.get_s(), - debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + // Generate the polygon color, slightly randomly modified from the settings one. + polygon_color.set_hsv(debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, debug_face_color.get_s(), debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + polygon_color.a = debug_face_color.a; } - random_variation_color.a = debug_face_color.a; - Vector<Color> debug_face_colors; - debug_face_colors.push_back(random_variation_color); - RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), debug_polygon_vertices, debug_face_colors); + for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size - 2; polygon_indices_index++) { + face_vertex_array_ptrw[face_vertex_index] = vertices[polygon_indices[0]]; + face_vertex_array_ptrw[face_vertex_index + 1] = vertices[polygon_indices[polygon_indices_index + 1]]; + face_vertex_array_ptrw[face_vertex_index + 2] = vertices[polygon_indices[polygon_indices_index + 2]]; + if (enabled_geometry_face_random_color) { + face_color_array_ptrw[face_vertex_index] = polygon_color; + face_color_array_ptrw[face_vertex_index + 1] = polygon_color; + face_color_array_ptrw[face_vertex_index + 2] = polygon_color; + } + face_vertex_index += 3; + } if (enabled_edge_lines) { - Vector<Color> debug_edge_colors; - debug_edge_colors.push_back(debug_edge_color); - debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline. - RS::get_singleton()->canvas_item_add_polyline(get_canvas_item(), debug_polygon_vertices, debug_edge_colors); + for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size; polygon_indices_index++) { + line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index]]; + line_vertex_index += 1; + if (polygon_indices_index + 1 == polygon_indices_size) { + line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[0]]; + line_vertex_index += 1; + } else { + line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index + 1]]; + line_vertex_index += 1; + } + } } } + + if (!enabled_geometry_face_random_color) { + face_color_array.resize(face_vertex_array.size()); + face_color_array.fill(debug_face_color); + } + + Array face_mesh_array; + face_mesh_array.resize(Mesh::ARRAY_MAX); + face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array; + face_mesh_array[Mesh::ARRAY_COLOR] = face_color_array; + + rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_TRIANGLES, face_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + + if (enabled_edge_lines) { + Vector<Color> line_color_array; + line_color_array.resize(line_vertex_array.size()); + line_color_array.fill(debug_edge_color); + + Array line_mesh_array; + line_mesh_array.resize(Mesh::ARRAY_MAX); + line_mesh_array[Mesh::ARRAY_VERTEX] = line_vertex_array; + line_mesh_array[Mesh::ARRAY_COLOR] = line_color_array; + + rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_LINES, line_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + } + + rs->canvas_item_add_mesh(debug_instance_rid, debug_mesh_rid, Transform2D()); + rs->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree()); } #endif // DEBUG_ENABLED @@ -512,3 +626,19 @@ void NavigationRegion2D::_update_debug_baking_rect() { } } #endif // DEBUG_ENABLED + +#ifdef DEBUG_ENABLED +void NavigationRegion2D::_free_debug() { + RenderingServer *rs = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rs); + if (debug_instance_rid.is_valid()) { + rs->canvas_item_clear(debug_instance_rid); + rs->free(debug_instance_rid); + debug_instance_rid = RID(); + } + if (debug_mesh_rid.is_valid()) { + rs->free(debug_mesh_rid); + debug_mesh_rid = RID(); + } +} +#endif // DEBUG_ENABLED diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 5a86dd607d..52101cb93e 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -52,6 +52,12 @@ class NavigationRegion2D : public Node2D { #ifdef DEBUG_ENABLED private: + RID debug_mesh_rid; + RID debug_instance_rid; + + bool debug_mesh_dirty = true; + + void _free_debug(); void _update_debug_mesh(); void _update_debug_edge_connections_mesh(); void _update_debug_baking_rect(); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index d4c6ca3ea0..e600de6b8b 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -501,6 +501,7 @@ AnimationMixer::AnimationCallbackModeMethod AnimationMixer::get_callback_mode_me void AnimationMixer::set_callback_mode_discrete(AnimationCallbackModeDiscrete p_mode) { callback_mode_discrete = p_mode; + _clear_caches(); emit_signal(SNAME("mixer_updated")); } @@ -688,7 +689,7 @@ bool AnimationMixer::_update_caches() { track_value->init_value = anim->track_get_key_value(i, 0); track_value->init_value.zero(); - track_value->init_use_continuous = callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS; + track_value->is_init = false; // Can't interpolate them, need to convert. track_value->is_variant_interpolatable = Animation::is_variant_interpolatable(track_value->init_value); @@ -698,7 +699,6 @@ bool AnimationMixer::_update_caches() { int rt = reset_anim->find_track(path, track_src_type); if (rt >= 0) { if (track_src_type == Animation::TYPE_VALUE) { - track_value->init_use_continuous = track_value->init_use_continuous || (reset_anim->value_track_get_update_mode(rt) != Animation::UPDATE_DISCRETE); // Take precedence Force Continuous. if (reset_anim->track_get_key_count(rt) > 0) { track_value->init_value = reset_anim->track_get_key_value(rt, 0); } @@ -1006,7 +1006,7 @@ void AnimationMixer::_blend_init() { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); t->value = Animation::cast_to_blendwise(t->init_value); t->element_size = t->init_value.is_string() ? (real_t)(t->init_value.operator String()).length() : 0; - t->use_continuous = t->init_use_continuous; + t->use_continuous = false; t->use_discrete = false; } break; case Animation::TYPE_AUDIO: { @@ -1462,12 +1462,12 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { t->value = Animation::blend_variant(t->value, value, blend); } } else { - t->use_discrete = true; if (seeked) { int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); if (idx < 0) { continue; } + t->use_discrete = true; Variant value = a->track_get_key_value(i, idx); value = post_process_key_value(a, i, value, t->object_id); Object *t_obj = ObjectDB::get_instance(t->object_id); @@ -1478,6 +1478,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { List<int> indices; a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { + t->use_discrete = true; Variant value = a->track_get_key_value(i, F); value = post_process_key_value(a, i, value, t->object_id); Object *t_obj = ObjectDB::get_instance(t->object_id); @@ -1682,7 +1683,8 @@ void AnimationMixer::_blend_apply() { // Finally, set the tracks. for (const KeyValue<Animation::TypeHash, TrackCache *> &K : track_cache) { TrackCache *track = K.value; - if (!deterministic && Math::is_zero_approx(track->total_weight)) { + bool is_zero_amount = Math::is_zero_approx(track->total_weight); + if (!deterministic && is_zero_amount) { continue; } switch (track->type) { @@ -1742,10 +1744,24 @@ void AnimationMixer::_blend_apply() { case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); - if (!t->is_variant_interpolatable || !t->use_continuous || (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT && t->use_discrete)) { + if (t->use_discrete && !t->use_continuous) { + t->is_init = true; // If only disctere value is applied, no more RESET. + } + + if ((t->is_init && (is_zero_amount || !t->use_continuous)) || + (callback_mode_discrete != ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS && + !is_zero_amount && + callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT && + t->use_discrete)) { break; // Don't overwrite the value set by UPDATE_DISCRETE. } + if (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { + t->is_init = false; // Always update in Force Continuous. + } else { + t->is_init = !t->use_continuous; // If there is no Continuous in non-Force Continuous type, it means RESET. + } + // Trim unused elements if init array/string is not blended. if (t->value.is_array()) { int actual_blended_size = (int)Math::round(Math::abs(t->element_size.operator real_t())); diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index b7898cffc9..089a210193 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -126,7 +126,7 @@ protected: /* ---- General settings for animation ---- */ AnimationCallbackModeProcess callback_mode_process = ANIMATION_CALLBACK_MODE_PROCESS_IDLE; AnimationCallbackModeMethod callback_mode_method = ANIMATION_CALLBACK_MODE_METHOD_DEFERRED; - AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT; + AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE; int audio_max_polyphony = 32; NodePath root_node; @@ -224,7 +224,7 @@ protected: Vector<StringName> subpath; // TODO: There are many boolean, can be packed into one integer. - bool init_use_continuous = false; + bool is_init = false; bool use_continuous = false; bool use_discrete = false; bool is_using_angle = false; @@ -237,7 +237,7 @@ protected: init_value(p_other.init_value), value(p_other.value), subpath(p_other.subpath), - init_use_continuous(p_other.init_use_continuous), + is_init(p_other.is_init), use_continuous(p_other.use_continuous), use_discrete(p_other.use_discrete), is_using_angle(p_other.is_using_angle), diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 435776843c..0c24d79ad7 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -447,10 +447,10 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo } else { if (p_from_end && c.current.pos == 0) { // Animation reset but played backwards, set position to the end. - c.current.pos = c.current.from->animation->get_length(); + seek(c.current.from->animation->get_length(), true, true); } else if (!p_from_end && c.current.pos == c.current.from->animation->get_length()) { // Animation resumed but already ended, set position to the beginning. - c.current.pos = 0; + seek(0, true, true); } else if (playing) { return; } diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 58961d370c..3d8be38fbd 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -47,7 +47,7 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { } void AcceptDialog::_parent_focused() { - if (!is_exclusive() && get_flag(FLAG_POPUP)) { + if (popped_up && !is_exclusive() && get_flag(FLAG_POPUP)) { _cancel_pressed(); } } @@ -71,6 +71,7 @@ void AcceptDialog::_notification(int p_what) { parent_visible->connect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); } } else { + popped_up = false; if (parent_visible) { parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); parent_visible = nullptr; @@ -78,6 +79,14 @@ void AcceptDialog::_notification(int p_what) { } } break; + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + if (!is_in_edited_scene_root()) { + if (has_focus()) { + popped_up = true; + } + } + } break; + case NOTIFICATION_THEME_CHANGED: { bg_panel->add_theme_style_override("panel", theme_cache.panel_style); @@ -114,8 +123,14 @@ void AcceptDialog::_text_submitted(const String &p_text) { _ok_pressed(); } +void AcceptDialog::_post_popup() { + Window::_post_popup(); + popped_up = true; +} + void AcceptDialog::_ok_pressed() { if (hide_on_ok) { + popped_up = false; set_visible(false); } ok_pressed(); @@ -124,6 +139,7 @@ void AcceptDialog::_ok_pressed() { } void AcceptDialog::_cancel_pressed() { + popped_up = false; Window *parent_window = parent_visible; if (parent_visible) { parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 12b48c903a..404237bfd8 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -51,6 +51,7 @@ class AcceptDialog : public Window { HBoxContainer *buttons_hbox = nullptr; Button *ok_button = nullptr; + bool popped_up = false; bool hide_on_ok = true; bool close_on_escape = true; @@ -72,6 +73,7 @@ class AcceptDialog : public Window { protected: virtual Size2 _get_contents_minimum_size() const override; virtual void _input_from_window(const Ref<InputEvent> &p_event) override; + virtual void _post_popup() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index c9f3fc1dfe..6c2a61d255 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -1299,18 +1299,26 @@ List<Ref<GraphEdit::Connection>> GraphEdit::get_connections_intersecting_with_re return intersecting_connections; } -void GraphEdit::_draw_minimap_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_from_color, const Color &p_to_color) { - const Vector<Vector2> &points = get_connection_line(p_from, p_to); +void GraphEdit::_draw_minimap_connection_line(const Vector2 &p_from_graph_position, const Vector2 &p_to_graph_position, const Color &p_from_color, const Color &p_to_color) { + Vector<Vector2> points = get_connection_line(p_from_graph_position, p_to_graph_position); + ERR_FAIL_COND_MSG(points.size() < 2, "\"_get_connection_line()\" returned an invalid line."); + // Convert to minimap points. + for (Vector2 &point : points) { + point = minimap->_convert_from_graph_position(point) + minimap->minimap_offset; + } + + // Setup polyline colors. LocalVector<Color> colors; colors.reserve(points.size()); - - float length_inv = 1.0 / (p_from).distance_to(p_to); + const Vector2 &from = points[0]; + const Vector2 &to = points[points.size() - 1]; + float length_inv = 1.0 / (from).distance_to(to); for (const Vector2 &point : points) { - float normalized_curve_position = (p_from).distance_to(point) * length_inv; + float normalized_curve_position = from.distance_to(point) * length_inv; colors.push_back(p_from_color.lerp(p_to_color, normalized_curve_position)); } - p_where->draw_polyline_colors(points, colors, 0.5, lines_antialiased); + minimap->draw_polyline_colors(points, colors, 0.5, lines_antialiased); } void GraphEdit::_update_connections() { @@ -1565,8 +1573,8 @@ void GraphEdit::_minimap_draw() { // Draw node connections. for (const Ref<Connection> &c : connections) { - Vector2 from_position = minimap->_convert_from_graph_position(c->_cache.from_pos * zoom - graph_offset) + minimap_offset; - Vector2 to_position = minimap->_convert_from_graph_position(c->_cache.to_pos * zoom - graph_offset) + minimap_offset; + Vector2 from_graph_position = c->_cache.from_pos * zoom - graph_offset; + Vector2 to_graph_position = c->_cache.to_pos * zoom - graph_offset; Color from_color = c->_cache.from_color; Color to_color = c->_cache.to_color; @@ -1574,7 +1582,8 @@ void GraphEdit::_minimap_draw() { from_color = from_color.lerp(theme_cache.activity_color, c->activity); to_color = to_color.lerp(theme_cache.activity_color, c->activity); } - _draw_minimap_connection_line(minimap, from_position, to_position, from_color, to_color); + + _draw_minimap_connection_line(from_graph_position, to_graph_position, from_color, to_color); } // Draw the "camera" viewport. diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index eeda9ae200..20c98c462c 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -328,7 +328,7 @@ private: void _top_connection_layer_input(const Ref<InputEvent> &p_ev); float _get_shader_line_width(); - void _draw_minimap_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color); + void _draw_minimap_connection_line(const Vector2 &p_from_graph_position, const Vector2 &p_to_graph_position, const Color &p_from_color, const Color &p_to_color); void _invalidate_connection_line_cache(); void _update_top_connection_layer(); void _update_connections(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index f6cfe6ab18..c715aceb0b 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -287,7 +287,7 @@ void SubViewportContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_stretch_shrink"), &SubViewportContainer::get_stretch_shrink); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink"), "set_stretch_shrink", "get_stretch_shrink"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), "set_stretch_shrink", "get_stretch_shrink"); GDVIRTUAL_BIND(_propagate_input_event, "event"); } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index a36eb0652e..1dd00fab4d 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -4237,8 +4237,11 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { } Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds) const { - float rows = p_pos.y; - rows -= theme_cache.style_normal->get_margin(SIDE_TOP); + float rows = p_pos.y - theme_cache.style_normal->get_margin(SIDE_TOP); + if (!editable) { + rows -= theme_cache.style_readonly->get_offset().y / 2; + rows += theme_cache.style_normal->get_offset().y / 2; + } rows /= get_line_height(); rows += _get_v_scroll_offset(); int first_vis_line = get_first_visible_line(); @@ -4269,6 +4272,10 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ int col = 0; int colx = p_pos.x - (theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); colx += first_visible_col; + if (!editable) { + colx -= theme_cache.style_readonly->get_offset().x / 2; + colx += theme_cache.style_normal->get_offset().x / 2; + } col = _get_char_pos_for_line(colx, row, wrap_index); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { // Move back one if we are at the end of the row. diff --git a/scene/main/window.cpp b/scene/main/window.cpp index e9a7123da0..addbd6078a 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -306,10 +306,21 @@ String Window::get_title() const { return title; } +void Window::_settings_changed() { + if (visible && initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE && is_in_edited_scene_root()) { + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + position = (screen_size - size) / 2; + if (embedder) { + embedder->_sub_window_update(this); + } + } +} + void Window::set_initial_position(Window::WindowInitialPosition p_initial_position) { ERR_MAIN_THREAD_GUARD; initial_position = p_initial_position; + _settings_changed(); notify_property_list_changed(); } @@ -829,7 +840,12 @@ void Window::set_visible(bool p_visible) { if (visible) { embedder = embedder_vp; if (initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE) { - position = (embedder->get_visible_rect().size - size) / 2; + if (is_in_edited_scene_root()) { + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + position = (screen_size - size) / 2; + } else { + position = (embedder->get_visible_rect().size - size) / 2; + } } embedder->_sub_window_register(this); RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_PARENT_VISIBLE); @@ -1265,6 +1281,12 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { + if (is_in_edited_scene_root()) { + if (!ProjectSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &Window::_settings_changed))) { + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Window::_settings_changed)); + } + } + bool embedded = false; { embedder = get_embedder(); @@ -1280,7 +1302,12 @@ void Window::_notification(int p_what) { // Create as embedded. if (embedder) { if (initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE) { - position = (embedder->get_visible_rect().size - size) / 2; + if (is_in_edited_scene_root()) { + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + position = (screen_size - size) / 2; + } else { + position = (embedder->get_visible_rect().size - size) / 2; + } } embedder->_sub_window_register(this); RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_PARENT_VISIBLE); @@ -1377,6 +1404,10 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_EXIT_TREE: { + if (ProjectSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &Window::_settings_changed))) { + ProjectSettings::get_singleton()->disconnect("settings_changed", callable_mp(this, &Window::_settings_changed)); + } + set_theme_context(nullptr, false); if (transient) { diff --git a/scene/main/window.h b/scene/main/window.h index ffcf50ccdd..33d593711f 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -214,6 +214,8 @@ private: int resize_margin = 0; } theme_cache; + void _settings_changed(); + Viewport *embedder = nullptr; Transform2D window_transform; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 27da825bfe..8e49a8b56f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -822,7 +822,18 @@ uniform float distance_fade_max : hint_range(0.0, 4096.0, 0.01); )"; } - if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + if (flags[FLAG_ALBEDO_TEXTURE_MSDF] && flags[FLAG_UV1_USE_TRIPLANAR]) { + String msg = "MSDF is not supported on triplanar materials. Ignoring MSDF in favor of triplanar mapping."; + if (textures[TEXTURE_ALBEDO].is_valid()) { + WARN_PRINT(vformat("%s (albedo %s): " + msg, get_path(), textures[TEXTURE_ALBEDO]->get_path())); + } else if (!get_path().is_empty()) { + WARN_PRINT(vformat("%s: " + msg, get_path())); + } else { + WARN_PRINT(msg); + } + } + + if (flags[FLAG_ALBEDO_TEXTURE_MSDF] && !flags[FLAG_UV1_USE_TRIPLANAR]) { code += R"( uniform float msdf_pixel_range : hint_range(1.0, 100.0, 1.0); uniform float msdf_outline_size : hint_range(0.0, 250.0, 1.0); @@ -1271,7 +1282,7 @@ void vertex() {)"; code += "}\n"; - if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + if (flags[FLAG_ALBEDO_TEXTURE_MSDF] && !flags[FLAG_UV1_USE_TRIPLANAR]) { code += R"( float msdf_median(float r, float g, float b, float a) { return min(max(min(r, g), min(max(r, g), b)), a); @@ -1414,7 +1425,7 @@ void fragment() {)"; } } - if (flags[FLAG_ALBEDO_TEXTURE_MSDF]) { + if (flags[FLAG_ALBEDO_TEXTURE_MSDF] && !flags[FLAG_UV1_USE_TRIPLANAR]) { code += R"( { // Albedo Texture MSDF: Enabled @@ -1427,11 +1438,7 @@ void fragment() {)"; if (flags[FLAG_USE_POINT_SIZE]) { code += " vec2 dest_size = vec2(1.0) / fwidth(POINT_COORD);\n"; } else { - if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += " vec2 dest_size = vec2(1.0) / fwidth(uv1_triplanar_pos);\n"; - } else { - code += " vec2 dest_size = vec2(1.0) / fwidth(base_uv);\n"; - } + code += " vec2 dest_size = vec2(1.0) / fwidth(base_uv);\n"; } code += R"( float px_size = max(0.5 * dot(msdf_size, dest_size), 1.0); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 2e27ac9198..7c13e623c2 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -33,9 +33,7 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" #include "core/io/missing_resource.h" -#include "core/io/resource_format_binary.h" #include "core/object/script_language.h" -#include "core/version.h" // Version 2: Changed names for Basis, AABB, Vectors, etc. // Version 3: New string ID for ext/subresources, breaks forward compat. @@ -44,11 +42,6 @@ // For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray. #define FORMAT_VERSION_COMPAT 3 -#define BINARY_FORMAT_VERSION 4 - -#include "core/io/dir_access.h" -#include "core/version.h" - #define _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data()); /// @@ -1127,298 +1120,6 @@ void ResourceLoaderText::open(Ref<FileAccess> p_f, bool p_skip_first_tag) { rp.userdata = this; } -static void bs_save_unicode_string(Ref<FileAccess> p_f, const String &p_string, bool p_bit_on_len = false) { - CharString utf8 = p_string.utf8(); - if (p_bit_on_len) { - p_f->store_32((utf8.length() + 1) | 0x80000000); - } else { - p_f->store_32(utf8.length() + 1); - } - p_f->store_buffer((const uint8_t *)utf8.get_data(), utf8.length() + 1); -} - -Error ResourceLoaderText::save_as_binary(const String &p_path) { - if (error) { - return error; - } - - Ref<FileAccess> wf = FileAccess::open(p_path, FileAccess::WRITE); - if (wf.is_null()) { - return ERR_CANT_OPEN; - } - - //save header compressed - static const uint8_t header[4] = { 'R', 'S', 'R', 'C' }; - wf->store_buffer(header, 4); - - wf->store_32(0); //endianness, little endian - wf->store_32(0); //64 bits file, false for now - wf->store_32(VERSION_MAJOR); - wf->store_32(VERSION_MINOR); - static const int save_format_version = BINARY_FORMAT_VERSION; - wf->store_32(save_format_version); - - bs_save_unicode_string(wf, is_scene ? "PackedScene" : resource_type); - wf->store_64(0); //offset to import metadata, this is no longer used - - wf->store_32(ResourceFormatSaverBinaryInstance::FORMAT_FLAG_NAMED_SCENE_IDS | ResourceFormatSaverBinaryInstance::FORMAT_FLAG_UIDS); - - wf->store_64(res_uid); - - for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { - wf->store_32(0); // reserved - } - - wf->store_32(0); //string table size, will not be in use - uint64_t ext_res_count_pos = wf->get_position(); - - wf->store_32(0); //zero ext resources, still parsing them - - //go with external resources - - DummyReadData dummy_read; - VariantParser::ResourceParser rp_new; - rp_new.ext_func = _parse_ext_resource_dummys; - rp_new.sub_func = _parse_sub_resource_dummys; - rp_new.userdata = &dummy_read; - - while (next_tag.name == "ext_resource") { - if (!next_tag.fields.has("path")) { - error = ERR_FILE_CORRUPT; - error_text = "Missing 'path' in external resource tag"; - _printerr(); - return error; - } - - if (!next_tag.fields.has("type")) { - error = ERR_FILE_CORRUPT; - error_text = "Missing 'type' in external resource tag"; - _printerr(); - return error; - } - - if (!next_tag.fields.has("id")) { - error = ERR_FILE_CORRUPT; - error_text = "Missing 'id' in external resource tag"; - _printerr(); - return error; - } - - String path = next_tag.fields["path"]; - String type = next_tag.fields["type"]; - String id = next_tag.fields["id"]; - ResourceUID::ID uid = ResourceUID::INVALID_ID; - if (next_tag.fields.has("uid")) { - String uidt = next_tag.fields["uid"]; - uid = ResourceUID::get_singleton()->text_to_id(uidt); - } - - bs_save_unicode_string(wf, type); - bs_save_unicode_string(wf, path); - wf->store_64(uid); - - int lindex = dummy_read.external_resources.size(); - Ref<DummyResource> dr; - dr.instantiate(); - dr->set_path("res://dummy" + itos(lindex)); //anything is good to detect it for saving as external - dummy_read.external_resources[dr] = lindex; - dummy_read.rev_external_resources[id] = dr; - - error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp_new); - - if (error) { - _printerr(); - return error; - } - } - - // save external resource table - wf->seek(ext_res_count_pos); - wf->store_32(dummy_read.external_resources.size()); - wf->seek_end(); - - //now, save resources to a separate file, for now - - uint64_t sub_res_count_pos = wf->get_position(); - wf->store_32(0); //zero sub resources, still parsing them - - String temp_file = p_path + ".temp"; - Vector<uint64_t> local_offsets; - Vector<uint64_t> local_pointers_pos; - { - Ref<FileAccess> wf2 = FileAccess::open(temp_file, FileAccess::WRITE); - if (wf2.is_null()) { - return ERR_CANT_OPEN; - } - - while (next_tag.name == "sub_resource" || next_tag.name == "resource") { - String type; - String id; - bool main_res; - - if (next_tag.name == "sub_resource") { - if (!next_tag.fields.has("type")) { - error = ERR_FILE_CORRUPT; - error_text = "Missing 'type' in external resource tag"; - _printerr(); - return error; - } - - if (!next_tag.fields.has("id")) { - error = ERR_FILE_CORRUPT; - error_text = "Missing 'id' in external resource tag"; - _printerr(); - return error; - } - - type = next_tag.fields["type"]; - id = next_tag.fields["id"]; - main_res = false; - - if (!dummy_read.resource_map.has(id)) { - Ref<DummyResource> dr; - dr.instantiate(); - dr->set_scene_unique_id(id); - dummy_read.resource_map[id] = dr; - uint32_t im_size = dummy_read.resource_index_map.size(); - dummy_read.resource_index_map.insert(dr, im_size); - } - - } else { - type = res_type; - String uid_text = ResourceUID::get_singleton()->id_to_text(res_uid); - id = type + "_" + uid_text.replace("uid://", "").replace("<invalid>", "0"); - main_res = true; - } - - local_offsets.push_back(wf2->get_position()); - - bs_save_unicode_string(wf, "local://" + id); - local_pointers_pos.push_back(wf->get_position()); - wf->store_64(0); //temp local offset - - bs_save_unicode_string(wf2, type); - uint64_t propcount_ofs = wf2->get_position(); - wf2->store_32(0); - - int prop_count = 0; - - while (true) { - String assign; - Variant value; - - error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new); - - if (error) { - if (main_res && error == ERR_FILE_EOF) { - next_tag.name = ""; //exit - break; - } - - _printerr(); - return error; - } - - if (!assign.is_empty()) { - HashMap<StringName, int> empty_string_map; //unused - bs_save_unicode_string(wf2, assign, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); - prop_count++; - - } else if (!next_tag.name.is_empty()) { - error = OK; - break; - } else { - error = ERR_FILE_CORRUPT; - error_text = "Premature end of file while parsing [sub_resource]"; - _printerr(); - return error; - } - } - - wf2->seek(propcount_ofs); - wf2->store_32(prop_count); - wf2->seek_end(); - } - - if (next_tag.name == "node") { - // This is a node, must save one more! - - if (!is_scene) { - error_text += "found the 'node' tag on a resource file!"; - _printerr(); - error = ERR_FILE_CORRUPT; - return error; - } - - Ref<PackedScene> packed_scene = _parse_node_tag(rp_new); - - if (!packed_scene.is_valid()) { - return error; - } - - error = OK; - //get it here - List<PropertyInfo> props; - packed_scene->get_property_list(&props); - - String id = "PackedScene_" + ResourceUID::get_singleton()->id_to_text(res_uid).replace("uid://", "").replace("<invalid>", "0"); - bs_save_unicode_string(wf, "local://" + id); - local_pointers_pos.push_back(wf->get_position()); - wf->store_64(0); //temp local offset - - local_offsets.push_back(wf2->get_position()); - bs_save_unicode_string(wf2, "PackedScene"); - uint64_t propcount_ofs = wf2->get_position(); - wf2->store_32(0); - - int prop_count = 0; - - for (const PropertyInfo &E : props) { - if (!(E.usage & PROPERTY_USAGE_STORAGE)) { - continue; - } - - String name = E.name; - Variant value = packed_scene->get(name); - - HashMap<StringName, int> empty_string_map; //unused - bs_save_unicode_string(wf2, name, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); - prop_count++; - } - - wf2->seek(propcount_ofs); - wf2->store_32(prop_count); - wf2->seek_end(); - } - } - - uint64_t offset_from = wf->get_position(); - wf->seek(sub_res_count_pos); //plus one because the saved one - wf->store_32(local_offsets.size()); - - for (int i = 0; i < local_offsets.size(); i++) { - wf->seek(local_pointers_pos[i]); - wf->store_64(local_offsets[i] + offset_from); - } - - wf->seek_end(); - - Vector<uint8_t> data = FileAccess::get_file_as_bytes(temp_file); - wf->store_buffer(data.ptr(), data.size()); - { - Ref<DirAccess> dar = DirAccess::open(temp_file.get_base_dir()); - ERR_FAIL_COND_V(dar.is_null(), FAILED); - - dar->remove(temp_file); - } - - wf->store_buffer((const uint8_t *)"RSRC", 4); //magic at end - - return OK; -} - Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) { if (error) { return error; @@ -1835,29 +1536,6 @@ Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const ResourceFormatLoaderText *ResourceFormatLoaderText::singleton = nullptr; -Error ResourceFormatLoaderText::convert_file_to_binary(const String &p_src_path, const String &p_dst_path) { - Error err; - Ref<FileAccess> f = FileAccess::open(p_src_path, FileAccess::READ, &err); - - ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_OPEN, "Cannot open file '" + p_src_path + "'."); - - ResourceLoaderText loader; - const String &path = p_src_path; - loader.local_path = ProjectSettings::get_singleton()->localize_path(path); - loader.res_path = loader.local_path; - loader.open(f); - return loader.save_as_binary(p_dst_path); -} - -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ -/*****************************************************************************************************/ /*****************************************************************************************************/ String ResourceFormatSaverTextInstance::_write_resources(void *ud, const Ref<Resource> &p_resource) { diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 41363fd975..b5542f77ba 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -87,11 +87,6 @@ class ResourceLoaderText { Error _parse_sub_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str); Error _parse_ext_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str); - // for converter - class DummyResource : public Resource { - public: - }; - struct DummyReadData { bool no_placeholders = false; HashMap<Ref<Resource>, int> external_resources; @@ -133,7 +128,6 @@ public: Error rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const HashMap<String, String> &p_map); Error get_classes_used(HashSet<StringName> *r_classes); - Error save_as_binary(const String &p_path); ResourceLoaderText(); }; @@ -152,8 +146,6 @@ public: virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override; virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override; - static Error convert_file_to_binary(const String &p_src_path, const String &p_dst_path); - ResourceFormatLoaderText() { singleton = this; } }; diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index 2cfe98ea1e..a4e68afee0 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -98,6 +98,8 @@ void XRServer::_bind_methods() { BIND_ENUM_CONSTANT(RESET_BUT_KEEP_TILT); BIND_ENUM_CONSTANT(DONT_RESET_ROTATION); + ADD_SIGNAL(MethodInfo("reference_frame_changed")); + ADD_SIGNAL(MethodInfo("interface_added", PropertyInfo(Variant::STRING_NAME, "interface_name"))); ADD_SIGNAL(MethodInfo("interface_removed", PropertyInfo(Variant::STRING_NAME, "interface_name"))); @@ -213,11 +215,13 @@ void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) { reference_frame = new_reference_frame.inverse(); set_render_reference_frame(reference_frame); + emit_signal(SNAME("reference_frame_changed")); } void XRServer::clear_reference_frame() { reference_frame = Transform3D(); set_render_reference_frame(reference_frame); + emit_signal(SNAME("reference_frame_changed")); } void XRServer::_set_render_reference_frame(const Transform3D &p_reference_frame) { diff --git a/tests/core/math/test_transform_2d.h b/tests/core/math/test_transform_2d.h index 36d27ce7a9..6d3c80e5ca 100644 --- a/tests/core/math/test_transform_2d.h +++ b/tests/core/math/test_transform_2d.h @@ -45,48 +45,132 @@ Transform2D identity() { return Transform2D(); } +TEST_CASE("[Transform2D] Default constructor") { + Transform2D default_constructor = Transform2D(); + CHECK(default_constructor == Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0, 0))); +} + +TEST_CASE("[Transform2D] Copy constructor") { + Transform2D T = create_dummy_transform(); + Transform2D copy_constructor = Transform2D(T); + CHECK(T == copy_constructor); +} + +TEST_CASE("[Transform2D] Constructor from angle and position") { + constexpr float ROTATION = Math_PI / 4; + const Vector2 TRANSLATION = Vector2(20, -20); + + const Transform2D test = Transform2D(ROTATION, TRANSLATION); + const Transform2D expected = Transform2D().rotated(ROTATION).translated(TRANSLATION); + CHECK(test == expected); +} + +TEST_CASE("[Transform2D] Constructor from angle, scale, skew and position") { + constexpr float ROTATION = Math_PI / 2; + const Vector2 SCALE = Vector2(2, 0.5); + constexpr float SKEW = Math_PI / 4; + const Vector2 TRANSLATION = Vector2(30, 0); + + const Transform2D test = Transform2D(ROTATION, SCALE, SKEW, TRANSLATION); + Transform2D expected = Transform2D().scaled(SCALE).rotated(ROTATION).translated(TRANSLATION); + expected.set_skew(SKEW); + + CHECK(test.is_equal_approx(expected)); +} + +TEST_CASE("[Transform2D] Constructor from raw values") { + const Transform2D test = Transform2D(1, 2, 3, 4, 5, 6); + const Transform2D expected = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + CHECK(test == expected); +} + +TEST_CASE("[Transform2D] xform") { + const Vector2 v = Vector2(2, 3); + const Transform2D T = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + const Vector2 expected = Vector2(1 * 2 + 3 * 3 + 5 * 1, 2 * 2 + 4 * 3 + 6 * 1); + CHECK(T.xform(v) == expected); +} + +TEST_CASE("[Transform2D] Basis xform") { + const Vector2 v = Vector2(2, 2); + const Transform2D T1 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(0, 0)); + + // Both versions should be the same when the origin is (0,0). + CHECK(T1.basis_xform(v) == T1.xform(v)); + + const Transform2D T2 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + + // Each version should be different when the origin is not (0,0). + CHECK_FALSE(T2.basis_xform(v) == T2.xform(v)); +} + +TEST_CASE("[Transform2D] Affine inverse") { + const Transform2D orig = create_dummy_transform(); + const Transform2D affine_inverted = orig.affine_inverse(); + const Transform2D affine_inverted_again = affine_inverted.affine_inverse(); + CHECK(affine_inverted_again == orig); +} + +TEST_CASE("[Transform2D] Orthonormalized") { + const Transform2D T = create_dummy_transform(); + const Transform2D orthonormalized_T = T.orthonormalized(); + + // Check each basis has length 1. + CHECK(Math::is_equal_approx(orthonormalized_T[0].length_squared(), 1)); + CHECK(Math::is_equal_approx(orthonormalized_T[1].length_squared(), 1)); + + const Vector2 vx = Vector2(orthonormalized_T[0].x, orthonormalized_T[1].x); + const Vector2 vy = Vector2(orthonormalized_T[0].y, orthonormalized_T[1].y); + + // Check the basis are orthogonal. + CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vx), 1)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vy), 0)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vx), 0)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vy), 1)); +} + TEST_CASE("[Transform2D] translation") { - Vector2 offset = Vector2(1, 2); + const Vector2 offset = Vector2(1, 2); // Both versions should give the same result applied to identity. CHECK(identity().translated(offset) == identity().translated_local(offset)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D T = identity().translated(offset); + const Transform2D orig = create_dummy_transform(); + const Transform2D T = identity().translated(offset); CHECK(orig.translated(offset) == T * orig); CHECK(orig.translated_local(offset) == orig * T); } TEST_CASE("[Transform2D] scaling") { - Vector2 scaling = Vector2(1, 2); + const Vector2 scaling = Vector2(1, 2); // Both versions should give the same result applied to identity. CHECK(identity().scaled(scaling) == identity().scaled_local(scaling)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D S = identity().scaled(scaling); + const Transform2D orig = create_dummy_transform(); + const Transform2D S = identity().scaled(scaling); CHECK(orig.scaled(scaling) == S * orig); CHECK(orig.scaled_local(scaling) == orig * S); } TEST_CASE("[Transform2D] rotation") { - real_t phi = 1.0; + constexpr real_t phi = 1.0; // Both versions should give the same result applied to identity. CHECK(identity().rotated(phi) == identity().rotated_local(phi)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D R = identity().rotated(phi); + const Transform2D orig = create_dummy_transform(); + const Transform2D R = identity().rotated(phi); CHECK(orig.rotated(phi) == R * orig); CHECK(orig.rotated_local(phi) == orig * R); } TEST_CASE("[Transform2D] Interpolation") { - Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8)); - Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4)); + const Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8)); + const Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4)); Transform2D interpolated = Transform2D().interpolate_with(rotate_scale_skew_pos, 0.5); CHECK(interpolated.get_origin().is_equal_approx(rotate_scale_skew_pos_halfway.get_origin())); CHECK(interpolated.get_rotation() == doctest::Approx(rotate_scale_skew_pos_halfway.get_rotation())); @@ -98,8 +182,8 @@ TEST_CASE("[Transform2D] Interpolation") { } TEST_CASE("[Transform2D] Finite number checks") { - const Vector2 x(0, 1); - const Vector2 infinite(NAN, NAN); + const Vector2 x = Vector2(0, 1); + const Vector2 infinite = Vector2(NAN, NAN); CHECK_MESSAGE( Transform2D(x, x, x).is_finite(), |