diff options
74 files changed, 997 insertions, 428 deletions
diff --git a/SConstruct b/SConstruct index 5c1ccd2449..178f2d86a2 100644 --- a/SConstruct +++ b/SConstruct @@ -1053,7 +1053,7 @@ if env["ninja"]: SetOption("experimental", "ninja") env["NINJA_FILE_NAME"] = env["ninja_file"] env["NINJA_DISABLE_AUTO_RUN"] = not env["ninja_auto_run"] - env.Tool("ninja") + env.Tool("ninja", "build.ninja") # Threads if env["threads"]: @@ -1116,7 +1116,7 @@ atexit.register(print_elapsed_time) def purge_flaky_files(): - paths_to_keep = ["ninja.build"] + paths_to_keep = ["build.ninja"] for build_failure in GetBuildFailures(): path = build_failure.node.path if os.path.isfile(path) and path not in paths_to_keep: diff --git a/doc/classes/Node2D.xml b/doc/classes/Node2D.xml index 851290de7b..0b2dfcea03 100644 --- a/doc/classes/Node2D.xml +++ b/doc/classes/Node2D.xml @@ -95,43 +95,44 @@ </methods> <members> <member name="global_position" type="Vector2" setter="set_global_position" getter="get_global_position"> - Global position. + Global position. See also [member position]. </member> <member name="global_rotation" type="float" setter="set_global_rotation" getter="get_global_rotation"> - Global rotation in radians. + Global rotation in radians. See also [member rotation]. </member> <member name="global_rotation_degrees" type="float" setter="set_global_rotation_degrees" getter="get_global_rotation_degrees"> - Helper property to access [member global_rotation] in degrees instead of radians. + Helper property to access [member global_rotation] in degrees instead of radians. See also [member rotation_degrees]. </member> <member name="global_scale" type="Vector2" setter="set_global_scale" getter="get_global_scale"> - Global scale. + Global scale. See also [member scale]. </member> <member name="global_skew" type="float" setter="set_global_skew" getter="get_global_skew"> - Global skew in radians. + Global skew in radians. See also [member skew]. </member> <member name="global_transform" type="Transform2D" setter="set_global_transform" getter="get_global_transform"> - Global [Transform2D]. + Global [Transform2D]. See also [member transform]. </member> <member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)"> - Position, relative to the node's parent. + Position, relative to the node's parent. See also [member global_position]. </member> <member name="rotation" type="float" setter="set_rotation" getter="get_rotation" default="0.0"> - Rotation in radians, relative to the node's parent. + Rotation in radians, relative to the node's parent. See also [member global_rotation]. [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees]. </member> <member name="rotation_degrees" type="float" setter="set_rotation_degrees" getter="get_rotation_degrees"> - Helper property to access [member rotation] in degrees instead of radians. + Helper property to access [member rotation] in degrees instead of radians. See also [member global_rotation_degrees]. </member> <member name="scale" type="Vector2" setter="set_scale" getter="get_scale" default="Vector2(1, 1)"> - The node's scale. Unscaled value: [code](1, 1)[/code]. + The node's scale, relative to the node's parent. Unscaled value: [code](1, 1)[/code]. See also [member global_scale]. [b]Note:[/b] Negative X scales in 2D are not decomposable from the transformation matrix. Due to the way scale is represented with transformation matrices in Godot, negative scales on the X axis will be changed to negative scales on the Y axis and a rotation of 180 degrees when decomposed. </member> <member name="skew" type="float" setter="set_skew" getter="get_skew" default="0.0"> - Slants the node. - [b]Note:[/b] Skew is X axis only. + If set to a non-zero value, slants the node in one direction or another. This can be used for pseudo-3D effects. See also [member global_skew]. + [b]Note:[/b] Skew is performed on the X axis only, and [i]between[/i] rotation and scaling. + [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [code]skew = deg_to_rad(value_in_degrees)[/code]. </member> <member name="transform" type="Transform2D" setter="set_transform" getter="get_transform"> - Local [Transform2D]. + The node's [Transform2D], relative to the node's parent. See also [member global_transform]. </member> </members> </class> diff --git a/doc/classes/RDShaderSource.xml b/doc/classes/RDShaderSource.xml index 062c22f11f..a7b897d56e 100644 --- a/doc/classes/RDShaderSource.xml +++ b/doc/classes/RDShaderSource.xml @@ -23,6 +23,7 @@ <param index="1" name="source" type="String" /> <description> Sets [param source] code for the specified shader [param stage]. Equivalent to setting one of [member source_compute], [member source_fragment], [member source_tesselation_control], [member source_tesselation_evaluation] or [member source_vertex]. + [b]Note:[/b] If you set the compute shader source code using this method directly, remember to remove the Godot-specific hint [code]#[compute][/code]. </description> </method> </methods> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 78a703c213..bd46c54990 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -19,7 +19,7 @@ <param index="3" name="disabled" type="bool" default="false" /> <param index="4" name="tooltip_text" type="String" default="""" /> <description> - Adds a button with [Texture2D] [param button] at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text]. + Adds a button with [Texture2D] [param button] to the end of the cell at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text]. </description> </method> <method name="add_child"> @@ -643,7 +643,7 @@ <param index="0" name="column" type="int" /> <param index="1" name="texture" type="Texture2D" /> <description> - Sets the given cell's icon [Texture2D]. The cell has to be in [constant CELL_MODE_ICON] mode. + Sets the given cell's icon [Texture2D]. If the cell is in [constant CELL_MODE_ICON] mode, the icon is displayed in the center of the cell. Otherwise, the icon is displayed before the cell's text. [constant CELL_MODE_RANGE] does not display an icon. </description> </method> <method name="set_icon_max_width"> @@ -811,17 +811,17 @@ </members> <constants> <constant name="CELL_MODE_STRING" value="0" enum="TreeCellMode"> - Cell shows a string label. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used. + Cell shows a string label, optionally with an icon. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used. </constant> <constant name="CELL_MODE_CHECK" value="1" enum="TreeCellMode"> - Cell shows a checkbox, optionally with text. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable. + Cell shows a checkbox, optionally with text and an icon. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable. </constant> <constant name="CELL_MODE_RANGE" value="2" enum="TreeCellMode"> Cell shows a numeric range. When editable, it can be edited using a range slider. Use [method set_range] to set the value and [method set_range_config] to configure the range. This cell can also be used in a text dropdown mode when you assign a text with [method set_text]. Separate options with a comma, e.g. [code]"Option1,Option2,Option3"[/code]. </constant> <constant name="CELL_MODE_ICON" value="3" enum="TreeCellMode"> - Cell shows an icon. It can't be edited nor display text. + Cell shows an icon. It can't be edited nor display text. The icon is always centered within the cell. </constant> <constant name="CELL_MODE_CUSTOM" value="4" enum="TreeCellMode"> Cell shows as a clickable button. It will display an arrow similar to [OptionButton], but doesn't feature a dropdown (for that you can use [constant CELL_MODE_RANGE]). Clicking the button emits the [signal Tree.item_edited] signal. The button is flat by default, you can use [method set_custom_as_button] to display it with a [StyleBox]. diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index aab1aadf02..9b976c2206 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -1521,6 +1521,11 @@ bool LightStorage::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, int *p_i uint64_t min_pass = 0; // Pass of the existing one, try to use the least recently used one (LRU fashion). for (int j = 0; j < sc; j++) { + if (sarr[j].owner_is_omni != is_omni) { + // Existing light instance type doesn't match new light instance type skip. + continue; + } + LightInstance *sli = light_instance_owner.get_or_null(sarr[j].owner); if (!sli) { // Found a released light instance. diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index a37eba3b15..7d5af48384 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -348,7 +348,7 @@ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataType type, int p_ } } -_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::ConstantNode::Value> &value, uint8_t *data) { +_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::Scalar> &value, uint8_t *data) { switch (type) { case ShaderLanguage::TYPE_BOOL: { uint32_t *gui = (uint32_t *)data; @@ -572,7 +572,7 @@ void ShaderData::set_default_texture_parameter(const StringName &p_name, RID p_t Variant ShaderData::get_default_parameter(const StringName &p_parameter) const { if (uniforms.has(p_parameter)) { ShaderLanguage::ShaderNode::Uniform uniform = uniforms[p_parameter]; - Vector<ShaderLanguage::ConstantNode::Value> default_value = uniform.default_value; + Vector<ShaderLanguage::Scalar> default_value = uniform.default_value; return ShaderLanguage::constant_value_to_variant(default_value, uniform.type, uniform.array_size, uniform.hint); } return Variant(); diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 063241fd1b..5273b61f37 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -486,11 +486,6 @@ void ConnectDialog::_notification(int p_what) { type_list->set_item_icon(i, get_editor_theme_icon(type_name)); } - Ref<StyleBox> style = get_theme_stylebox(CoreStringName(normal), "LineEdit")->duplicate(); - if (style.is_valid()) { - style->set_content_margin(SIDE_TOP, style->get_content_margin(SIDE_TOP) + 1.0); - from_signal->add_theme_style_override(CoreStringName(normal), style); - } method_search->set_right_icon(get_editor_theme_icon("Search")); open_method_tree->set_icon(get_editor_theme_icon("Edit")); } break; diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index 75135532aa..0bdda41f26 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -509,7 +509,7 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str } for (int i = 0; i < hsplits.size(); i++) { - p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset()); + p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE)); } FileSystemDock::get_singleton()->save_layout_to_config(p_layout, p_section); @@ -605,7 +605,7 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S continue; } int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1)); - hsplits[i]->set_split_offset(ofs); + hsplits[i]->set_split_offset(ofs * EDSCALE); } FileSystemDock::get_singleton()->load_layout_from_config(p_layout, p_section); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6899a35ded..39291138a6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4404,6 +4404,21 @@ bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimp return true; } +void EditorNode::get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table) { + SceneEditorDataEntry new_entry; + new_entry.is_display_folded = p_node->is_displayed_folded(); + + if (p_root != p_node) { + new_entry.is_editable = p_root->is_editable_instance(p_node); + } + + p_table.insert(p_root->get_path_to(p_node), new_entry); + + for (int i = 0; i < p_node->get_child_count(); i++) { + get_scene_editor_data_for_node(p_root, p_node->get_child(i), p_table); + } +} + void EditorNode::get_preload_scene_modification_table( Node *p_edited_scene, Node *p_reimported_root, @@ -4510,7 +4525,7 @@ void EditorNode::get_preload_scene_modification_table( void EditorNode::get_preload_modifications_reference_to_nodes( Node *p_root, Node *p_node, - List<Node *> &p_excluded_nodes, + HashSet<Node *> &p_excluded_nodes, List<Node *> &p_instance_list_with_children, HashMap<NodePath, ModificationNodeEntry> &p_modification_table) { if (!p_excluded_nodes.find(p_node)) { @@ -5984,12 +5999,14 @@ void EditorNode::reload_scene(const String &p_path) { scene_tabs->set_current_tab(current_tab); } -void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) { +void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list) { String scene_file_path = p_node->get_scene_file_path(); - // This is going to get messy... + bool valid_instance_found = false; + + // Attempt to find all the instances matching path we're going to reload. if (p_node->get_scene_file_path() == p_instance_path) { - p_instance_list.push_back(p_node); + valid_instance_found = true; } else { Node *current_node = p_node; @@ -5997,7 +6014,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * while (inherited_state.is_valid()) { String inherited_path = inherited_state->get_path(); if (inherited_path == p_instance_path) { - p_instance_list.push_back(p_node); + valid_instance_found = true; break; } @@ -6005,6 +6022,19 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * } } + // Instead of adding this instance directly, if its not owned by the scene, walk its ancestors + // and find the first node still owned by the scene. This is what we will reloading instead. + if (valid_instance_found) { + Node *current_node = p_node; + while (true) { + if (current_node->get_owner() == p_root || current_node->get_owner() == nullptr) { + p_instance_list.insert(current_node); + break; + } + current_node = current_node->get_parent(); + } + } + for (int i = 0; i < p_node->get_child_count(); i++) { find_all_instances_inheriting_path_in_node(p_root, p_node->get_child(i), p_instance_path, p_instance_list); } @@ -6023,20 +6053,20 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin Node *edited_scene_root = editor_data.get_edited_scene_root(current_scene_idx); if (edited_scene_root) { - SceneModificationsEntry scene_motifications; + SceneModificationsEntry scene_modifications; for (const String &instance_path : p_scenes) { if (editor_data.get_scene_path(current_scene_idx) == instance_path) { continue; } - List<Node *> instance_list; - find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instance_list); - if (instance_list.size() > 0) { + HashSet<Node *> instances_to_reimport; + find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instances_to_reimport); + if (instances_to_reimport.size() > 0) { editor_data.set_edited_scene(current_scene_idx); List<Node *> instance_list_with_children; - for (Node *original_node : instance_list) { + for (Node *original_node : instances_to_reimport) { InstanceModificationsEntry instance_modifications; // Fetching all the modified properties of the nodes reimported scene. @@ -6044,19 +6074,19 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin instance_modifications.original_node = original_node; instance_modifications.instance_path = instance_path; - scene_motifications.instance_list.push_back(instance_modifications); + scene_modifications.instance_list.push_back(instance_modifications); instance_list_with_children.push_back(original_node); get_children_nodes(original_node, instance_list_with_children); } // Search the scene to find nodes that references the nodes will be recreated. - get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instance_list, instance_list_with_children, scene_motifications.other_instances_modifications); + get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instances_to_reimport, instance_list_with_children, scene_modifications.other_instances_modifications); } } - if (scene_motifications.instance_list.size() > 0) { - scenes_modification_table[current_scene_idx] = scene_motifications; + if (scene_modifications.instance_list.size() > 0) { + scenes_modification_table[current_scene_idx] = scene_modifications; } } } @@ -6179,10 +6209,10 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { // Instantiate early so that caches cleared on load in SceneState can be rebuilt early. Node *instantiated_node = nullptr; - // If we are in a inherit scene, it's easier to create a new base scene and + // If we are in a inherited scene, it's easier to create a new base scene and // grab the node from there. // When scene_path_to_node is '.' and we have scene_inherited_state, it's because - // it's a muli-level inheritance scene. We should use + // it's a multi-level inheritance scene. We should use NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node); Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state(); if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != instance_modifications.instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) { @@ -6206,9 +6236,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { if (!instantiated_node) { // If no base scene was found to create the node, we will use the reimported packed scene directly. - // But, when the current edited scene is the reimported scene, it's because it's a inherited scene - // of the reimported scene. In that case, we will not instantiate current_packed_scene, because - // we would reinstanciate ourself. Using the base scene is better. + // But, when the current edited scene is the reimported scene, it's because it's an inherited scene + // derived from the reimported scene. In that case, we will not instantiate current_packed_scene, because + // we would reinstantiate ourself. Using the base scene is better. if (current_edited_scene == original_node) { if (base_packed_scene.is_valid()) { instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); @@ -6264,6 +6294,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { // crash when reimporting scenes with animations when "Editable children" was enabled. replace_history_reimported_nodes(original_node, instantiated_node, original_node); + // Reset the editable instance state. + HashMap<NodePath, SceneEditorDataEntry> scene_editor_data_table; + Node *owner = original_node->get_owner(); + if (!owner) { + owner = original_node; + } + + get_scene_editor_data_for_node(owner, original_node, scene_editor_data_table); + + bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); + // Delete all the remaining node children. while (original_node->get_child_count()) { Node *child = original_node->get_child(0); @@ -6272,16 +6313,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { child->queue_free(); } - // Reset the editable instance state. - bool is_editable = true; - Node *owner = original_node->get_owner(); - if (owner) { - is_editable = owner->is_editable_instance(original_node); - } - - 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(); - // Update the name to match instantiated_node->set_name(original_node->get_name()); @@ -6312,19 +6343,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { // Mark the old node for deletion. original_node->queue_free(); - // Restore the folded and placeholder state from the original node. - instantiated_node->set_display_folded(original_node_is_displayed_folded); + // Restore the placeholder state from the original node. instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder); - if (owner) { - Ref<SceneState> ss_inst = owner->get_scene_instance_state(); - if (ss_inst.is_valid()) { - ss_inst->update_instance_resource(instance_modifications.instance_path, current_packed_scene); - } - - owner->set_editable_instance(instantiated_node, is_editable); - } - // Attempt to re-add all the additional nodes. for (AdditiveNodeEntry additive_node_entry : instance_modifications.addition_list) { Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent); @@ -6356,6 +6377,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { } } + // Restore the scene's editable instance and folded states. + for (HashMap<NodePath, SceneEditorDataEntry>::Iterator I = scene_editor_data_table.begin(); I; ++I) { + Node *node = owner->get_node_or_null(I->key); + if (node) { + if (owner != node) { + owner->set_editable_instance(node, I->value.is_editable); + } + node->set_display_folded(I->value.is_display_folded); + } + } + // Restore the selection. if (selected_node_paths.size()) { for (NodePath selected_node_path : selected_node_paths) { @@ -7585,8 +7617,8 @@ EditorNode::EditorNode() { default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0); } default_layout->set_value(docks_section, "dock_hsplit_1", 0); - default_layout->set_value(docks_section, "dock_hsplit_2", 270 * EDSCALE); - default_layout->set_value(docks_section, "dock_hsplit_3", -270 * EDSCALE); + default_layout->set_value(docks_section, "dock_hsplit_2", 270); + default_layout->set_value(docks_section, "dock_hsplit_3", -270); default_layout->set_value(docks_section, "dock_hsplit_4", 0); _update_layouts_menu(); diff --git a/editor/editor_node.h b/editor/editor_node.h index e9d2c28528..f1b50cf3fa 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -852,12 +852,19 @@ public: HashMap<NodePath, ModificationNodeEntry> other_instances_modifications; }; + struct SceneEditorDataEntry { + bool is_editable; + bool is_display_folded; + }; + HashMap<int, SceneModificationsEntry> scenes_modification_table; List<String> scenes_reimported; List<String> resources_reimported; void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification); + void get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table); + void get_preload_scene_modification_table( Node *p_edited_scene, Node *p_reimported_root, @@ -866,7 +873,7 @@ public: void get_preload_modifications_reference_to_nodes( Node *p_root, Node *p_node, - List<Node *> &p_excluded_nodes, + HashSet<Node *> &p_excluded_nodes, List<Node *> &p_instance_list_with_children, HashMap<NodePath, ModificationNodeEntry> &p_modification_table); void get_children_nodes(Node *p_node, List<Node *> &p_nodes); @@ -927,7 +934,7 @@ public: void reload_scene(const String &p_path); - void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list); + void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list); void preload_reimporting_with_path_in_edited_scenes(const List<String> &p_scenes); void reload_instances_with_path_in_edited_scenes(); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 123d903220..6e375dc1fc 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2703,7 +2703,11 @@ void EditorPropertyNodePath::_update_menu() { void EditorPropertyNodePath::_menu_option(int p_idx) { switch (p_idx) { case ACTION_CLEAR: { - emit_changed(get_edited_property(), NodePath()); + if (editing_node) { + emit_changed(get_edited_property(), Variant()); + } else { + emit_changed(get_edited_property(), NodePath()); + } update_property(); } break; diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index d49d597084..a073a2338b 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -636,7 +636,7 @@ void EditorSpinSlider::_grabber_mouse_exited() { void EditorSpinSlider::set_read_only(bool p_enable) { read_only = p_enable; - if (read_only && value_input) { + if (read_only && value_input && value_input->is_inside_tree()) { value_input->release_focus(); } diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 7ca3cb6c3a..011d0135b4 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -37,6 +37,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/plugins/skeleton_3d_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/animation/animation_player.h" @@ -419,7 +420,9 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite animation_player->connect(SceneStringName(animation_finished), callable_mp(this, &SceneImportSettingsDialog::_animation_finished)); } else if (Object::cast_to<Skeleton3D>(p_node)) { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE; - skeletons.push_back(Object::cast_to<Skeleton3D>(p_node)); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node); + skeleton->connect(SceneStringName(tree_entered), callable_mp(this, &SceneImportSettingsDialog::_skeleton_tree_entered).bind(skeleton)); + skeletons.push_back(skeleton); } else { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; } @@ -480,6 +483,31 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite contents_aabb.merge_with(aabb); } } + + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node); + if (skeleton) { + Ref<ArrayMesh> bones_mesh = Skeleton3DGizmoPlugin::get_bones_mesh(skeleton, -1, true); + + bones_mesh_preview->set_mesh(bones_mesh); + + Transform3D accum_xform; + Node3D *base = skeleton; + while (base) { + accum_xform = base->get_transform() * accum_xform; + base = Object::cast_to<Node3D>(base->get_parent()); + } + + bones_mesh_preview->set_transform(accum_xform * skeleton->get_transform()); + + AABB aabb = accum_xform.xform(bones_mesh->get_aabb()); + + if (first_aabb) { + contents_aabb = aabb; + first_aabb = false; + } else { + contents_aabb.merge_with(aabb); + } + } } void SceneImportSettingsDialog::_update_scene() { @@ -795,6 +823,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons selecting = true; scene_import_settings_data->hide_options = false; + bones_mesh_preview->hide(); if (p_type == "Node") { node_selected->hide(); // Always hide just in case. mesh_preview->hide(); @@ -834,6 +863,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE; } else if (Object::cast_to<Skeleton3D>(nd.node)) { scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE; + bones_mesh_preview->show(); } else { scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; scene_import_settings_data->hide_options = editing_animation; @@ -853,6 +883,8 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons scene_import_settings_data->settings = &ad.settings; scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION; + + _animation_update_skeleton_visibility(); } else if (p_type == "Mesh") { node_selected->hide(); if (Object::cast_to<Node3D>(scene)) { @@ -1055,6 +1087,11 @@ void SceneImportSettingsDialog::_animation_slider_value_changed(double p_value) animation_player->seek(p_value * animation_map[selected_id].animation->get_length(), true); } +void SceneImportSettingsDialog::_skeleton_tree_entered(Skeleton3D *p_skeleton) { + bones_mesh_preview->set_skeleton_path(p_skeleton->get_path()); + bones_mesh_preview->set_skin(p_skeleton->register_skin(p_skeleton->create_skin_from_rest_transforms())); +} + void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) { Animation::LoopMode loop_mode = animation_loop_mode; @@ -1080,6 +1117,14 @@ void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) { } } +void SceneImportSettingsDialog::_animation_update_skeleton_visibility() { + if (animation_toggle_skeleton_visibility->is_pressed()) { + bones_mesh_preview->show(); + } else { + bones_mesh_preview->hide(); + } +} + void SceneImportSettingsDialog::_material_tree_selected() { if (selecting) { return; @@ -1282,6 +1327,8 @@ void SceneImportSettingsDialog::_notification(int p_what) { light_1_switch->set_icon(theme_cache.light_1_icon); light_2_switch->set_icon(theme_cache.light_2_icon); light_rotate_switch->set_icon(theme_cache.rotate_icon); + + animation_toggle_skeleton_visibility->set_icon(get_editor_theme_icon(SNAME("Skeleton3D"))); } break; case NOTIFICATION_PROCESS: { @@ -1661,6 +1708,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { animation_hbox->add_child(animation_stop_button); animation_stop_button->set_flat(true); animation_stop_button->set_focus_mode(Control::FOCUS_NONE); + animation_stop_button->set_tooltip_text(TTR("Selected Animation Stop")); animation_stop_button->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_stop_current_animation)); animation_slider = memnew(HSlider); @@ -1673,6 +1721,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { animation_slider->set_focus_mode(Control::FOCUS_NONE); animation_slider->connect(SceneStringName(value_changed), callable_mp(this, &SceneImportSettingsDialog::_animation_slider_value_changed)); + animation_toggle_skeleton_visibility = memnew(Button); + animation_hbox->add_child(animation_toggle_skeleton_visibility); + animation_toggle_skeleton_visibility->set_toggle_mode(true); + animation_toggle_skeleton_visibility->set_flat(true); + animation_toggle_skeleton_visibility->set_focus_mode(Control::FOCUS_NONE); + animation_toggle_skeleton_visibility->set_tooltip_text(TTR("Toggle Animation Skeleton Visibility")); + + animation_toggle_skeleton_visibility->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_animation_update_skeleton_visibility)); + base_viewport->set_use_own_world_3d(true); HBoxContainer *viewport_hbox = memnew(HBoxContainer); @@ -1800,6 +1857,13 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { collider_mat->set_albedo(Color(0.5, 0.5, 1.0)); } + { + bones_mesh_preview = memnew(MeshInstance3D); + bones_mesh_preview->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); + bones_mesh_preview->set_skeleton_path(NodePath()); + base_viewport->add_child(bones_mesh_preview); + } + inspector = memnew(EditorInspector); inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); inspector->connect(SNAME("property_edited"), callable_mp(this, &SceneImportSettingsDialog::_inspector_property_edited)); diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index bbd0d2c22d..1088acf772 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -109,10 +109,12 @@ class SceneImportSettingsDialog : public ConfirmationDialog { HSlider *animation_slider = nullptr; Button *animation_play_button = nullptr; Button *animation_stop_button = nullptr; + Button *animation_toggle_skeleton_visibility = nullptr; Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE; bool animation_pingpong = false; bool previous_import_as_skeleton = false; bool previous_rest_as_reset = false; + MeshInstance3D *bones_mesh_preview = nullptr; Ref<StandardMaterial3D> collider_mat; @@ -187,9 +189,11 @@ class SceneImportSettingsDialog : public ConfirmationDialog { void _reset_animation(const String &p_animation_name = ""); void _animation_slider_value_changed(double p_value); void _animation_finished(const StringName &p_name); + void _animation_update_skeleton_visibility(); void _material_tree_selected(); void _mesh_tree_selected(); void _scene_tree_selected(); + void _skeleton_tree_entered(Skeleton3D *p_skeleton); void _cleanup(); void _on_light_1_switch_pressed(); void _on_light_2_switch_pressed(); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 4b7bf1674f..d1032470f2 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5384,7 +5384,7 @@ CanvasItemEditor::CanvasItemEditor() { main_menu_hbox->add_child(pivot_button); pivot_button->set_toggle_mode(true); pivot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT)); - pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the rotation pivot in the center of the selected nodes.")); + pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary rotation pivot in the center of the selected nodes.")); pan_button = memnew(Button); pan_button->set_theme_type_variation("FlatButton"); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 7ba464f4e5..890035e6a6 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -687,11 +687,11 @@ void Path3DEditorPlugin::make_visible(bool p_visible) { } void Path3DEditorPlugin::_mode_changed(int p_mode) { - curve_create->set_pressed(p_mode == MODE_CREATE); - curve_edit_curve->set_pressed(p_mode == MODE_EDIT_CURVE); - curve_edit_tilt->set_pressed(p_mode == MODE_EDIT_TILT); - curve_edit->set_pressed(p_mode == MODE_EDIT); - curve_del->set_pressed(p_mode == MODE_DELETE); + curve_create->set_pressed_no_signal(p_mode == MODE_CREATE); + curve_edit_curve->set_pressed_no_signal(p_mode == MODE_EDIT_CURVE); + curve_edit_tilt->set_pressed_no_signal(p_mode == MODE_EDIT_TILT); + curve_edit->set_pressed_no_signal(p_mode == MODE_EDIT); + curve_del->set_pressed_no_signal(p_mode == MODE_DELETE); Node3DEditor::get_singleton()->clear_subgizmo_selection(); } @@ -790,17 +790,14 @@ void Path3DEditorPlugin::_restore_curve_points(const PackedVector3Array &p_point } void Path3DEditorPlugin::_update_theme() { - // TODO: Split the EditorPlugin instance from the UI instance and connect this properly. - // See the 2D path editor for inspiration. - curve_edit->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveEdit"), EditorStringName(EditorIcons))); - curve_edit_curve->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCurve"), EditorStringName(EditorIcons))); - curve_edit_tilt->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveTilt"), EditorStringName(EditorIcons))); - curve_create->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCreate"), EditorStringName(EditorIcons))); - curve_del->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveDelete"), EditorStringName(EditorIcons))); - curve_close->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveClose"), EditorStringName(EditorIcons))); - curve_clear_points->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Clear"), EditorStringName(EditorIcons))); - - create_curve_button->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Curve3D"), EditorStringName(EditorIcons))); + curve_edit->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveEdit"))); + curve_edit_curve->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCurve"))); + curve_edit_tilt->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveTilt"))); + curve_create->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCreate"))); + curve_del->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete"))); + curve_close->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose"))); + curve_clear_points->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear"))); + create_curve_button->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D"))); } void Path3DEditorPlugin::_update_toolbar() { @@ -812,42 +809,14 @@ void Path3DEditorPlugin::_update_toolbar() { create_curve_button->set_visible(!has_curve); } -void Path3DEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_CREATE)); - curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_CURVE)); - curve_edit_tilt->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_TILT)); - curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT)); - curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE)); - curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_close_curve)); - - _update_theme(); - } break; - - case NOTIFICATION_READY: { - // FIXME: This can trigger theme updates when the nodes that we want to update are not yet available. - // The toolbar should be extracted to a dedicated control and theme updates should be handled through - // the notification. - Node3DEditor::get_singleton()->connect(SceneStringName(theme_changed), callable_mp(this, &Path3DEditorPlugin::_update_theme)); - } break; - } -} - void Path3DEditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points); } -Path3DEditorPlugin *Path3DEditorPlugin::singleton = nullptr; - Path3DEditorPlugin::Path3DEditorPlugin() { - path = nullptr; singleton = this; - mirror_handle_angle = true; - mirror_handle_length = true; - disk_size = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8); Ref<Path3DGizmoPlugin> gizmo_plugin = memnew(Path3DGizmoPlugin(disk_size)); @@ -856,7 +825,6 @@ Path3DEditorPlugin::Path3DEditorPlugin() { topmenu_bar = memnew(HBoxContainer); topmenu_bar->hide(); - Node3DEditor::get_singleton()->add_control_to_menu_panel(topmenu_bar); toolbar = memnew(HBoxContainer); topmenu_bar->add_child(toolbar); @@ -867,6 +835,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_edit->set_focus_mode(Control::FOCUS_NONE); curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Click: Select multiple Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); toolbar->add_child(curve_edit); + curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT)); curve_edit_curve = memnew(Button); curve_edit_curve->set_theme_type_variation("FlatButton"); @@ -874,6 +843,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); curve_edit_curve->set_tooltip_text(TTR("Select Control Points") + "\n" + TTR("Shift+Click: Drag out Control Points")); toolbar->add_child(curve_edit_curve); + curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_CURVE)); curve_edit_tilt = memnew(Button); curve_edit_tilt->set_theme_type_variation("FlatButton"); @@ -881,6 +851,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_edit_tilt->set_focus_mode(Control::FOCUS_NONE); curve_edit_tilt->set_tooltip_text(TTR("Select Tilt Handles")); toolbar->add_child(curve_edit_tilt); + curve_edit_tilt->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_TILT)); curve_create = memnew(Button); curve_create->set_theme_type_variation("FlatButton"); @@ -888,6 +859,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_create->set_focus_mode(Control::FOCUS_NONE); curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)")); toolbar->add_child(curve_create); + curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_CREATE)); curve_del = memnew(Button); curve_del->set_theme_type_variation("FlatButton"); @@ -895,12 +867,14 @@ Path3DEditorPlugin::Path3DEditorPlugin() { curve_del->set_focus_mode(Control::FOCUS_NONE); curve_del->set_tooltip_text(TTR("Delete Point")); toolbar->add_child(curve_del); + curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE)); curve_close = memnew(Button); curve_close->set_theme_type_variation("FlatButton"); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip_text(TTR("Close Curve")); toolbar->add_child(curve_close); + curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_close_curve)); curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation("FlatButton"); @@ -927,18 +901,17 @@ Path3DEditorPlugin::Path3DEditorPlugin() { topmenu_bar->add_child(create_curve_button); create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_create_curve)); - PopupMenu *menu; - menu = handle_menu->get_popup(); + PopupMenu *menu = handle_menu->get_popup(); menu->add_check_item(TTR("Mirror Handle Angles")); menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); menu->add_check_item(TTR("Mirror Handle Lengths")); menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path3DEditorPlugin::_handle_option_pressed)); - curve_edit->set_pressed(true); -} + curve_edit->set_pressed_no_signal(true); -Path3DEditorPlugin::~Path3DEditorPlugin() { + topmenu_bar->connect(SceneStringName(theme_changed), callable_mp(this, &Path3DEditorPlugin::_update_theme)); + Node3DEditor::get_singleton()->add_control_to_menu_panel(topmenu_bar); } Ref<EditorNode3DGizmo> Path3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index 09a7a46803..60cb7f940f 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -162,13 +162,12 @@ class Path3DEditorPlugin : public EditorPlugin { }; protected: - void _notification(int p_what); static void _bind_methods(); public: Path3D *get_edited_path() { return path; } - static Path3DEditorPlugin *singleton; + inline static Path3DEditorPlugin *singleton = nullptr; virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } @@ -183,7 +182,6 @@ public: void set_handle_clicked(bool clicked) { handle_clicked = clicked; } Path3DEditorPlugin(); - ~Path3DEditorPlugin(); }; #endif // PATH_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index dc4d4db3f8..9fc88b3a6a 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1348,16 +1348,18 @@ int Skeleton3DEditor::get_selected_bone() const { return selected_bone; } +Skeleton3DGizmoPlugin::SelectionMaterials Skeleton3DGizmoPlugin::selection_materials; + Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { - unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); - unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); - unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); - unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); - unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - - selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); - selected_sh = Ref<Shader>(memnew(Shader)); + selection_materials.unselected_mat.instantiate(); + selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + + selection_materials.selected_mat.instantiate(); + Ref<Shader> selected_sh = Ref<Shader>(memnew(Shader)); selected_sh->set_code(R"( // Skeleton 3D gizmo bones shader. @@ -1376,7 +1378,7 @@ void fragment() { ALPHA = COLOR.a; } )"); - selected_mat->set_shader(selected_sh); + selection_materials.selected_mat->set_shader(selected_sh); // Register properties in editor settings. EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); @@ -1386,6 +1388,11 @@ void fragment() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron")); } +Skeleton3DGizmoPlugin::~Skeleton3DGizmoPlugin() { + selection_materials.unselected_mat.unref(); + selection_materials.selected_mat.unref(); +} + bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; } @@ -1526,6 +1533,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { selected = se->get_selected_bone(); } + Ref<ArrayMesh> m = get_bones_mesh(skeleton, selected, p_gizmo->is_selected()); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); +} + +Ref<ArrayMesh> Skeleton3DGizmoPlugin::get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected) { Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton"); Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone"); real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length"); @@ -1539,11 +1551,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); surface_tool->begin(Mesh::PRIMITIVE_LINES); - if (p_gizmo->is_selected()) { - surface_tool->set_material(selected_mat); + if (p_is_selected) { + surface_tool->set_material(selection_materials.selected_mat); } else { - unselected_mat->set_albedo(bone_color); - surface_tool->set_material(unselected_mat); + selection_materials.unselected_mat->set_albedo(bone_color); + surface_tool->set_material(selection_materials.unselected_mat); } LocalVector<int> bones; @@ -1557,16 +1569,16 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { weights[0] = 1; int current_bone_index = 0; - Vector<int> bones_to_process = skeleton->get_parentless_bones(); + Vector<int> bones_to_process = p_skeleton->get_parentless_bones(); while (bones_to_process.size() > current_bone_index) { int current_bone_idx = bones_to_process[current_bone_index]; current_bone_index++; - Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; + Color current_bone_color = (current_bone_idx == p_selected) ? selected_bone_color : bone_color; Vector<int> child_bones_vector; - child_bones_vector = skeleton->get_bone_children(current_bone_idx); + child_bones_vector = p_skeleton->get_bone_children(current_bone_idx); int child_bones_size = child_bones_vector.size(); for (int i = 0; i < child_bones_size; i++) { @@ -1577,8 +1589,8 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int child_bone_idx = child_bones_vector[i]; - Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin; - Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin; + Vector3 v0 = p_skeleton->get_bone_global_rest(current_bone_idx).origin; + Vector3 v1 = p_skeleton->get_bone_global_rest(child_bone_idx).origin; Vector3 d = (v1 - v0).normalized(); real_t dist = v0.distance_to(v1); @@ -1586,7 +1598,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int closest = -1; real_t closest_d = 0.0; for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); + real_t dp = Math::abs(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); if (j == 0 || dp > closest_d) { closest = j; } @@ -1613,7 +1625,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int j = 0; j < 3; j++) { Vector3 axis; if (first == Vector3()) { - axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); + axis = d.cross(d.cross(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); first = axis; } else { axis = d.cross(first).normalized(); @@ -1668,7 +1680,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->add_vertex(v0); surface_tool->set_bones(bones); surface_tool->set_weights(weights); - surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); + surface_tool->add_vertex(v0 + (p_skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); if (j == closest) { continue; @@ -1685,7 +1697,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->add_vertex(v1); surface_tool->set_bones(bones); surface_tool->set_weights(weights); - surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); + surface_tool->add_vertex(v1 + (p_skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); if (j == closest) { continue; @@ -1698,6 +1710,5 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } - Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); + return surface_tool->commit(); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 0bb58aac23..d4dee1f16f 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -260,11 +260,15 @@ public: class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); - Ref<StandardMaterial3D> unselected_mat; - Ref<ShaderMaterial> selected_mat; - Ref<Shader> selected_sh; + struct SelectionMaterials { + Ref<StandardMaterial3D> unselected_mat; + Ref<ShaderMaterial> selected_mat; + }; + static SelectionMaterials selection_materials; public: + static Ref<ArrayMesh> get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected); + bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; int get_priority() const override; @@ -277,6 +281,7 @@ public: void redraw(EditorNode3DGizmo *p_gizmo) override; Skeleton3DGizmoPlugin(); + ~Skeleton3DGizmoPlugin(); }; #endif // SKELETON_3D_EDITOR_PLUGIN_H diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index add52ce566..2a39b11815 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1241,6 +1241,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; } + if (!_validate_no_foreign()) { + break; + } + List<Node *> selection = editor_selection->get_selected_node_list(); List<Node *>::Element *e = selection.front(); if (e) { diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 119508f59b..3041857d83 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -865,7 +865,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // CheckBox. { Ref<StyleBoxFlat> checkbox_style = p_config.panel_container_style->duplicate(); - checkbox_style->set_content_margin_individual((p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE, (p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE); p_theme->set_stylebox(CoreStringName(normal), "CheckBox", checkbox_style); p_theme->set_stylebox(SceneStringName(pressed), "CheckBox", checkbox_style); @@ -1165,9 +1164,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // LineEdit & TextEdit. { Ref<StyleBoxFlat> text_editor_style = p_config.button_style->duplicate(); - // The original button_style style has an extra 1 pixel offset that makes LineEdits not align with Buttons, - // so this compensates for that. - text_editor_style->set_content_margin(SIDE_TOP, text_editor_style->get_content_margin(SIDE_TOP) - 1 * EDSCALE); // Don't round the bottom corners to make the line look sharper. text_editor_style->set_corner_radius(CORNER_BOTTOM_LEFT, 0); diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 394213963a..e29fe9295a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -159,6 +159,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackMP3::get_sample_playback() const { void AudioStreamPlaybackMP3::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } } void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) { diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index ff032c88c6..c89534a60c 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -390,6 +390,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackOggVorbis::get_sample_playback() con void AudioStreamPlaybackOggVorbis::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } } AudioStreamPlaybackOggVorbis::~AudioStreamPlaybackOggVorbis() { diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index ab13105d18..66be313ff6 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -169,12 +169,13 @@ Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_displa int fds[2]; if (pipe(fds) == 0) { - // This function expects to return a string, so we can only ask for a MIME of - // "text/plain" zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]); - // Wait for the compositor to know about the pipe. - wl_display_roundtrip(p_display); + // NOTE: It's important to just flush and not roundtrip here as we would risk + // running some cleanup event, like for example `wl_data_device::leave`. We're + // going to wait for the message anyways as the read will probably block if + // the compositor doesn't read from the other end of the pipe. + wl_display_flush(p_display); // Close the write end of the pipe, which we don't need and would otherwise // just stall our next `read`s. diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 770871ae19..c99e9cdd0c 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -357,17 +357,13 @@ List<String> EditorExportPlatformMacOS::get_binary_extensions(const Ref<EditorEx list.push_back("dmg"); #endif list.push_back("zip"); -#ifndef WINDOWS_ENABLED list.push_back("app"); -#endif } else if (dist_type == 1) { #ifdef MACOS_ENABLED list.push_back("dmg"); #endif list.push_back("zip"); -#ifndef WINDOWS_ENABLED list.push_back("app"); -#endif } else if (dist_type == 2) { #ifdef MACOS_ENABLED list.push_back("pkg"); @@ -1903,6 +1899,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p if (is_executable(file)) { // chmod with 0755 if the file is executable. FileAccess::set_unix_permissions(file, 0755); +#ifndef UNIX_ENABLED + if (export_format == "app") { + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Unable to set Unix permissions for executable \"%s\". Use \"chmod +x\" to set it after transferring the exported .app to macOS or Linux."), "Contents/MacOS/" + file.get_file())); + } +#endif } } else { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file)); @@ -1928,6 +1929,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p if ((con_scr == 1 && p_debug) || (con_scr == 2)) { err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path); FileAccess::set_unix_permissions(scr_path, 0755); +#ifndef UNIX_ENABLED + if (export_format == "app") { + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Unable to set Unix permissions for executable \"%s\". Use \"chmod +x\" to set it after transferring the exported .app to macOS or Linux."), scr_path.get_file())); + } +#endif if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not create console wrapper.")); } @@ -2156,6 +2162,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false, true); } FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755); +#ifndef UNIX_ENABLED + if (export_format == "app") { + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Unable to set Unix permissions for executable \"%s\". Use \"chmod +x\" to set it after transferring the exported .app to macOS or Linux."), "Contents/Helpers/" + hlp_path.get_file())); + } +#endif } } diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 2c5c6a1a16..dc83775c71 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -114,7 +114,7 @@ Color CanvasModulate::get_color() const { } PackedStringArray CanvasModulate::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_in_canvas && is_visible_in_tree()) { List<Node *> nodes; diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 5ce26b3ed4..50c5873781 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -417,7 +417,7 @@ Vector2 PointLight2D::get_texture_offset() const { } PackedStringArray PointLight2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!texture.is_valid()) { warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 092c987ac0..7c3fb61d04 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -263,7 +263,7 @@ int LightOccluder2D::get_occluder_light_mask() const { } PackedStringArray LightOccluder2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect.")); diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index 111f5a7b78..4961e18dc9 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -328,7 +328,7 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) { } PackedStringArray NavigationLink2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful.")); diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 023e9201fc..24f261deb6 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -133,7 +133,7 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s } PackedStringArray ParallaxLayer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node.")); diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index c3768386e9..5813ab02e3 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -288,7 +288,7 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const { } PackedStringArray PathFollow2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path2D>(get_parent())) { diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp index 00b6085f0c..27ee6b883c 100644 --- a/scene/2d/physics/collision_object_2d.cpp +++ b/scene/2d/physics/collision_object_2d.cpp @@ -582,7 +582,7 @@ void CollisionObject2D::_update_pickable() { } PackedStringArray CollisionObject2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape.")); diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp index a9b47ef4d4..b49badac1f 100644 --- a/scene/2d/physics/collision_polygon_2d.cpp +++ b/scene/2d/physics/collision_polygon_2d.cpp @@ -232,7 +232,7 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl #endif PackedStringArray CollisionPolygon2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); diff --git a/scene/2d/physics/collision_shape_2d.cpp b/scene/2d/physics/collision_shape_2d.cpp index 6fc29ffe63..bdd0d06b5e 100644 --- a/scene/2d/physics/collision_shape_2d.cpp +++ b/scene/2d/physics/collision_shape_2d.cpp @@ -174,7 +174,7 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double } PackedStringArray CollisionShape2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/2d/physics/physical_bone_2d.cpp b/scene/2d/physics/physical_bone_2d.cpp index 77bb8c24b8..19274c8084 100644 --- a/scene/2d/physics/physical_bone_2d.cpp +++ b/scene/2d/physics/physical_bone_2d.cpp @@ -107,7 +107,7 @@ void PhysicalBone2D::_find_joint_child() { } PackedStringArray PhysicalBone2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = RigidBody2D::get_configuration_warnings(); if (!parent_skeleton) { warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index 920f5720fa..1816a3409b 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -211,7 +211,7 @@ void RemoteTransform2D::force_update_cache() { } PackedStringArray RemoteTransform2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { warnings.push_back(RTR("Path property must point to a valid Node2D node to work.")); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index f9e8c831d1..90bfb4c84c 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -412,7 +412,7 @@ int Bone2D::get_index_in_skeleton() const { } PackedStringArray Bone2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!skeleton) { if (parent_bone) { warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node.")); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index b10f2097da..45cfb8cf33 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -827,7 +827,7 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) { } PackedStringArray TileMap::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); warnings.push_back(RTR("The TileMap node is deprecated as it is superseded by the use of multiple TileMapLayer nodes.\nTo convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with this node selected, click the toolbox icon in the top-right corner and choose \"Extract TileMap layers as individual TileMapLayer nodes\".")); diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 485599d0fb..8702b1d3da 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -163,7 +163,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const { } PackedStringArray Decal::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends.")); diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 54631a8dff..195074ba2f 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -116,7 +116,7 @@ AABB FogVolume::get_aabb() const { } PackedStringArray FogVolume::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment(); diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 3a05ec9c9e..9791f23bc3 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { } PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings(); if (bake_mask == 0) { warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 4048a8bd62..26a574cd26 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1600,7 +1600,7 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const { } PackedStringArray LightmapGI::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index bebba9a6c0..0cce21b9d0 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -453,7 +453,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) { } PackedStringArray NavigationLink3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful.")); diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index d7397a932d..c0c254e7ed 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -271,7 +271,7 @@ bool NavigationRegion3D::is_baking() const { } PackedStringArray NavigationRegion3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navigation_mesh.is_valid()) { diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 6982df12f6..6d88323c76 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -691,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, } PackedStringArray OccluderInstance3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) { warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling.")); diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index dc030b6a0f..20d646fe1e 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -318,7 +318,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const { } PackedStringArray PathFollow3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path3D>(get_parent())) { diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp index f11aa7012a..f0a5013ca2 100644 --- a/scene/3d/physics/collision_object_3d.cpp +++ b/scene/3d/physics/collision_object_3d.cpp @@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const { } PackedStringArray CollisionObject3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape.")); diff --git a/scene/3d/physics/collision_polygon_3d.cpp b/scene/3d/physics/collision_polygon_3d.cpp index 76cd4db779..bf8dec7b54 100644 --- a/scene/3d/physics/collision_polygon_3d.cpp +++ b/scene/3d/physics/collision_polygon_3d.cpp @@ -169,7 +169,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) { } PackedStringArray CollisionPolygon3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!Object::cast_to<CollisionObject3D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape.")); diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp index f3492a3cf3..304fa74b06 100644 --- a/scene/3d/physics/collision_shape_3d.cpp +++ b/scene/3d/physics/collision_shape_3d.cpp @@ -120,7 +120,7 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) { #endif PackedStringArray CollisionShape3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp index b4c321cf5f..5073705145 100644 --- a/scene/3d/physics/vehicle_body_3d.cpp +++ b/scene/3d/physics/vehicle_body_3d.cpp @@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) { } PackedStringArray VehicleWheel3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!Object::cast_to<VehicleBody3D>(get_parent())) { warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D.")); diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index e580882c46..f970879aa4 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -211,7 +211,7 @@ void RemoteTransform3D::force_update_cache() { } PackedStringArray RemoteTransform3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) { warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work.")); diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 4fe5dd2385..7f67bde0cf 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -383,7 +383,7 @@ void SoftBody3D::_bind_methods() { } PackedStringArray SoftBody3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = MeshInstance3D::get_configuration_warnings(); if (mesh.is_null()) { warnings.push_back(RTR("This body will be ignored until you set a mesh.")); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 79a01450dd..a59754c8cc 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -497,7 +497,7 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() { } PackedStringArray GeometryInstance3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) { warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance.")); diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index ffca856fba..80ff176a98 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -518,7 +518,7 @@ AABB VoxelGI::get_aabb() const { } PackedStringArray VoxelGI::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index b71f9bc0c4..214c1f77ca 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -77,7 +77,7 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) { } PackedStringArray XRCamera3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Camera3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // Warn if the node has a parent which isn't an XROrigin3D! @@ -461,7 +461,7 @@ XRNode3D::~XRNode3D() { } PackedStringArray XRNode3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // Warn if the node has a parent which isn't an XROrigin3D! @@ -644,7 +644,7 @@ Plane XRAnchor3D::get_plane() const { Vector<XROrigin3D *> XROrigin3D::origin_nodes; PackedStringArray XROrigin3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { bool has_camera = false; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 1c5f40f56e..664302d45e 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -1636,6 +1636,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } if (t_obj->call(SNAME("get_is_sample"))) { + if (t->audio_stream_playback->get_sample_playback().is_valid()) { + AudioServer::get_singleton()->stop_sample_playback(t->audio_stream_playback->get_sample_playback()); + } Ref<AudioSamplePlayback> sample_playback; sample_playback.instantiate(); sample_playback->stream = stream; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 867bbda4b3..19080e61de 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -681,7 +681,7 @@ uint64_t AnimationTree::get_last_process_pass() const { } PackedStringArray AnimationTree::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = AnimationMixer::get_configuration_warnings(); if (!root_animation_node.is_valid()) { warnings.push_back(RTR("No root AnimationNode for the graph is set.")); } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 15ada0021a..e030828595 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -248,7 +248,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List PackedStringArray Control::get_configuration_warnings() const { ERR_READ_THREAD_GUARD_V(PackedStringArray()); - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = CanvasItem::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); @@ -2356,6 +2356,24 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons points[2] = xform.xform(c->get_size()); points[3] = xform.xform(Point2(0, c->get_size().y)); + // Tie-breaking aims to address situations where a potential focus neighbor's bounding rect + // is right next to the currently focused control (e.g. in BoxContainer with + // separation overridden to 0). This needs specific handling so that the correct + // focus neighbor is selected. + + // Calculate centers of the potential neighbor, currently focused, and closest controls. + Point2 center = xform.xform(0.5 * c->get_size()); + // We only have the points, not an actual reference. + Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]); + Point2 closest_center; + bool should_tiebreak = false; + if (*r_closest != nullptr) { + should_tiebreak = true; + Control *closest = *r_closest; + Transform2D closest_xform = closest->get_global_transform(); + closest_center = closest_xform.xform(0.5 * closest->get_size()); + } + real_t min = 1e7; for (int i = 0; i < 4; i++) { @@ -2376,10 +2394,15 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Vector2 pa, pb; real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb); - //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0)); if (d < r_closest_dist) { r_closest_dist = d; *r_closest = c; + } else if (should_tiebreak && d == r_closest_dist) { + // Tie-break in favor of the control most aligned with p_dir. + if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) { + r_closest_dist = d; + *r_closest = c; + } } } } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 83359653f1..d7b1a4933d 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -31,7 +31,7 @@ #include "range.h" PackedStringArray Range::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Control::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 715b682342..552458245c 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -3412,6 +3412,21 @@ bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) { selection.click_item = nullptr; selection.active = false; + if (is_processing_internal()) { + bool process_enabled = false; + Item *it = main; + while (it) { + Vector<ItemFX *> fx_stack; + _fetch_item_fx_stack(it, fx_stack); + if (fx_stack.size()) { + process_enabled = true; + break; + } + it = _get_next_item(it, true); + } + set_process_internal(process_enabled); + } + if (p_no_invalidate) { // Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar. int to_line = main->first_invalid_line.load() - 1; @@ -3985,6 +4000,7 @@ void RichTextLabel::pop_all() { void RichTextLabel::clear() { _stop_thread(); + set_process_internal(false); MutexLock data_lock(data_mutex); main->_clear_children(); @@ -4177,8 +4193,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { bool after_list_open_tag = false; bool after_list_close_tag = false; - set_process_internal(false); - while (pos <= p_bbcode.length()) { int brk_pos = p_bbcode.find_char('[', pos); @@ -5253,17 +5267,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - - Vector<ItemFX *> fx_items; - for (Item *E : main->subitems) { - Item *subitem = static_cast<Item *>(E); - _fetch_item_fx_stack(subitem, fx_items); - - if (fx_items.size()) { - set_process_internal(true); - break; - } - } } void RichTextLabel::scroll_to_selection() { diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index c715aceb0b..a443ae9abf 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -259,7 +259,7 @@ void SubViewportContainer::remove_child_notify(Node *p_child) { } PackedStringArray SubViewportContainer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Container::get_configuration_warnings(); bool has_viewport = false; for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index edd25d1d5c..8871af23cb 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -3466,29 +3466,37 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) { + if (!selected_item || selected_col > (columns.size() - 1)) { return; } + if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(false); - } else { + } else if (select_mode != SELECT_ROW) { _go_right(); + } else if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) { + selected_item->set_collapsed(false); + } else { + _go_down(); } } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) { + if (!selected_item || selected_col < 0) { return; } if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(true); - } else { + } else if (select_mode != SELECT_ROW) { _go_left(); + } else if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) { + selected_item->set_collapsed(true); + } else { + _go_up(); } - } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); diff --git a/scene/resources/audio_stream_polyphonic.cpp b/scene/resources/audio_stream_polyphonic.cpp index 999b0c9f0a..c7b8b1c723 100644 --- a/scene/resources/audio_stream_polyphonic.cpp +++ b/scene/resources/audio_stream_polyphonic.cpp @@ -247,6 +247,11 @@ AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(con sp->volume_vector.write[2] = AudioFrame(linear_volume, linear_volume); sp->volume_vector.write[3] = AudioFrame(linear_volume, linear_volume); sp->bus = p_bus; + + if (streams[i].stream_playback->get_sample_playback().is_valid()) { + AudioServer::get_singleton()->stop_playback_stream(sp); + } + streams[i].stream_playback->set_sample_playback(sp); AudioServer::get_singleton()->start_sample_playback(sp); } @@ -315,6 +320,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackPolyphonic::get_sample_playback() co void AudioStreamPlaybackPolyphonic::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } } void AudioStreamPlaybackPolyphonic::_bind_methods() { diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index 08ebacc2b3..de67a93bd1 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -123,10 +123,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int16_t nibble, diff, step; p_ima_adpcm[i].last_nibble++; - const uint8_t *src_ptr = (const uint8_t *)base->data; - src_ptr += AudioStreamWAV::DATA_PAD; - uint8_t nbb = src_ptr[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i]; + uint8_t nbb = p_src[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i]; nibble = (p_ima_adpcm[i].last_nibble & 1) ? (nbb >> 4) : (nbb & 0xF); step = _ima_adpcm_step_table[p_ima_adpcm[i].step_index]; @@ -184,9 +182,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, if (p_qoa->data_ofs != new_data_ofs) { p_qoa->data_ofs = new_data_ofs; - const uint8_t *src_ptr = (const uint8_t *)base->data; - src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD; - qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); + const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs; + qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len); } uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels; @@ -267,7 +264,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, } int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { - if (!base->data || !active) { + if (base->data.is_empty() || !active) { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } @@ -324,8 +321,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ /* audio data */ - uint8_t *dataptr = (uint8_t *)base->data; - const void *data = dataptr + AudioStreamWAV::DATA_PAD; + const uint8_t *data = base->data.ptr() + AudioStreamWAV::DATA_PAD; AudioFrame *dst_buff = p_buffer; if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) { @@ -479,15 +475,14 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::get_sample_playback() const { void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } } AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} -AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { - if (qoa.dec) { - memfree(qoa.dec); - } -} +AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {} ///////////////////// @@ -554,7 +549,7 @@ double AudioStreamWAV::get_length() const { break; case AudioStreamWAV::FORMAT_QOA: qoa_desc desc = {}; - qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc); + qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &desc); len = desc.samples * desc.channels; break; } @@ -572,22 +567,16 @@ bool AudioStreamWAV::is_monophonic() const { void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); - if (data) { - memfree(data); - data = nullptr; - data_bytes = 0; - } - int datalen = p_data.size(); - if (datalen) { - const uint8_t *r = p_data.ptr(); - int alloc_len = datalen + DATA_PAD * 2; - data = memalloc(alloc_len); //alloc with some padding for interpolation - memset(data, 0, alloc_len); - uint8_t *dataptr = (uint8_t *)data; - memcpy(dataptr + DATA_PAD, r, datalen); - data_bytes = datalen; - } + int src_data_len = p_data.size(); + + data.clear(); + + int alloc_len = src_data_len + DATA_PAD * 2; + data.resize(alloc_len); + memset(data.ptr(), 0, alloc_len); + memcpy(data.ptr() + DATA_PAD, p_data.ptr(), src_data_len); + data_bytes = src_data_len; AudioServer::get_singleton()->unlock(); } @@ -595,13 +584,9 @@ void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) { Vector<uint8_t> AudioStreamWAV::get_data() const { Vector<uint8_t> pv; - if (data) { + if (!data.is_empty()) { pv.resize(data_bytes); - { - uint8_t *w = pv.ptrw(); - uint8_t *dataptr = (uint8_t *)data; - memcpy(w, dataptr + DATA_PAD, data_bytes); - } + memcpy(pv.ptrw(), data.ptr() + DATA_PAD, data_bytes); } return pv; @@ -693,12 +678,12 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { sample->base = Ref<AudioStreamWAV>(this); if (format == AudioStreamWAV::FORMAT_QOA) { - uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &sample->qoa.desc); + uint32_t ffp = qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &sample->qoa.desc); ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>()); sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc); int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples); - int alloc_len = sample->qoa.desc.channels * samples_len * sizeof(int16_t); - sample->qoa.dec = (int16_t *)memalloc(alloc_len); + int dec_len = sample->qoa.desc.channels * samples_len; + sample->qoa.dec.resize(dec_len); } return sample; @@ -780,10 +765,4 @@ void AudioStreamWAV::_bind_methods() { AudioStreamWAV::AudioStreamWAV() {} -AudioStreamWAV::~AudioStreamWAV() { - if (data) { - memfree(data); - data = nullptr; - data_bytes = 0; - } -} +AudioStreamWAV::~AudioStreamWAV() {} diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 47aa10e790..bc62e8883a 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -62,7 +62,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { qoa_desc desc = {}; uint32_t data_ofs = 0; uint32_t frame_len = 0; - int16_t *dec = nullptr; + LocalVector<int16_t> dec; uint32_t dec_len = 0; int64_t cache_pos = -1; int16_t cache[2] = { 0, 0 }; @@ -137,7 +137,7 @@ private: int loop_begin = 0; int loop_end = 0; int mix_rate = 44100; - void *data = nullptr; + LocalVector<uint8_t> data; uint32_t data_bytes = 0; protected: diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 62ea749465..69dc71e414 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -1354,25 +1354,6 @@ Ref<SceneState> SceneState::get_base_scene_state() const { return Ref<SceneState>(); } -void SceneState::update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene) { - ERR_FAIL_COND(p_packed_scene.is_null()); - - for (const NodeData &nd : nodes) { - if (nd.instance >= 0) { - if (!(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER)) { - int instance_id = nd.instance & FLAG_MASK; - Ref<PackedScene> original_packed_scene = variants[instance_id]; - if (original_packed_scene.is_valid()) { - if (original_packed_scene->get_path() == p_path) { - variants.remove_at(instance_id); - variants.insert(instance_id, p_packed_scene); - } - } - } - } - } -} - int SceneState::find_node_by_path(const NodePath &p_node) const { ERR_FAIL_COND_V_MSG(node_path_cache.is_empty(), -1, "This operation requires the node cache to have been built."); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index e2c8686911..ece088a694 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -173,12 +173,12 @@ int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, } float mu2 = mu * mu; - AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0; - AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3; - AudioFrame a2 = y2 - y0; - AudioFrame a3 = 2 * y1; + float h11 = mu2 * (mu - 1); + float z = mu2 - h11; + float h01 = z - h11; + float h10 = mu - z; - p_buffer[i] = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3) / 2; + p_buffer[i] = y1 + (y2 - y1) * h01 + ((y2 - y0) * h10 + (y3 - y1) * h11) * 0.5; mix_offset += mix_increment; diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index c41545aeba..7f2b68a796 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -48,6 +48,7 @@ class AudioSamplePlayback : public RefCounted { public: Ref<AudioStream> stream; + Ref<AudioStreamPlayback> stream_playback; float offset = 0.0f; float pitch_scale = 1.0; diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 332f8984a2..e06079efe8 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -501,12 +501,7 @@ void AudioServer::_mix_step() { switch (playback->state.load()) { case AudioStreamPlaybackListNode::AWAITING_DELETION: case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION: - playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) { - delete p->prev_bus_details; - delete p->bus_details.load(); - p->stream_playback.unref(); - delete p; - }); + _delete_stream_playback_list_node(playback); break; case AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE: { // Pause the stream. @@ -697,6 +692,23 @@ AudioServer::AudioStreamPlaybackListNode *AudioServer::_find_playback_list_node( return nullptr; } +void AudioServer::_delete_stream_playback(Ref<AudioStreamPlayback> p_playback) { + ERR_FAIL_COND(p_playback.is_null()); + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); + if (playback_node) { + _delete_stream_playback_list_node(playback_node); + } +} + +void AudioServer::_delete_stream_playback_list_node(AudioStreamPlaybackListNode *p_playback_node) { + playback_list.erase(p_playback_node, [](AudioStreamPlaybackListNode *p) { + delete p->prev_bus_details; + delete p->bus_details.load(); + p->stream_playback.unref(); + delete p; + }); +} + bool AudioServer::thread_has_channel_mix_buffer(int p_bus, int p_buffer) const { if (p_bus < 0 || p_bus >= buses.size()) { return false; @@ -1227,8 +1239,12 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) { ERR_FAIL_COND(p_playback.is_null()); // Handle sample playback. - if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) { - AudioServer::get_singleton()->stop_sample_playback(p_playback->get_sample_playback()); + if (p_playback->get_is_sample()) { + if (p_playback->get_sample_playback().is_valid()) { + AudioServer::get_singleton()->stop_sample_playback(p_playback->get_sample_playback()); + } else { + _delete_stream_playback(p_playback); + } return; } @@ -1370,8 +1386,12 @@ void AudioServer::set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playb bool AudioServer::is_playback_active(Ref<AudioStreamPlayback> p_playback) { ERR_FAIL_COND_V(p_playback.is_null(), false); - if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) { - return sample_playback_list.has(p_playback->get_sample_playback()); + if (p_playback->get_is_sample()) { + if (p_playback->get_sample_playback().is_valid()) { + return sample_playback_list.has(p_playback->get_sample_playback()); + } else { + return false; + } } AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); @@ -1845,8 +1865,12 @@ void AudioServer::start_sample_playback(const Ref<AudioSamplePlayback> &p_playba void AudioServer::stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null."); - AudioDriver::get_singleton()->stop_sample_playback(p_playback); - sample_playback_list.erase(p_playback); + if (sample_playback_list.has(p_playback)) { + sample_playback_list.erase(p_playback); + AudioDriver::get_singleton()->stop_sample_playback(p_playback); + p_playback->stream_playback->set_sample_playback(nullptr); + stop_playback_stream(p_playback->stream_playback); + } } void AudioServer::set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) { diff --git a/servers/audio_server.h b/servers/audio_server.h index 2d6fc60860..16fcc029b3 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -297,6 +297,8 @@ private: SafeList<AudioStreamPlaybackListNode *> playback_list; SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard; + void _delete_stream_playback(Ref<AudioStreamPlayback> p_playback); + void _delete_stream_playback_list_node(AudioStreamPlaybackListNode *p_node); // TODO document if this is necessary. SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard_frame_old; diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp index 63dc54e24c..9f390c99f9 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp @@ -342,7 +342,7 @@ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataType type, int p_ } } -_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::ConstantNode::Value> &value, uint8_t *data) { +_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::Scalar> &value, uint8_t *data) { switch (type) { case ShaderLanguage::TYPE_BOOL: { uint32_t *gui = (uint32_t *)data; @@ -566,7 +566,7 @@ void MaterialStorage::ShaderData::set_default_texture_parameter(const StringName Variant MaterialStorage::ShaderData::get_default_parameter(const StringName &p_parameter) const { if (uniforms.has(p_parameter)) { ShaderLanguage::ShaderNode::Uniform uniform = uniforms[p_parameter]; - Vector<ShaderLanguage::ConstantNode::Value> default_value = uniform.default_value; + Vector<ShaderLanguage::Scalar> default_value = uniform.default_value; return ShaderLanguage::constant_value_to_variant(default_value, uniform.type, uniform.array_size, uniform.hint); } return Variant(); diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp index 49e005ca96..43703f8656 100644 --- a/servers/rendering/shader_compiler.cpp +++ b/servers/rendering/shader_compiler.cpp @@ -185,7 +185,7 @@ static String f2sp0(float p_float) { return num; } -static String get_constant_text(SL::DataType p_type, const Vector<SL::ConstantNode::Value> &p_values) { +static String get_constant_text(SL::DataType p_type, const Vector<SL::Scalar> &p_values) { switch (p_type) { case SL::TYPE_BOOL: return p_values[0].boolean ? "true" : "false"; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 4eaf7fcb55..2249cd2010 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -1362,7 +1362,7 @@ void ShaderLanguage::_parse_used_identifier(const StringName &p_identifier, Iden } #endif // DEBUG_ENABLED -bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name, ConstantNode::Value *r_constant_value) { +bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name, Vector<Scalar> *r_constant_values) { if (is_shader_inc) { for (int i = 0; i < RenderingServer::SHADER_MAX; i++) { for (const KeyValue<StringName, FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) { @@ -1424,8 +1424,8 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea if (r_struct_name) { *r_struct_name = p_block->variables[p_identifier].struct_name; } - if (r_constant_value) { - *r_constant_value = p_block->variables[p_identifier].value; + if (r_constant_values && !p_block->variables[p_identifier].values.is_empty()) { + *r_constant_values = p_block->variables[p_identifier].values; } if (r_type) { *r_type = IDENTIFIER_LOCAL_VAR; @@ -1507,13 +1507,9 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea if (r_struct_name) { *r_struct_name = shader->constants[p_identifier].struct_name; } - if (r_constant_value) { - if (shader->constants[p_identifier].initializer && shader->constants[p_identifier].initializer->type == Node::NODE_TYPE_CONSTANT) { - ConstantNode *cnode = static_cast<ConstantNode *>(shader->constants[p_identifier].initializer); - - if (cnode->values.size() == 1) { - *r_constant_value = cnode->values[0]; - } + if (r_constant_values) { + if (shader->constants[p_identifier].initializer && !shader->constants[p_identifier].initializer->get_values().is_empty()) { + *r_constant_values = shader->constants[p_identifier].initializer->get_values(); } } if (r_type) { @@ -1544,7 +1540,7 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea return false; } -bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type, int *r_ret_size) { +bool ShaderLanguage::_validate_operator(const BlockNode *p_block, OperatorNode *p_op, DataType *r_ret_type, int *r_ret_size) { bool valid = false; DataType ret_type = TYPE_VOID; int ret_size = 0; @@ -2007,9 +2003,384 @@ bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type if (r_ret_size) { *r_ret_size = ret_size; } + + if (valid && (!p_block || p_block->use_op_eval)) { + // Need to be placed here and not in the `_reduce_expression` because otherwise expressions like `1 + 2 / 2` will not work correctly. + valid = _eval_operator(p_block, p_op); + } + return valid; } +Vector<ShaderLanguage::Scalar> ShaderLanguage::_get_node_values(const BlockNode *p_block, Node *p_node) { + Vector<Scalar> result; + + switch (p_node->type) { + case Node::NODE_TYPE_VARIABLE: { + _find_identifier(p_block, false, FunctionInfo(), static_cast<VariableNode *>(p_node)->name, nullptr, nullptr, nullptr, nullptr, nullptr, &result); + } break; + default: { + result = p_node->get_values(); + } break; + } + + return result; +} + +bool ShaderLanguage::_eval_operator(const BlockNode *p_block, OperatorNode *p_op) { + bool is_valid = true; + + switch (p_op->op) { + case OP_EQUAL: + case OP_NOT_EQUAL: + case OP_LESS: + case OP_LESS_EQUAL: + case OP_GREATER: + case OP_GREATER_EQUAL: + case OP_AND: + case OP_OR: + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_MOD: + case OP_SHIFT_LEFT: + case OP_SHIFT_RIGHT: + case OP_BIT_AND: + case OP_BIT_OR: + case OP_BIT_XOR: { + DataType a = p_op->arguments[0]->get_datatype(); + DataType b = p_op->arguments[1]->get_datatype(); + + bool is_op_vec_transform = false; + if (p_op->op == OP_MUL) { + DataType ta = a; + DataType tb = b; + + if (ta > tb) { + SWAP(ta, tb); + } + if (ta == TYPE_VEC2 && tb == TYPE_MAT2) { + is_op_vec_transform = true; + } else if (ta == TYPE_VEC3 && tb == TYPE_MAT3) { + is_op_vec_transform = true; + } else if (ta == TYPE_VEC4 && tb == TYPE_MAT4) { + is_op_vec_transform = true; + } + } + + Vector<Scalar> va = _get_node_values(p_block, p_op->arguments[0]); + Vector<Scalar> vb = _get_node_values(p_block, p_op->arguments[1]); + + if (is_op_vec_transform) { + p_op->values = _eval_vector_transform(va, vb, a, b, p_op->get_datatype()); + } else { + p_op->values = _eval_vector(va, vb, a, b, p_op->get_datatype(), p_op->op, is_valid); + } + } break; + case OP_NOT: + case OP_NEGATE: + case OP_BIT_INVERT: { + p_op->values = _eval_unary_vector(_get_node_values(p_block, p_op->arguments[0]), p_op->get_datatype(), p_op->op); + } break; + default: { + } break; + } + + return is_valid; +} + +ShaderLanguage::Scalar ShaderLanguage::_eval_unary_scalar(const Scalar &p_a, Operator p_op, DataType p_ret_type) { + Scalar scalar; + + switch (p_op) { + case OP_NOT: { + scalar.boolean = !p_a.boolean; + } break; + case OP_NEGATE: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = -p_a.sint; + } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) { + // Intentionally wrap the unsigned int value, because GLSL does. + scalar.uint = 0 - p_a.uint; + } else { // float types + scalar.real = -scalar.real; + } + } break; + case OP_BIT_INVERT: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = ~p_a.sint; + } else { // uint types + scalar.uint = ~p_a.uint; + } + } break; + default: { + } break; + } + + return scalar; +} + +ShaderLanguage::Scalar ShaderLanguage::_eval_scalar(const Scalar &p_a, const Scalar &p_b, Operator p_op, DataType p_ret_type, bool &r_is_valid) { + Scalar scalar; + + switch (p_op) { + case OP_EQUAL: { + scalar.boolean = p_a.boolean == p_b.boolean; + } break; + case OP_NOT_EQUAL: { + scalar.boolean = p_a.boolean != p_b.boolean; + } break; + case OP_LESS: { + if (p_ret_type == TYPE_INT) { + scalar.boolean = p_a.sint < p_b.sint; + } else if (p_ret_type == TYPE_UINT) { + scalar.boolean = p_a.uint < p_b.uint; + } else { // float type + scalar.boolean = p_a.real < p_b.real; + } + } break; + case OP_LESS_EQUAL: { + if (p_ret_type == TYPE_INT) { + scalar.boolean = p_a.sint <= p_b.sint; + } else if (p_ret_type == TYPE_UINT) { + scalar.boolean = p_a.uint <= p_b.uint; + } else { // float type + scalar.boolean = p_a.real <= p_b.real; + } + } break; + case OP_GREATER: { + if (p_ret_type == TYPE_INT) { + scalar.boolean = p_a.sint > p_b.sint; + } else if (p_ret_type == TYPE_UINT) { + scalar.boolean = p_a.uint > p_b.uint; + } else { // float type + scalar.boolean = p_a.real > p_b.real; + } + } break; + case OP_GREATER_EQUAL: { + if (p_ret_type == TYPE_INT) { + scalar.boolean = p_a.sint >= p_b.sint; + } else if (p_ret_type == TYPE_UINT) { + scalar.boolean = p_a.uint >= p_b.uint; + } else { // float type + scalar.boolean = p_a.real >= p_b.real; + } + } break; + case OP_AND: { + scalar.boolean = p_a.boolean && p_b.boolean; + } break; + case OP_OR: { + scalar.boolean = p_a.boolean || p_b.boolean; + } break; + case OP_ADD: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint + p_b.sint; + } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) { + scalar.uint = p_a.uint + p_b.uint; + } else { // float + matrix types + scalar.real = p_a.real + p_b.real; + } + } break; + case OP_SUB: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint - p_b.sint; + } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) { + scalar.uint = p_a.uint - p_b.uint; + } else { // float + matrix types + scalar.real = p_a.real - p_b.real; + } + } break; + case OP_MUL: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint * p_b.sint; + } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) { + scalar.uint = p_a.uint * p_b.uint; + } else { // float + matrix types + scalar.real = p_a.real * p_b.real; + } + } break; + case OP_DIV: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + if (p_b.sint == 0) { + _set_error(RTR("Division by zero error.")); + r_is_valid = false; + break; + } + scalar.sint = p_a.sint / p_b.sint; + } else if (p_ret_type == TYPE_UINT && p_ret_type <= TYPE_UVEC4) { + if (p_b.uint == 0U) { + _set_error(RTR("Division by zero error.")); + r_is_valid = false; + break; + } + scalar.uint = p_a.uint / p_b.uint; + } else { // float + matrix types + scalar.real = p_a.real / p_b.real; + } + } break; + case OP_MOD: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + if (p_b.sint == 0) { + _set_error(RTR("Modulo by zero error.")); + r_is_valid = false; + break; + } + scalar.sint = p_a.sint % p_b.sint; + } else { // uint types + if (p_b.uint == 0U) { + _set_error(RTR("Modulo by zero error.")); + r_is_valid = false; + break; + } + scalar.uint = p_a.uint % p_b.uint; + } + } break; + case OP_SHIFT_LEFT: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint << p_b.sint; + } else { // uint types + scalar.uint = p_a.uint << p_b.uint; + } + } break; + case OP_SHIFT_RIGHT: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint >> p_b.sint; + } else { // uint types + scalar.uint = p_a.uint >> p_b.uint; + } + } break; + case OP_BIT_AND: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint & p_b.sint; + } else { // uint types + scalar.uint = p_a.uint & p_b.uint; + } + } break; + case OP_BIT_OR: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint | p_b.sint; + } else { // uint types + scalar.uint = p_a.uint | p_b.uint; + } + } break; + case OP_BIT_XOR: { + if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) { + scalar.sint = p_a.sint ^ p_b.sint; + } else { // uint types + scalar.uint = p_a.uint ^ p_b.uint; + } + } break; + default: { + } break; + } + + return scalar; +} + +Vector<ShaderLanguage::Scalar> ShaderLanguage::_eval_unary_vector(const Vector<Scalar> &p_va, DataType p_ret_type, Operator p_op) { + uint32_t size = get_datatype_component_count(p_ret_type); + if (p_va.size() != p_ret_type) { + return Vector<Scalar>(); // Non-evaluable values should not be parsed further. + } + Vector<Scalar> value; + value.resize(size); + + Scalar *w = value.ptrw(); + for (uint32_t i = 0U; i < size; i++) { + w[i] = _eval_unary_scalar(p_va[i], p_op, p_ret_type); + } + return value; +} + +Vector<ShaderLanguage::Scalar> ShaderLanguage::_eval_vector(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type, Operator p_op, bool &r_is_valid) { + uint32_t left_size = get_datatype_component_count(p_left_type); + uint32_t right_size = get_datatype_component_count(p_right_type); + + if (p_va.size() != left_size || p_vb.size() != right_size) { + return Vector<Scalar>(); // Non-evaluable values should not be parsed further. + } + + uint32_t ret_size = get_datatype_component_count(p_ret_type); + Vector<Scalar> value; + value.resize(ret_size); + + Scalar *w = value.ptrw(); + for (uint32_t i = 0U; i < ret_size; i++) { + w[i] = _eval_scalar(p_va[MIN(i, left_size - 1)], p_vb[MIN(i, right_size - 1)], p_op, p_ret_type, r_is_valid); + if (!r_is_valid) { + return value; + } + } + return value; +} + +Vector<ShaderLanguage::Scalar> ShaderLanguage::_eval_vector_transform(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type) { + uint32_t left_size = get_datatype_component_count(p_left_type); + uint32_t right_size = get_datatype_component_count(p_right_type); + + if (p_va.size() != left_size || p_vb.size() != right_size) { + return Vector<Scalar>(); // Non-evaluable values should not be parsed further. + } + + uint32_t ret_size = get_datatype_component_count(p_ret_type); + Vector<Scalar> value; + value.resize_zeroed(ret_size); + + Scalar *w = value.ptrw(); + switch (p_ret_type) { + case TYPE_VEC2: { + if (left_size == 2) { // v * m + Vector2 v = Vector2(p_va[0].real, p_va[1].real); + + w[0].real = (p_vb[0].real * v.x + p_vb[1].real * v.y); + w[1].real = (p_vb[2].real * v.x + p_vb[3].real * v.y); + } else { // m * v + Vector2 v = Vector2(p_vb[0].real, p_vb[1].real); + + w[0].real = (p_va[0].real * v.x + p_va[2].real * v.y); + w[1].real = (p_va[1].real * v.x + p_va[3].real * v.y); + } + } break; + case TYPE_VEC3: { + if (left_size == 3) { // v * m + Vector3 v = Vector3(p_va[0].real, p_va[1].real, p_va[2].real); + + w[0].real = (p_vb[0].real * v.x + p_vb[1].real * v.y + p_vb[2].real * v.z); + w[1].real = (p_vb[3].real * v.x + p_vb[4].real * v.y + p_vb[5].real * v.z); + w[2].real = (p_vb[6].real * v.x + p_vb[7].real * v.y + p_vb[8].real * v.z); + } else { // m * v + Vector3 v = Vector3(p_vb[0].real, p_vb[1].real, p_vb[2].real); + + w[0].real = (p_va[0].real * v.x + p_va[3].real * v.y + p_va[6].real * v.z); + w[1].real = (p_va[1].real * v.x + p_va[4].real * v.y + p_va[7].real * v.z); + w[2].real = (p_va[2].real * v.x + p_va[5].real * v.y + p_va[8].real * v.z); + } + } break; + case TYPE_VEC4: { + if (left_size == 4) { // v * m + Vector4 v = Vector4(p_va[0].real, p_va[1].real, p_va[2].real, p_va[3].real); + + w[0].real = (p_vb[0].real * v.x + p_vb[1].real * v.y + p_vb[2].real * v.z + p_vb[3].real * v.w); + w[1].real = (p_vb[4].real * v.x + p_vb[5].real * v.y + p_vb[6].real * v.z + p_vb[7].real * v.w); + w[2].real = (p_vb[8].real * v.x + p_vb[9].real * v.y + p_vb[10].real * v.z + p_vb[11].real * v.w); + w[3].real = (p_vb[12].real * v.x + p_vb[13].real * v.y + p_vb[14].real * v.z + p_vb[15].real * v.w); + } else { // m * v + Vector4 v = Vector4(p_vb[0].real, p_vb[1].real, p_vb[2].real, p_vb[3].real); + + w[0].real = (p_vb[0].real * v.x + p_vb[4].real * v.y + p_vb[8].real * v.z + p_vb[12].real * v.w); + w[1].real = (p_vb[1].real * v.x + p_vb[5].real * v.y + p_vb[9].real * v.z + p_vb[13].real * v.w); + w[2].real = (p_vb[2].real * v.x + p_vb[6].real * v.y + p_vb[10].real * v.z + p_vb[14].real * v.w); + w[3].real = (p_vb[3].real * v.x + p_vb[7].real * v.y + p_vb[11].real * v.z + p_vb[15].real * v.w); + } + } break; + default: { + } break; + } + + return value; +} + const ShaderLanguage::BuiltinFuncDef ShaderLanguage::builtin_func_defs[] = { // Constructors. @@ -3271,34 +3642,15 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI int max = builtin_func_const_args[constarg_idx].max; bool error = false; - if (p_func->arguments[arg]->type == Node::NODE_TYPE_VARIABLE) { - const VariableNode *vn = static_cast<VariableNode *>(p_func->arguments[arg]); - - bool is_const = false; - ConstantNode::Value value; - value.sint = -1; - - _find_identifier(p_block, false, p_function_info, vn->name, nullptr, nullptr, &is_const, nullptr, nullptr, &value); - if (!is_const || value.sint < min || value.sint > max) { + Vector<Scalar> values = _get_node_values(p_block, p_func->arguments[arg]); + if (p_func->arguments[arg]->get_datatype() == TYPE_INT && !values.is_empty()) { + if (values[0].sint < min || values[0].sint > max) { error = true; } } else { - if (p_func->arguments[arg]->type == Node::NODE_TYPE_CONSTANT) { - const ConstantNode *cn = static_cast<ConstantNode *>(p_func->arguments[arg]); - - if (cn->get_datatype() == TYPE_INT && cn->values.size() == 1) { - int value = cn->values[0].sint; - - if (value < min || value > max) { - error = true; - } - } else { - error = true; - } - } else { - error = true; - } + error = true; } + if (error) { _set_error(vformat(RTR("Expected integer constant within [%d..%d] range."), min, max)); return false; @@ -3760,7 +4112,7 @@ bool ShaderLanguage::is_token_hint(TokenType p_type) { return int(p_type) > int(TK_RENDER_MODE) && int(p_type) < int(TK_SHADER_TYPE); } -bool ShaderLanguage::convert_constant(ConstantNode *p_constant, DataType p_to_type, ConstantNode::Value *p_value) { +bool ShaderLanguage::convert_constant(ConstantNode *p_constant, DataType p_to_type, Scalar *p_value) { if (p_constant->datatype == p_to_type) { if (p_value) { for (int i = 0; i < p_constant->values.size(); i++) { @@ -3828,7 +4180,7 @@ bool ShaderLanguage::is_sampler_type(DataType p_type) { return p_type > TYPE_MAT4 && p_type < TYPE_STRUCT; } -Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::ConstantNode::Value> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint) { +Variant ShaderLanguage::constant_value_to_variant(const Vector<Scalar> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint) { int array_size = p_array_size; if (p_value.size() > 0) { @@ -4437,6 +4789,52 @@ uint32_t ShaderLanguage::get_datatype_size(ShaderLanguage::DataType p_type) { ERR_FAIL_V(0); } +uint32_t ShaderLanguage::get_datatype_component_count(ShaderLanguage::DataType p_type) { + switch (p_type) { + case TYPE_BOOL: + return 1U; + case TYPE_BVEC2: + return 2U; + case TYPE_BVEC3: + return 3U; + case TYPE_BVEC4: + return 4U; + case TYPE_INT: + return 1U; + case TYPE_IVEC2: + return 2U; + case TYPE_IVEC3: + return 3U; + case TYPE_IVEC4: + return 4U; + case TYPE_UINT: + return 1U; + case TYPE_UVEC2: + return 2U; + case TYPE_UVEC3: + return 3U; + case TYPE_UVEC4: + return 4U; + case TYPE_FLOAT: + return 1U; + case TYPE_VEC2: + return 2U; + case TYPE_VEC3: + return 3U; + case TYPE_VEC4: + return 4U; + case TYPE_MAT2: + return 4U; + case TYPE_MAT3: + return 9U; + case TYPE_MAT4: + return 16U; + default: + break; + } + return 0U; +} + void ShaderLanguage::get_keyword_list(List<String> *r_keywords) { HashSet<String> kws; @@ -4929,45 +5327,30 @@ Error ShaderLanguage::_parse_array_size(BlockNode *p_block, const FunctionInfo & *r_unknown_size = true; } } else { - int array_size = 0; + _set_tkpos(pos); - if (!tk.is_integer_constant() || ((int)tk.constant) <= 0) { - _set_tkpos(pos); - Node *n = _parse_and_reduce_expression(p_block, p_function_info); - if (n) { - if (n->type == Node::NODE_TYPE_VARIABLE) { - VariableNode *vn = static_cast<VariableNode *>(n); - if (vn) { - ConstantNode::Value v; - DataType data_type; - bool is_const = false; + int array_size = 0; + Node *expr = _parse_and_reduce_expression(p_block, p_function_info); - _find_identifier(p_block, false, p_function_info, vn->name, &data_type, nullptr, &is_const, nullptr, nullptr, &v); + if (expr) { + Vector<Scalar> values = _get_node_values(p_block, expr); - if (is_const) { - if (data_type == TYPE_INT) { - int32_t value = v.sint; - if (value > 0) { - array_size = value; - } - } else if (data_type == TYPE_UINT) { - uint32_t value = v.uint; - if (value > 0U) { - array_size = value; - } - } - } - } - } else if (n->type == Node::NODE_TYPE_OPERATOR) { - _set_error(vformat(RTR("Array size expressions are not supported."))); - return ERR_PARSE_ERROR; - } - if (r_size_expression != nullptr) { - *r_size_expression = n; + if (!values.is_empty()) { + switch (expr->get_datatype()) { + case TYPE_INT: { + array_size = values[0].sint; + } break; + case TYPE_UINT: { + array_size = (int)values[0].uint; + } break; + default: { + } break; } } - } else if (((int)tk.constant) > 0) { - array_size = (uint32_t)tk.constant; + + if (r_size_expression != nullptr) { + *r_size_expression = expr; + } } if (array_size <= 0) { @@ -5064,7 +5447,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_array_constructor(BlockNode *p_bloc idx++; } if (!auto_size && !undefined_size && an->initializer.size() != array_size) { - _set_error(RTR("Array size mismatch.")); + _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), array_size, an->initializer.size())); return nullptr; } } else { @@ -5185,7 +5568,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_array_constructor(BlockNode *p_bloc } } if (an->initializer.size() != p_array_size) { - _set_error(RTR("Array size mismatch.")); + _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), p_array_size, an->initializer.size())); return nullptr; } } else { @@ -5230,7 +5613,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } else if (tk.type == TK_FLOAT_CONSTANT) { ConstantNode *constant = alloc_node<ConstantNode>(); - ConstantNode::Value v; + Scalar v; v.real = tk.constant; constant->values.push_back(v); constant->datatype = TYPE_FLOAT; @@ -5238,7 +5621,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } else if (tk.type == TK_INT_CONSTANT) { ConstantNode *constant = alloc_node<ConstantNode>(); - ConstantNode::Value v; + Scalar v; v.sint = tk.constant; constant->values.push_back(v); constant->datatype = TYPE_INT; @@ -5246,7 +5629,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } else if (tk.type == TK_UINT_CONSTANT) { ConstantNode *constant = alloc_node<ConstantNode>(); - ConstantNode::Value v; + Scalar v; v.uint = tk.constant; constant->values.push_back(v); constant->datatype = TYPE_UINT; @@ -5255,7 +5638,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } else if (tk.type == TK_TRUE) { //handle true constant ConstantNode *constant = alloc_node<ConstantNode>(); - ConstantNode::Value v; + Scalar v; v.boolean = true; constant->values.push_back(v); constant->datatype = TYPE_BOOL; @@ -5264,7 +5647,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } else if (tk.type == TK_FALSE) { //handle false constant ConstantNode *constant = alloc_node<ConstantNode>(); - ConstantNode::Value v; + Scalar v; v.boolean = false; constant->values.push_back(v); constant->datatype = TYPE_BOOL; @@ -6527,7 +6910,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons op->op = tk.type == TK_OP_DECREMENT ? OP_POST_DECREMENT : OP_POST_INCREMENT; op->arguments.push_back(expr); - if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) { + if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) { _set_error(RTR("Invalid base type for increment/decrement operator.")); return nullptr; } @@ -6876,7 +7259,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons expression.write[i].is_op = false; expression.write[i].node = op; - if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) { + if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) { + if (error_set) { + return nullptr; + } + String at; for (int j = 0; j < op->arguments.size(); j++) { if (j > 0) { @@ -6914,7 +7301,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons expression.write[next_op - 1].is_op = false; expression.write[next_op - 1].node = op; - if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) { + if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) { + if (error_set) { + return nullptr; + } + String at; for (int i = 0; i < op->arguments.size(); i++) { if (i > 0) { @@ -6950,6 +7341,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } if (_is_operator_assign(op->op)) { + if (p_block && expression[next_op - 1].node->type == Node::NODE_TYPE_VARIABLE) { + VariableNode *vn = static_cast<VariableNode *>(expression[next_op - 1].node); + p_block->use_op_eval = vn->is_const; + } + String assign_message; if (!_validate_assign(expression[next_op - 1].node, p_function_info, &assign_message)) { _set_error(assign_message); @@ -6972,7 +7368,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons //replace all 3 nodes by this operator and make it an expression - if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) { + if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) { + if (error_set) { + return nullptr; + } + String at; for (int i = 0; i < op->arguments.size(); i++) { if (i > 0) { @@ -6998,6 +7398,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } } + if (p_block) { + p_block->use_op_eval = true; + } + if (p_previous_expression_info != nullptr) { p_previous_expression_info->expression->push_back(expression[0]); } @@ -7020,7 +7424,7 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha DataType base = get_scalar_type(type); int cardinality = get_cardinality(type); - Vector<ConstantNode::Value> values; + Vector<Scalar> values; for (int i = 1; i < op->arguments.size(); i++) { op->arguments.write[i] = _reduce_expression(p_block, op->arguments[i]); @@ -7032,7 +7436,7 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha values.push_back(cn->values[j]); } } else if (get_scalar_type(cn->datatype) == cn->datatype) { - ConstantNode::Value v; + Scalar v; if (!convert_constant(cn, base, &v)) { return p_node; } @@ -7048,8 +7452,8 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha if (values.size() == 1) { if (type >= TYPE_MAT2 && type <= TYPE_MAT4) { - ConstantNode::Value value = values[0]; - ConstantNode::Value zero; + Scalar value = values[0]; + Scalar zero; zero.real = 0.0f; int size = 2 + (type - TYPE_MAT2); @@ -7060,7 +7464,7 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha } } } else { - ConstantNode::Value value = values[0]; + Scalar value = values[0]; for (int i = 1; i < cardinality; i++) { values.push_back(value); } @@ -7081,10 +7485,10 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha DataType base = get_scalar_type(cn->datatype); - Vector<ConstantNode::Value> values; + Vector<Scalar> values; for (int i = 0; i < cn->values.size(); i++) { - ConstantNode::Value nv; + Scalar nv; switch (base) { case TYPE_BOOL: { nv.boolean = !cn->values[i].boolean; @@ -7515,7 +7919,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun decl.size = decl.initializer.size(); var.array_size = decl.initializer.size(); } else if (decl.initializer.size() != var.array_size) { - _set_error(RTR("Array size mismatch.")); + _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), var.array_size, decl.initializer.size())); return ERR_PARSE_ERROR; } tk = _get_token(); @@ -7537,7 +7941,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun array_size = var.array_size; } else if (tk.type == TK_OP_ASSIGN) { - //variable created with assignment! must parse an expression + p_block->use_op_eval = is_const; + + // Variable created with assignment! Must parse an expression. Node *n = _parse_and_reduce_expression(p_block, p_function_info); if (!n) { return ERR_PARSE_ERROR; @@ -7552,11 +7958,8 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun } } - if (n->type == Node::NODE_TYPE_CONSTANT) { - ConstantNode *const_node = static_cast<ConstantNode *>(n); - if (const_node && const_node->values.size() == 1) { - var.value = const_node->values[0]; - } + if (is_const) { + var.values = n->get_values(); } if (!_compare_datatypes(var.type, var.struct_name, var.array_size, n->get_datatype(), n->get_datatype_name(), n->get_array_size())) { @@ -7734,13 +8137,13 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun if (!vn) { return ERR_PARSE_ERROR; } - ConstantNode::Value v; + Vector<Scalar> v = { Scalar() }; _find_identifier(p_block, false, p_function_info, vn->name, nullptr, nullptr, nullptr, nullptr, nullptr, &v); - if (constants.has(v.sint)) { - _set_error(vformat(RTR("Duplicated case label: %d."), v.sint)); + if (constants.has(v[0].sint)) { + _set_error(vformat(RTR("Duplicated case label: %d."), v[0].sint)); return ERR_PARSE_ERROR; } - constants.insert(v.sint); + constants.insert(v[0].sint); } } else if (flow->flow_op == FLOW_OP_DEFAULT) { continue; @@ -7801,7 +8204,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun vn->name = tk.text; n = vn; } else { - ConstantNode::Value v; + Scalar v; if (tk.type == TK_UINT_CONSTANT) { v.uint = (uint32_t)tk.constant; } else { @@ -9678,7 +10081,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f decl.size = decl.initializer.size(); constant.array_size = decl.initializer.size(); } else if (decl.initializer.size() != constant.array_size) { - _set_error(RTR("Array size mismatch.")); + _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), constant.array_size, decl.initializer.size())); return ERR_PARSE_ERROR; } } else { diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index 63dca99654..ba02e181b9 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -355,6 +355,13 @@ public: } }; + union Scalar { + bool boolean = false; + float real; + int32_t sint; + uint32_t uint; + }; + struct Node { Node *next = nullptr; @@ -379,6 +386,7 @@ public: virtual String get_datatype_name() const { return ""; } virtual int get_array_size() const { return 0; } virtual bool is_indexed() const { return false; } + virtual Vector<Scalar> get_values() const { return Vector<Scalar>(); } Node(Type t) : type(t) {} @@ -402,11 +410,13 @@ public: Operator op = OP_EQUAL; StringName struct_name; Vector<Node *> arguments; + Vector<Scalar> values; virtual DataType get_datatype() const override { return return_cache; } virtual String get_datatype_name() const override { return String(struct_name); } virtual int get_array_size() const override { return return_array_size; } virtual bool is_indexed() const override { return op == OP_INDEX; } + virtual Vector<Scalar> get_values() const override { return values; } OperatorNode() : Node(NODE_TYPE_OPERATOR) {} @@ -485,19 +495,15 @@ public: String struct_name = ""; int array_size = 0; - union Value { - bool boolean = false; - float real; - int32_t sint; - uint32_t uint; - }; - - Vector<Value> values; + Vector<Scalar> values; Vector<VariableDeclarationNode::Declaration> array_declarations; virtual DataType get_datatype() const override { return datatype; } virtual String get_datatype_name() const override { return struct_name; } virtual int get_array_size() const override { return array_size; } + virtual Vector<Scalar> get_values() const override { + return values; + } ConstantNode() : Node(NODE_TYPE_CONSTANT) {} @@ -529,13 +535,14 @@ public: int line; //for completion int array_size; bool is_const; - ConstantNode::Value value; + Vector<Scalar> values; }; HashMap<StringName, Variable> variables; List<Node *> statements; bool single_statement = false; bool use_comma_between_statements = false; + bool use_op_eval = true; BlockNode() : Node(NODE_TYPE_BLOCK) {} @@ -657,7 +664,7 @@ public: DataType type = TYPE_VOID; DataPrecision precision = PRECISION_DEFAULT; int array_size = 0; - Vector<ConstantNode::Value> default_value; + Vector<Scalar> default_value; Scope scope = SCOPE_LOCAL; Hint hint = HINT_NONE; bool use_color = false; @@ -803,15 +810,16 @@ public: static bool is_token_operator_assign(TokenType p_type); static bool is_token_hint(TokenType p_type); - static bool convert_constant(ConstantNode *p_constant, DataType p_to_type, ConstantNode::Value *p_value = nullptr); + static bool convert_constant(ConstantNode *p_constant, DataType p_to_type, Scalar *p_value = nullptr); static DataType get_scalar_type(DataType p_type); static int get_cardinality(DataType p_type); static bool is_scalar_type(DataType p_type); static bool is_float_type(DataType p_type); static bool is_sampler_type(DataType p_type); - static Variant constant_value_to_variant(const Vector<ShaderLanguage::ConstantNode::Value> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint = ShaderLanguage::ShaderNode::Uniform::HINT_NONE); + static Variant constant_value_to_variant(const Vector<Scalar> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint = ShaderLanguage::ShaderNode::Uniform::HINT_NONE); static PropertyInfo uniform_to_property_info(const ShaderNode::Uniform &p_uniform); static uint32_t get_datatype_size(DataType p_type); + static uint32_t get_datatype_component_count(DataType p_type); static void get_keyword_list(List<String> *r_keywords); static bool is_control_flow_keyword(String p_keyword); @@ -1070,13 +1078,21 @@ private: IdentifierType last_type = IDENTIFIER_MAX; - bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr, ConstantNode::Value *r_constant_value = nullptr); + bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr, Vector<Scalar> *r_constant_values = nullptr); #ifdef DEBUG_ENABLED void _parse_used_identifier(const StringName &p_identifier, IdentifierType p_type, const StringName &p_function); #endif // DEBUG_ENABLED bool _is_operator_assign(Operator p_op) const; bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr); - bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr, int *r_ret_size = nullptr); + bool _validate_operator(const BlockNode *p_block, OperatorNode *p_op, DataType *r_ret_type = nullptr, int *r_ret_size = nullptr); + + Vector<Scalar> _get_node_values(const BlockNode *p_block, Node *p_node); + bool _eval_operator(const BlockNode *p_block, OperatorNode *p_op); + Scalar _eval_unary_scalar(const Scalar &p_a, Operator p_op, DataType p_ret_type); + Scalar _eval_scalar(const Scalar &p_a, const Scalar &p_b, Operator p_op, DataType p_ret_type, bool &r_is_valid); + Vector<Scalar> _eval_unary_vector(const Vector<Scalar> &p_va, DataType p_ret_type, Operator p_op); + Vector<Scalar> _eval_vector(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type, Operator p_op, bool &r_is_valid); + Vector<Scalar> _eval_vector_transform(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type); struct BuiltinEntry { const char *name; |