diff options
Diffstat (limited to 'scene')
56 files changed, 3382 insertions, 596 deletions
diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 9521667854..51b3e676f9 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -279,6 +279,13 @@ void RayCast2D::remove_exception(const CollisionObject2D *p_node) { void RayCast2D::clear_exceptions() { exclude.clear(); + + if (exclude_parent_body && is_inside_tree()) { + CollisionObject2D *parent = Object::cast_to<CollisionObject2D>(get_parent()); + if (parent) { + exclude.insert(parent->get_rid()); + } + } } void RayCast2D::set_collide_with_areas(bool p_enabled) { diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index b71c54dcf9..b251aa38ba 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -259,6 +259,13 @@ void RayCast3D::remove_exception(const CollisionObject3D *p_node) { void RayCast3D::clear_exceptions() { exclude.clear(); + + if (exclude_parent_body && is_inside_tree()) { + CollisionObject3D *parent = Object::cast_to<CollisionObject3D>(get_parent()); + if (parent) { + exclude.insert(parent->get_rid()); + } + } } void RayCast3D::set_collide_with_areas(bool p_enabled) { diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index a054f35d2e..66d1b97056 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -482,22 +482,22 @@ void XRController3D::_unbind_tracker() { void XRController3D::_button_pressed(const String &p_name) { // just pass it on... - emit_signal("button_pressed", p_name); + emit_signal(SNAME("button_pressed"), p_name); } void XRController3D::_button_released(const String &p_name) { // just pass it on... - emit_signal("button_released", p_name); + emit_signal(SNAME("button_released"), p_name); } void XRController3D::_input_value_changed(const String &p_name, float p_value) { // just pass it on... - emit_signal("input_value_changed", p_name, p_value); + emit_signal(SNAME("input_value_changed"), p_name, p_value); } void XRController3D::_input_axis_changed(const String &p_name, Vector2 p_value) { // just pass it on... - emit_signal("input_axis_changed", p_name, p_value); + emit_signal(SNAME("input_axis_changed"), p_name, p_value); } bool XRController3D::is_button_pressed(const StringName &p_name) const { diff --git a/scene/SCsub b/scene/SCsub index 92288211bb..a7b23af598 100644 --- a/scene/SCsub +++ b/scene/SCsub @@ -9,6 +9,7 @@ env.add_source_files(env.scene_sources, "*.cpp") # Chain load SCsubs SConscript("main/SCsub") +SConscript("multiplayer/SCsub") SConscript("gui/SCsub") if not env["disable_3d"]: SConscript("3d/SCsub") diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 7e21a43ab6..8cb8a78e8d 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -34,14 +34,6 @@ #include "core/string/string_builder.h" #include "core/string/ustring.h" -static bool _is_whitespace(char32_t c) { - return c == '\t' || c == ' '; -} - -static bool _is_char(char32_t c) { - return !is_symbol(c); -} - void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -607,9 +599,9 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { + } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { insert_text_at_caret(chr); } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); @@ -1001,7 +993,7 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } /* Make sure this is the last char, trailing whitespace or comments are okay. */ - if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) { + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) { should_indent = false; } } @@ -1817,7 +1809,7 @@ void CodeEdit::request_code_completion(bool p_force) { String line = get_line(get_caret_line()); int ofs = CLAMP(get_caret_column(), 0, line.length()); - if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { + if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || !is_symbol(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { emit_signal(SNAME("code_completion_requested")); } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) { emit_signal(SNAME("code_completion_requested")); @@ -1926,7 +1918,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (merge_text) { for (; caret_col < line.length(); caret_col++) { - if (!_is_char(line[caret_col])) { + if (is_symbol(line[caret_col])) { break; } } @@ -2562,7 +2554,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c region = E->value(); in_region = true; for (int i = E->key() - 2; i >= 0; i--) { - if (!_is_whitespace(line[i])) { + if (!is_whitespace(line[i])) { return -1; } } @@ -2581,7 +2573,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } for (int i = end_col; i < line.length(); i++) { - if (!_is_whitespace(line[i])) { + if (!is_whitespace(line[i])) { return -1; } } @@ -2797,11 +2789,11 @@ void CodeEdit::_filter_code_completion_candidates_impl() { while (ofs > 0 && line[ofs] == ' ') { ofs--; } - prev_is_word = _is_char(line[ofs]); + prev_is_word = !is_symbol(line[ofs]); /* Otherwise get current word and set cofs to the start. */ } else { int start_cofs = cofs; - while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || _is_char(line[cofs - 1]))) { + while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || !is_symbol(line[cofs - 1]))) { cofs--; } string_to_complete = line.substr(cofs, start_cofs - cofs); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index a985a9d031..94fa5d81d8 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -136,6 +136,9 @@ int MenuButton::get_item_count() const { void MenuButton::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + popup->set_layout_direction((Window::LayoutDirection)get_layout_direction()); + } break; case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible_in_tree()) { popup->hide(); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 9984ab240a..ad2434cd8b 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -92,7 +92,10 @@ void OptionButton::_notification(int p_what) { arrow->draw(ci, ofs, clr); } break; case NOTIFICATION_TRANSLATION_CHANGED: - case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + popup->set_layout_direction((Window::LayoutDirection)get_layout_direction()); + [[fallthrough]]; + } case NOTIFICATION_THEME_CHANGED: { if (has_theme_icon(SNAME("arrow"))) { if (is_layout_rtl()) { @@ -115,6 +118,11 @@ void OptionButton::_notification(int p_what) { bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0] == "popup") { + String property = components[2]; + if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { + return false; + } + bool valid; popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); @@ -133,6 +141,11 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0] == "popup") { + String property = components[2]; + if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { + return false; + } + bool valid; r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); return valid; @@ -148,14 +161,6 @@ void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"); - pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i)); - pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); p_list->push_back(pi); @@ -183,10 +188,13 @@ void OptionButton::pressed() { popup->set_size(Size2(size.width, 0)); // If not triggered by the mouse, start the popup with the checked item selected. - if (popup->get_item_count() > 0 && - ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) || - (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept")))) { - popup->set_current_index(current > -1 ? current : 0); + if (popup->get_item_count() > 0) { + if ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) || + (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept"))) { + popup->set_current_index(current > -1 ? current : 0); + } else { + popup->scroll_to_item(current > -1 ? current : 0); + } } popup->popup(); @@ -264,7 +272,20 @@ bool OptionButton::is_item_disabled(int p_idx) const { void OptionButton::set_item_count(int p_count) { ERR_FAIL_COND(p_count < 0); + + int count_old = get_item_count(); + if (p_count == count_old) { + return; + } + popup->set_item_count(p_count); + + if (p_count > count_old) { + for (int i = count_old; i < p_count; i++) { + popup->set_item_as_radio_checkable(i, true); + } + } + notify_property_list_changed(); } @@ -294,7 +315,7 @@ void OptionButton::_select(int p_which, bool p_emit) { current = NONE_SELECTED; set_text(""); - set_icon(NULL); + set_icon(nullptr); } else { ERR_FAIL_INDEX(p_which, popup->get_item_count()); diff --git a/scene/gui/panel.cpp b/scene/gui/panel.cpp index 86858fdc78..c88e4ae2f2 100644 --- a/scene/gui/panel.cpp +++ b/scene/gui/panel.cpp @@ -30,35 +30,14 @@ #include "panel.h" -#include "core/string/print_string.h" - void Panel::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<StyleBox> style = mode == MODE_BACKGROUND ? get_theme_stylebox(SNAME("panel")) : get_theme_stylebox(SNAME("panel_fg")); + Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); style->draw(ci, Rect2(Point2(), get_size())); } } -void Panel::set_mode(Mode p_mode) { - mode = p_mode; - update(); -} - -Panel::Mode Panel::get_mode() const { - return mode; -} - -void Panel::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &Panel::set_mode); - ClassDB::bind_method(D_METHOD("get_mode"), &Panel::get_mode); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Background,Foreground"), "set_mode", "get_mode"); - - BIND_ENUM_CONSTANT(MODE_BACKGROUND); - BIND_ENUM_CONSTANT(MODE_FOREGROUND); -} - Panel::Panel() { // Has visible stylebox, so stop by default. set_mouse_filter(MOUSE_FILTER_STOP); diff --git a/scene/gui/panel.h b/scene/gui/panel.h index 37f14c250c..5d2e912680 100644 --- a/scene/gui/panel.h +++ b/scene/gui/panel.h @@ -36,26 +36,11 @@ class Panel : public Control { GDCLASS(Panel, Control); -public: - enum Mode { - MODE_BACKGROUND, - MODE_FOREGROUND, - }; - -private: - Mode mode = MODE_BACKGROUND; - protected: void _notification(int p_what); - static void _bind_methods(); public: - void set_mode(Mode p_mode); - Mode get_mode() const; - Panel(); }; -VARIANT_ENUM_CAST(Panel::Mode) - #endif // PANEL_H diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 812339dc19..6e0f1c5198 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -67,7 +67,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.width += items[i].h_ofs; - if (items[i].checkable_type) { + if (items[i].checkable_type && !items[i].separator) { has_check = true; } @@ -108,10 +108,9 @@ Size2 PopupMenu::_get_contents_minimum_size() const { int PopupMenu::_get_item_height(int p_item) const { ERR_FAIL_INDEX_V(p_item, items.size(), 0); - ERR_FAIL_COND_V(p_item < 0, 0); int icon_height = items[p_item].get_icon_size().height; - if (items[p_item].checkable_type) { + if (items[p_item].checkable_type && !items[p_item].separator) { icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height())); } @@ -141,23 +140,6 @@ int PopupMenu::_get_items_total_height() const { return items_total_height - vsep; } -void PopupMenu::_scroll_to_item(int p_item) { - ERR_FAIL_INDEX(p_item, items.size()); - ERR_FAIL_COND(p_item < 0); - - // Scroll item into view (upwards) - if (items[p_item]._ofs_cache < -control->get_position().y) { - int amnt_over = items[p_item]._ofs_cache + control->get_position().y; - scroll_container->set_v_scroll(scroll_container->get_v_scroll() + amnt_over); - } - - // Scroll item into view (downwards) - if (items[p_item]._ofs_cache + items[p_item]._height_cache > -control->get_position().y + scroll_container->get_size().height) { - int amnt_over = items[p_item]._ofs_cache + items[p_item]._height_cache + control->get_position().y - scroll_container->get_size().height; - scroll_container->set_v_scroll(scroll_container->get_v_scroll() + amnt_over); - } -} - int PopupMenu::_get_mouse_over(const Point2 &p_over) const { if (p_over.x < 0 || p_over.x >= get_size().width) { return -1; @@ -203,15 +185,17 @@ void PopupMenu::_activate_submenu(int p_over) { float scroll_offset = control->get_position().y; - Point2 submenu_pos; + submenu_popup->set_as_minsize(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); + + Point2 submenu_pos; if (control->is_layout_rtl()) { submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset); } else { submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset); } - // Fix pos if going outside parent rect + // Fix pos if going outside parent rect. if (submenu_pos.x < get_parent_rect().position.x) { submenu_pos.x = this_pos.x + submenu_size.width; } @@ -222,7 +206,6 @@ void PopupMenu::_activate_submenu(int p_over) { submenu_popup->set_close_on_parent_focus(false); submenu_popup->set_position(submenu_pos); - submenu_popup->set_as_minsize(); // Shrink the popup size to its contents. PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup); if (!submenu_pum) { @@ -275,7 +258,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); match_found = true; @@ -289,7 +272,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); break; @@ -307,7 +290,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); match_found = true; @@ -321,7 +304,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); break; @@ -471,7 +454,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (items[i].text.findn(search_string) == 0) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); break; @@ -495,7 +478,7 @@ void PopupMenu::_draw_items() { bool rtl = control->is_layout_rtl(); Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover")); - // In Item::checkable_type enum order (less the non-checkable member) + // In Item::checkable_type enum order (less the non-checkable member). Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")) }; Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")) }; Ref<Texture2D> submenu; @@ -524,6 +507,10 @@ void PopupMenu::_draw_items() { float icon_ofs = 0.0; bool has_check = false; for (int i = 0; i < items.size(); i++) { + if (items[i].separator) { + continue; + } + icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs); if (items[i].checkable_type) { @@ -567,29 +554,33 @@ void PopupMenu::_draw_items() { if (items[i].separator) { int sep_h = separator->get_center_size().height + separator->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - if (!text.is_empty()) { - int text_size = items[i].text_buf->get_size().width; - int text_center = display_width / 2; - int text_left = text_center - text_size / 2; - int text_right = text_center + text_size / 2; - if (text_left > item_ofs.x) { - labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, text_left - item_ofs.x), sep_h))); + if (!text.is_empty() || !items[i].icon.is_null()) { + int content_size = items[i].text_buf->get_size().width; + if (!items[i].icon.is_null()) { + content_size += icon_size.width + hseparation; + } + + int content_center = display_width / 2; + int content_left = content_center - content_size / 2; + int content_right = content_center + content_size / 2; + if (content_left > item_ofs.x) { + labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h))); } - if (text_right < display_width) { - labeled_separator_right->draw(ci, Rect2(Point2(text_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - text_right), sep_h))); + if (content_right < display_width) { + labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); } } else { separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); } } - Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1); // For non-separator items, add some padding for the content. item_ofs.x += item_start_padding; // Checkboxes - if (items[i].checkable_type) { + if (items[i].checkable_type && !items[i].separator) { Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr(); if (rtl) { icon->draw(ci, Size2(control->get_size().width - item_ofs.x - icon->get_width(), item_ofs.y) + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color); @@ -598,16 +589,28 @@ void PopupMenu::_draw_items() { } } + int separator_ofs = (display_width - items[i].text_buf->get_size().width) / 2; + // Icon if (!items[i].icon.is_null()) { - if (rtl) { - items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + if (items[i].separator) { + separator_ofs -= (icon_size.width + hseparation) / 2; + + if (rtl) { + items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } else { + items[i].icon->draw(ci, item_ofs + Size2(separator_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } } else { - items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + if (rtl) { + items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } else { + items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } } } - // Submenu arrow on right hand side + // Submenu arrow on right hand side. if (!items[i].submenu.is_empty()) { if (rtl) { submenu->draw(ci, Point2(scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); @@ -621,8 +624,11 @@ void PopupMenu::_draw_items() { int outline_size = get_theme_constant(SNAME("outline_size")); if (items[i].separator) { if (!text.is_empty()) { - int center = (display_width - items[i].text_buf->get_size().width) / 2; - Vector2 text_pos = Point2(center, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); + Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); + if (!rtl && !items[i].icon.is_null()) { + text_pos.x += icon_size.width + hseparation; + } + if (outline_size > 0 && font_outline_color.a > 0) { items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); } @@ -659,7 +665,7 @@ void PopupMenu::_draw_items() { items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color); } - // Cache the item vertical offset from the first item and the height + // Cache the item vertical offset from the first item and the height. items.write[i]._ofs_cache = ofs.y; items.write[i]._height_cache = h; @@ -724,13 +730,32 @@ void PopupMenu::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent()); if (pm) { - // Inherit submenu's popup delay time from parent menu + // Inherit submenu's popup delay time from parent menu. float pm_delay = pm->get_submenu_popup_delay(); set_submenu_popup_delay(pm_delay); } } break; case NOTIFICATION_THEME_CHANGED: - case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + // Pass the layout direction to all submenus. + for (int i = 0; i < items.size(); i++) { + if (items[i].submenu.is_empty()) { + continue; + } + + Node *n = get_node(items[i].submenu); + if (!n) { + continue; + } + + PopupMenu *pm = Object::cast_to<PopupMenu>(n); + if (pm) { + pm->set_layout_direction(get_layout_direction()); + } + } + + [[fallthrough]]; + } case NOTIFICATION_TRANSLATION_CHANGED: { for (int i = 0; i < items.size(); i++) { items.write[i].xl_text = atr(items[i].text); @@ -1281,7 +1306,7 @@ bool PopupMenu::is_item_shortcut_disabled(int p_idx) const { void PopupMenu::set_current_index(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); mouse_over = p_idx; - _scroll_to_item(mouse_over); + scroll_to_item(mouse_over); control->update(); } @@ -1309,6 +1334,20 @@ int PopupMenu::get_item_count() const { return items.size(); } +void PopupMenu::scroll_to_item(int p_item) { + ERR_FAIL_INDEX(p_item, items.size()); + + // Scroll item into view (upwards). + if (items[p_item]._ofs_cache - scroll_container->get_v_scroll() < -control->get_position().y) { + scroll_container->set_v_scroll(items[p_item]._ofs_cache + control->get_position().y); + } + + // Scroll item into view (downwards). + if (items[p_item]._ofs_cache + items[p_item]._height_cache - scroll_container->get_v_scroll() > -control->get_position().y + scroll_container->get_size().height) { + scroll_container->set_v_scroll(items[p_item]._ofs_cache + items[p_item]._height_cache + control->get_position().y); + } +} + bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { Key code = Key::NONE; Ref<InputEventKey> k = p_event; @@ -1582,7 +1621,7 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { } else if (property == "id") { set_item_id(item_index, p_value); return true; - } else if (components[1] == "disabled") { + } else if (property == "disabled") { set_item_disabled(item_index, p_value); return true; } else if (property == "separator") { @@ -1655,7 +1694,7 @@ bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const { } else if (property == "id") { r_ret = get_item_id(item_index); return true; - } else if (components[1] == "disabled") { + } else if (property == "disabled") { r_ret = is_item_disabled(item_index); return true; } else if (property == "separator") { @@ -1761,6 +1800,8 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count); + ClassDB::bind_method(D_METHOD("scroll_to_item", "index"), &PopupMenu::scroll_to_item); + ClassDB::bind_method(D_METHOD("remove_item", "index"), &PopupMenu::remove_item); ClassDB::bind_method(D_METHOD("add_separator", "label", "id"), &PopupMenu::add_separator, DEFVAL(String()), DEFVAL(-1)); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 7c2212d82d..5ce55209d4 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -103,7 +103,6 @@ class PopupMenu : public Popup { int _get_item_height(int p_item) const; int _get_items_total_height() const; - void _scroll_to_item(int p_item); void _shape_item(int p_item); @@ -218,6 +217,8 @@ public: void set_item_count(int p_count); int get_item_count() const; + void scroll_to_item(int p_item); + bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false); void activate_item(int p_item); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index c3fc08731e..818431a6a0 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -704,7 +704,7 @@ void TabContainer::add_child_notify(Node *p_child) { } _refresh_texts(); - call_deferred("_repaint"); + call_deferred(SNAME("_repaint")); update(); bool first = (_get_tabs().size() == 1); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 0ee4a6af4e..bb259843b8 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -43,18 +43,6 @@ #include "scene/main/window.h" -static bool _is_text_char(char32_t c) { - return !is_symbol(c); -} - -static bool _is_whitespace(char32_t c) { - return c == '\t' || c == ' '; -} - -static bool _is_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - /////////////////////////////////////////////////////////////////////////////// /// TEXT /// /////////////////////////////////////////////////////////////////////////////// @@ -820,8 +808,8 @@ void TextEdit::_notification(int p_what) { int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; bool out_of_bounds = (xpos >= xmargin_end + minimap_width); - bool is_whitespace = _is_whitespace(str[j]); - if (!is_whitespace) { + bool whitespace = is_whitespace(str[j]); + if (!whitespace) { characters++; if (j < str.length() - 1 && color == previous_color && !out_of_bounds) { @@ -843,7 +831,7 @@ void TextEdit::_notification(int p_what) { if (characters > 0) { previous_color.a *= 0.6; // take one for zero indexing, and if we hit whitespace / the end of a word. - int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1; + int chars = MAX(0, (j - (characters - 1)) - (whitespace ? 1 : 0)) + 1; int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs; if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); @@ -1144,7 +1132,7 @@ void TextEdit::_notification(int p_what) { } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word - if (_is_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '.') { + if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); while (highlighted_word_col != -1) { Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); @@ -3037,7 +3025,7 @@ int TextEdit::get_first_non_whitespace_column(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); int col = 0; - while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) { + while (col < text[p_line].length() && is_whitespace(text[p_line][col])) { col++; } return col; @@ -3622,9 +3610,9 @@ Point2i TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_fro if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) { // Validate for whole words. - if (pos > 0 && _is_text_char(text_line[pos - 1])) { + if (pos > 0 && !is_symbol(text_line[pos - 1])) { is_match = false; - } else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) { + } else if (pos + p_key.length() < text_line.length() && !is_symbol(text_line[pos + p_key.length()])) { is_match = false; } } @@ -5779,9 +5767,9 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) { p_from_column = col; - if (col > 0 && _is_text_char(p_search[col - 1])) { + if (col > 0 && !is_symbol(p_search[col - 1])) { col = -1; - } else if ((col + p_key.length()) < p_search.length() && _is_text_char(p_search[col + p_key.length()])) { + } else if ((col + p_key.length()) < p_search.length() && !is_symbol(p_search[col + p_key.length()])) { col = -1; } } diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 89a17ae854..26acfaaa70 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -37,7 +37,7 @@ Size2 TextureButton::get_minimum_size() const { Size2 rscale = Control::get_minimum_size(); - if (!expand) { + if (!ignore_texture_size) { if (normal.is_null()) { if (pressed.is_null()) { if (hover.is_null()) { @@ -182,50 +182,48 @@ void TextureButton::_notification(int p_what) { size = texdraw->get_size(); _texture_region = Rect2(Point2(), texdraw->get_size()); _tile = false; - if (expand) { - switch (stretch_mode) { - case STRETCH_KEEP: - size = texdraw->get_size(); - break; - case STRETCH_SCALE: - size = get_size(); - break; - case STRETCH_TILE: - size = get_size(); - _tile = true; - break; - case STRETCH_KEEP_CENTERED: - ofs = (get_size() - texdraw->get_size()) / 2; - size = texdraw->get_size(); - break; - case STRETCH_KEEP_ASPECT_CENTERED: - case STRETCH_KEEP_ASPECT: { - Size2 _size = get_size(); - float tex_width = texdraw->get_width() * _size.height / texdraw->get_height(); - float tex_height = _size.height; - - if (tex_width > _size.width) { - tex_width = _size.width; - tex_height = texdraw->get_height() * tex_width / texdraw->get_width(); - } + switch (stretch_mode) { + case STRETCH_KEEP: + size = texdraw->get_size(); + break; + case STRETCH_SCALE: + size = get_size(); + break; + case STRETCH_TILE: + size = get_size(); + _tile = true; + break; + case STRETCH_KEEP_CENTERED: + ofs = (get_size() - texdraw->get_size()) / 2; + size = texdraw->get_size(); + break; + case STRETCH_KEEP_ASPECT_CENTERED: + case STRETCH_KEEP_ASPECT: { + Size2 _size = get_size(); + float tex_width = texdraw->get_width() * _size.height / texdraw->get_height(); + float tex_height = _size.height; + + if (tex_width > _size.width) { + tex_width = _size.width; + tex_height = texdraw->get_height() * tex_width / texdraw->get_width(); + } - if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { - ofs.x = (_size.width - tex_width) / 2; - ofs.y = (_size.height - tex_height) / 2; - } - size.width = tex_width; - size.height = tex_height; - } break; - case STRETCH_KEEP_ASPECT_COVERED: { - size = get_size(); - Size2 tex_size = texdraw->get_size(); - Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); - float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; - Size2 scaled_tex_size = tex_size * scale; - Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f; - _texture_region = Rect2(ofs2, size / scale); - } break; - } + if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { + ofs.x = (_size.width - tex_width) / 2; + ofs.y = (_size.height - tex_height) / 2; + } + size.width = tex_width; + size.height = tex_height; + } break; + case STRETCH_KEEP_ASPECT_COVERED: { + size = get_size(); + Size2 tex_size = texdraw->get_size(); + Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); + float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; + Size2 scaled_tex_size = tex_size * scale; + Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f; + _texture_region = Rect2(ofs2, size / scale); + } break; } _position_rect = Rect2(ofs, size); @@ -258,7 +256,7 @@ void TextureButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disabled_texture", "texture"), &TextureButton::set_disabled_texture); ClassDB::bind_method(D_METHOD("set_focused_texture", "texture"), &TextureButton::set_focused_texture); ClassDB::bind_method(D_METHOD("set_click_mask", "mask"), &TextureButton::set_click_mask); - ClassDB::bind_method(D_METHOD("set_expand", "expand"), &TextureButton::set_expand); + ClassDB::bind_method(D_METHOD("set_ignore_texture_size", "ignore"), &TextureButton::set_ignore_texture_size); ClassDB::bind_method(D_METHOD("set_stretch_mode", "mode"), &TextureButton::set_stretch_mode); ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureButton::set_flip_h); ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureButton::is_flipped_h); @@ -271,7 +269,7 @@ void TextureButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_disabled_texture"), &TextureButton::get_disabled_texture); ClassDB::bind_method(D_METHOD("get_focused_texture"), &TextureButton::get_focused_texture); ClassDB::bind_method(D_METHOD("get_click_mask"), &TextureButton::get_click_mask); - ClassDB::bind_method(D_METHOD("get_expand"), &TextureButton::get_expand); + ClassDB::bind_method(D_METHOD("get_ignore_texture_size"), &TextureButton::get_ignore_texture_size); ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureButton::get_stretch_mode); ADD_GROUP("Textures", "texture_"); @@ -281,7 +279,7 @@ void TextureButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_disabled", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_disabled_texture", "get_disabled_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_focused", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_focused_texture", "get_focused_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_click_mask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_click_mask", "get_click_mask"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_expand", "get_expand"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_texture_size", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_ignore_texture_size", "get_ignore_texture_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_v", "is_flipped_v"); @@ -352,12 +350,12 @@ void TextureButton::set_focused_texture(const Ref<Texture2D> &p_focused) { focused = p_focused; }; -bool TextureButton::get_expand() const { - return expand; +bool TextureButton::get_ignore_texture_size() const { + return ignore_texture_size; } -void TextureButton::set_expand(bool p_expand) { - expand = p_expand; +void TextureButton::set_ignore_texture_size(bool p_ignore) { + ignore_texture_size = p_ignore; update_minimum_size(); update(); } diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h index 1428a79a1d..5762949acd 100644 --- a/scene/gui/texture_button.h +++ b/scene/gui/texture_button.h @@ -54,8 +54,8 @@ private: Ref<Texture2D> disabled; Ref<Texture2D> focused; Ref<BitMap> click_mask; - bool expand = false; - StretchMode stretch_mode = STRETCH_SCALE; + bool ignore_texture_size = false; + StretchMode stretch_mode = STRETCH_KEEP; Rect2 _texture_region; Rect2 _position_rect; @@ -85,8 +85,8 @@ public: Ref<Texture2D> get_focused_texture() const; Ref<BitMap> get_click_mask() const; - bool get_expand() const; - void set_expand(bool p_expand); + bool get_ignore_texture_size() const; + void set_ignore_texture_size(bool p_ignore); void set_stretch_mode(StretchMode p_stretch_mode); StretchMode get_stretch_mode() const; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1b32884880..9a6c87276f 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -202,7 +202,7 @@ void TreeItem::propagate_check(int p_column, bool p_emit_signal) { bool ch = cells[p_column].checked; if (p_emit_signal) { - tree->emit_signal("check_propagated_to_item", this, p_column); + tree->emit_signal(SNAME("check_propagated_to_item"), this, p_column); } _propagate_check_through_children(p_column, ch, p_emit_signal); _propagate_check_through_parents(p_column, p_emit_signal); @@ -213,7 +213,7 @@ void TreeItem::_propagate_check_through_children(int p_column, bool p_checked, b while (current) { current->set_checked(p_column, p_checked); if (p_emit_signal) { - current->tree->emit_signal("check_propagated_to_item", current, p_column); + current->tree->emit_signal(SNAME("check_propagated_to_item"), current, p_column); } current->_propagate_check_through_children(p_column, p_checked, p_emit_signal); current = current->get_next(); @@ -252,7 +252,7 @@ void TreeItem::_propagate_check_through_parents(int p_column, bool p_emit_signal } if (p_emit_signal) { - current->tree->emit_signal("check_propagated_to_item", current, p_column); + current->tree->emit_signal(SNAME("check_propagated_to_item"), current, p_column); } current->_propagate_check_through_parents(p_column, p_emit_signal); } @@ -2490,7 +2490,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int /* process selection */ if (p_double_click && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check - + // Emits the "item_activated" signal. propagate_mouse_activated = true; incr_search.clear(); @@ -4409,21 +4409,29 @@ Point2 Tree::get_scroll() const { return ofs; } -void Tree::scroll_to_item(TreeItem *p_item) { +void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { if (!is_visible_in_tree()) { - // hack to work around crash in get_item_rect() if Tree is not in tree. - return; + return; // Hack to work around crash in get_item_rect() if Tree is not in tree. } - // make sure the scrollbar min and max are up to date with latest changes. update_scrollbars(); - const Rect2 r = get_item_rect(p_item); + const real_t tree_height = get_size().y; + const Rect2 item_rect = get_item_rect(p_item); + const real_t item_y = item_rect.position.y; + const real_t item_height = item_rect.size.y + cache.vseparation; - if (r.position.y <= v_scroll->get_value()) { - v_scroll->set_value(r.position.y); - } else if (r.position.y + r.size.y + 2 * cache.vseparation > v_scroll->get_value() + get_size().y) { - v_scroll->set_value(r.position.y + r.size.y + 2 * cache.vseparation - get_size().y); + if (p_center_on_item) { + v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); + } else { + if (item_y < v_scroll->get_value()) { + v_scroll->set_value(item_y); + } else { + const real_t new_position = item_y + item_height - tree_height; + if (new_position > v_scroll->get_value()) { + v_scroll->set_value(new_position); + } + } } } @@ -4868,7 +4876,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_column_title_language", "column"), &Tree::get_column_title_language); ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll); - ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::scroll_to_item); + ClassDB::bind_method(D_METHOD("scroll_to_item", "item", "center_on_item"), &Tree::scroll_to_item, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_h_scroll_enabled", "h_scroll"), &Tree::set_h_scroll_enabled); ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &Tree::is_h_scroll_enabled); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index c24763a0e4..255a4f0576 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -682,7 +682,7 @@ public: TreeItem *get_item_with_text(const String &p_find) const; Point2 get_scroll() const; - void scroll_to_item(TreeItem *p_item); + void scroll_to_item(TreeItem *p_item, bool p_center_on_item = false); void set_h_scroll_enabled(bool p_enable); bool is_h_scroll_enabled() const; void set_v_scroll_enabled(bool p_enable); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index a0916c6291..b794bbbc57 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -56,34 +56,19 @@ Transform2D CanvasItem::_edit_get_transform() const { #endif bool CanvasItem::is_visible_in_tree() const { - if (!is_inside_tree()) { - return false; - } - - const CanvasItem *p = this; - - while (p) { - if (!p->visible) { - return false; - } - if (p->window && !p->window->is_visible()) { - return false; - } - p = p->get_parent_item(); - } - - return true; + return visible && visible_in_tree; } -void CanvasItem::_propagate_visibility_changed(bool p_visible) { +void CanvasItem::_propagate_visibility_changed(bool p_visible, bool p_was_visible) { if (p_visible && first_draw) { //avoid propagating it twice first_draw = false; } + visible_in_tree = p_visible; notification(NOTIFICATION_VISIBILITY_CHANGED); - if (p_visible) { - update(); //todo optimize - } else { + if (visible && p_visible) { + update(); + } else if (!p_visible && (visible || p_was_visible)) { emit_signal(SceneStringNames::get_singleton()->hidden); } _block(); @@ -111,7 +96,7 @@ void CanvasItem::set_visible(bool p_visible) { return; } - _propagate_visibility_changed(p_visible); + _propagate_visibility_changed(p_visible, !p_visible); } void CanvasItem::show() { @@ -139,7 +124,7 @@ void CanvasItem::_update_callback() { RenderingServer::get_singleton()->canvas_item_clear(get_canvas_item()); //todo updating = true - only allow drawing here - if (is_visible_in_tree()) { //todo optimize this!! + if (is_visible_in_tree()) { if (first_draw) { notification(NOTIFICATION_VISIBILITY_CHANGED); first_draw = false; @@ -273,32 +258,44 @@ void CanvasItem::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { ERR_FAIL_COND(!is_inside_tree()); first_draw = true; + Node *parent = get_parent(); if (parent) { CanvasItem *ci = Object::cast_to<CanvasItem>(parent); + if (ci) { + visible_in_tree = ci->is_visible_in_tree(); C = ci->children_items.push_back(this); - } - if (!ci) { - //look for a window - Viewport *viewport = nullptr; - - while (parent) { - viewport = Object::cast_to<Viewport>(parent); - if (viewport) { - break; + } else { + CanvasLayer *cl = Object::cast_to<CanvasLayer>(parent); + + if (cl) { + visible_in_tree = cl->is_visible(); + } else { + // Look for a window. + Viewport *viewport = nullptr; + + while (parent) { + viewport = Object::cast_to<Viewport>(parent); + if (viewport) { + break; + } + parent = parent->get_parent(); } - parent = parent->get_parent(); - } - ERR_FAIL_COND(!viewport); + ERR_FAIL_COND(!viewport); - window = Object::cast_to<Window>(viewport); - if (window) { - window->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed)); + window = Object::cast_to<Window>(viewport); + if (window) { + window->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed)); + visible_in_tree = window->is_visible(); + } else { + visible_in_tree = true; + } } } } + _enter_canvas(); _update_texture_filter_changed(false); @@ -335,6 +332,7 @@ void CanvasItem::_notification(int p_what) { window->disconnect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed)); } global_invalid = true; + visible_in_tree = false; } break; case NOTIFICATION_DRAW: case NOTIFICATION_TRANSFORM_CHANGED: { diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 3d49d89746..a368a10ad0 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -46,6 +46,8 @@ class World2D; class CanvasItem : public Node { GDCLASS(CanvasItem, Node); + friend class CanvasLayer; + public: enum TextureFilter { TEXTURE_FILTER_PARENT_NODE, @@ -85,6 +87,7 @@ private: Window *window = nullptr; bool first_draw = false; bool visible = true; + bool visible_in_tree = false; bool clip_children = false; bool pending_update = false; bool top_level = false; @@ -107,7 +110,7 @@ private: void _top_level_raise_self(); - void _propagate_visibility_changed(bool p_visible); + void _propagate_visibility_changed(bool p_visible, bool p_was_visible = false); void _update_callback(); diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index 282ab6b497..3f3e72357b 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "canvas_layer.h" +#include "canvas_item.h" #include "viewport.h" void CanvasLayer::set_layer(int p_xform) { @@ -42,6 +43,32 @@ int CanvasLayer::get_layer() const { return layer; } +void CanvasLayer::set_visible(bool p_visible) { + if (p_visible == visible) { + return; + } + + visible = p_visible; + emit_signal(SNAME("visibility_changed")); + + for (int i = 0; i < get_child_count(); i++) { + CanvasItem *c = Object::cast_to<CanvasItem>(get_child(i)); + if (c) { + RenderingServer::get_singleton()->canvas_item_set_visible(c->get_canvas_item(), p_visible && c->is_visible()); + + if (c->is_visible()) { + c->_propagate_visibility_changed(p_visible); + } else { + c->notification(CanvasItem::NOTIFICATION_VISIBILITY_CHANGED); + } + } + } +} + +bool CanvasLayer::is_visible() const { + return visible; +} + void CanvasLayer::set_transform(const Transform2D &p_xform) { transform = p_xform; locrotscale_dirty = true; @@ -264,6 +291,9 @@ void CanvasLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer", "layer"), &CanvasLayer::set_layer); ClassDB::bind_method(D_METHOD("get_layer"), &CanvasLayer::get_layer); + ClassDB::bind_method(D_METHOD("set_visible", "visible"), &CanvasLayer::set_visible); + ClassDB::bind_method(D_METHOD("is_visible"), &CanvasLayer::is_visible); + ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CanvasLayer::set_transform); ClassDB::bind_method(D_METHOD("get_transform"), &CanvasLayer::get_transform); @@ -289,6 +319,7 @@ void CanvasLayer::_bind_methods() { ADD_GROUP("Layer", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "layer", PROPERTY_HINT_RANGE, "-128,128,1"), "set_layer", "get_layer"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); @@ -299,6 +330,8 @@ void CanvasLayer::_bind_methods() { ADD_GROUP("Follow Viewport", "follow_viewport"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_viewport_enable"), "set_follow_viewport", "is_following_viewport"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "follow_viewport_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001,or_greater,or_lesser"), "set_follow_viewport_scale", "get_follow_viewport_scale"); + + ADD_SIGNAL(MethodInfo("visibility_changed")); } CanvasLayer::CanvasLayer() { diff --git a/scene/main/canvas_layer.h b/scene/main/canvas_layer.h index 93a0152787..b7bd793440 100644 --- a/scene/main/canvas_layer.h +++ b/scene/main/canvas_layer.h @@ -52,6 +52,7 @@ class CanvasLayer : public Node { Viewport *vp = nullptr; int sort_index = 0; + bool visible = true; bool follow_viewport = false; float follow_viewport_scale = 1.0; @@ -69,6 +70,9 @@ public: void set_layer(int p_xform); int get_layer() const; + void set_visible(bool p_visible); + bool is_visible() const; + void set_transform(const Transform2D &p_xform); Transform2D get_transform() const; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index a2415442f8..6b9d8ab211 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -32,6 +32,7 @@ #include "core/core_string_names.h" #include "core/io/resource_loader.h" +#include "core/multiplayer/multiplayer_api.h" #include "core/object/message_queue.h" #include "core/string/print_string.h" #include "instance_placeholder.h" @@ -110,9 +111,6 @@ void Node::_notification(int p_notification) { memdelete(data.path_cache); data.path_cache = nullptr; } - if (data.scene_file_path.length()) { - get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, false); - } } break; case NOTIFICATION_PATH_RENAMED: { if (data.path_cache) { @@ -141,12 +139,6 @@ void Node::_notification(int p_notification) { } GDVIRTUAL_CALL(_ready); - - if (data.scene_file_path.length()) { - ERR_FAIL_COND(!is_inside_tree()); - get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, true); - } - } break; case NOTIFICATION_POSTINITIALIZE: { data.in_constructor = false; @@ -211,6 +203,12 @@ void Node::_propagate_enter_tree() { data.tree->node_added(this); + if (data.parent) { + Variant c = this; + const Variant *cptr = &c; + data.parent->emit_signal(SNAME("child_entered_tree"), &cptr, 1); + } + data.blocked++; //block while adding children @@ -281,6 +279,12 @@ void Node::_propagate_exit_tree() { data.tree->node_removed(this); } + if (data.parent) { + Variant c = this; + const Variant *cptr = &c; + data.parent->emit_signal(SNAME("child_exited_tree"), &cptr, 1); + } + // exit groups for (KeyValue<StringName, GroupData> &E : data.grouped) { @@ -1049,7 +1053,7 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co String nums; for (int i = name_string.length() - 1; i >= 0; i--) { char32_t n = name_string[i]; - if (n >= '0' && n <= '9') { + if (is_digit(n)) { nums = String::chr(name_string[i]) + nums; } else { break; @@ -2865,6 +2869,8 @@ void Node::_bind_methods() { ADD_SIGNAL(MethodInfo("tree_entered")); ADD_SIGNAL(MethodInfo("tree_exiting")); ADD_SIGNAL(MethodInfo("tree_exited")); + ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); + ADD_SIGNAL(MethodInfo("child_exited_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path"); diff --git a/scene/main/node.h b/scene/main/node.h index a1fc672a15..0ac10f4381 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -212,7 +212,6 @@ protected: static String _get_name_num_separator(); friend class SceneState; - friend class MultiplayerReplicator; void _add_child_nocheck(Node *p_child, const StringName &p_name); void _set_owner_nocheck(Node *p_owner); @@ -467,7 +466,7 @@ public: bool is_displayed_folded() const; /* NETWORK */ - void set_multiplayer_authority(int p_peer_id, bool p_recursive = true); + virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true); int get_multiplayer_authority() const; bool is_multiplayer_authority() const; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 45f04b28b9..69d781cbfc 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -36,6 +36,7 @@ #include "core/io/dir_access.h" #include "core/io/marshalls.h" #include "core/io/resource_loader.h" +#include "core/multiplayer/multiplayer_api.h" #include "core/object/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" @@ -1164,7 +1165,7 @@ void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { ERR_FAIL_COND(!p_multiplayer.is_valid()); multiplayer = p_multiplayer; - multiplayer->set_root_node(root); + multiplayer->set_root_path("/" + root->get_name()); } void SceneTree::_bind_methods() { diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 1dff1dab4f..a5cd52b4ca 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -31,7 +31,6 @@ #ifndef SCENE_TREE_H #define SCENE_TREE_H -#include "core/multiplayer/multiplayer_api.h" #include "core/os/main_loop.h" #include "core/os/thread_safe.h" #include "core/templates/self_list.h" @@ -46,6 +45,7 @@ class Node; class Window; class Material; class Mesh; +class MultiplayerAPI; class SceneDebugger; class Tween; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 09880ad6cf..522997cdf5 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1231,7 +1231,7 @@ void Viewport::_gui_show_tooltip() { base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - panel->set_transient(false); + panel->set_transient(true); panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index fbc0bc5301..f2ebe50fa3 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1614,6 +1614,7 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(MODE_MINIMIZED); BIND_ENUM_CONSTANT(MODE_MAXIMIZED); BIND_ENUM_CONSTANT(MODE_FULLSCREEN); + BIND_ENUM_CONSTANT(MODE_EXCLUSIVE_FULLSCREEN); BIND_ENUM_CONSTANT(FLAG_RESIZE_DISABLED); BIND_ENUM_CONSTANT(FLAG_BORDERLESS); diff --git a/scene/main/window.h b/scene/main/window.h index 2dd1dd6601..f37689f905 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -46,6 +46,7 @@ public: MODE_MINIMIZED = DisplayServer::WINDOW_MODE_MINIMIZED, MODE_MAXIMIZED = DisplayServer::WINDOW_MODE_MAXIMIZED, MODE_FULLSCREEN = DisplayServer::WINDOW_MODE_FULLSCREEN, + MODE_EXCLUSIVE_FULLSCREEN = DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN, }; enum Flags { diff --git a/scene/multiplayer/SCsub b/scene/multiplayer/SCsub new file mode 100644 index 0000000000..fc61250247 --- /dev/null +++ b/scene/multiplayer/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.scene_sources, "*.cpp") diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp new file mode 100644 index 0000000000..4f2a9d9e83 --- /dev/null +++ b/scene/multiplayer/multiplayer_spawner.cpp @@ -0,0 +1,227 @@ +/*************************************************************************/ +/* multiplayer_spawner.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_spawner.h" + +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/window.h" +#include "scene/scene_string_names.h" + +void MultiplayerSpawner::_bind_methods() { + ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant())); + + ClassDB::bind_method(D_METHOD("get_spawnable_scenes"), &MultiplayerSpawner::get_spawnable_scenes); + ClassDB::bind_method(D_METHOD("set_spawnable_scenes", "scenes"), &MultiplayerSpawner::set_spawnable_scenes); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "replication", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_spawnable_scenes", "get_spawnable_scenes"); + + ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path); + ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "spawn_path", PROPERTY_HINT_NONE, ""), "set_spawn_path", "get_spawn_path"); + + ClassDB::bind_method(D_METHOD("get_spawn_limit"), &MultiplayerSpawner::get_spawn_limit); + ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); + + ClassDB::bind_method(D_METHOD("set_auto_spawning", "enabled"), &MultiplayerSpawner::set_auto_spawning); + ClassDB::bind_method(D_METHOD("is_auto_spawning"), &MultiplayerSpawner::is_auto_spawning); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_spawn"), "set_auto_spawning", "is_auto_spawning"); + + GDVIRTUAL_BIND(_spawn_custom, "data"); + + ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); +} + +void MultiplayerSpawner::_update_spawn_node() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + if (spawn_node.is_valid()) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)); + if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) { + node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); + } + } + Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path); + if (node) { + spawn_node = node->get_instance_id(); + if (auto_spawn) { + node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); + } + } else { + spawn_node = ObjectID(); + } +} + +void MultiplayerSpawner::_notification(int p_what) { + if (p_what == NOTIFICATION_POST_ENTER_TREE) { + _update_spawn_node(); + } else if (p_what == NOTIFICATION_EXIT_TREE) { + _update_spawn_node(); + const ObjectID *oid = nullptr; + while ((oid = tracked_nodes.next(oid))) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid)); + ERR_CONTINUE(!node); + node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit)); + // This is unlikely, but might still crash the engine. + if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) { + node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready)); + } + get_multiplayer()->despawn(node, this); + } + tracked_nodes.clear(); + } +} + +void MultiplayerSpawner::_node_added(Node *p_node) { + if (!get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority()) { + return; + } + if (tracked_nodes.has(p_node->get_instance_id())) { + return; + } + const Node *parent = get_spawn_node(); + if (!parent || p_node->get_parent() != parent) { + return; + } + int id = get_scene_id(p_node->get_scene_file_path()); + if (id == INVALID_ID) { + return; + } + const String name = p_node->get_name(); + ERR_FAIL_COND_MSG(name.validate_node_name() != name, vformat("Unable to auto-spawn node with reserved name: %s. Make sure to add your replicated scenes via 'add_child(node, true)' to produce valid names.", name)); + _track(p_node, Variant(), id); +} + +void MultiplayerSpawner::set_auto_spawning(bool p_enabled) { + auto_spawn = p_enabled; + _update_spawn_node(); +} + +bool MultiplayerSpawner::is_auto_spawning() const { + return auto_spawn; +} + +TypedArray<PackedScene> MultiplayerSpawner::get_spawnable_scenes() { + return spawnable_scenes; +} + +void MultiplayerSpawner::set_spawnable_scenes(TypedArray<PackedScene> p_scenes) { + spawnable_scenes = p_scenes; +} + +NodePath MultiplayerSpawner::get_spawn_path() const { + return spawn_path; +} + +void MultiplayerSpawner::set_spawn_path(const NodePath &p_path) { + spawn_path = p_path; + _update_spawn_node(); +} + +void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_scene_id) { + ObjectID oid = p_node->get_instance_id(); + if (!tracked_nodes.has(oid)) { + tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id); + p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit), varray(p_node->get_instance_id()), CONNECT_ONESHOT); + p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready), varray(p_node->get_instance_id()), CONNECT_ONESHOT); + } +} + +void MultiplayerSpawner::_node_ready(ObjectID p_id) { + get_multiplayer()->spawn(ObjectDB::get_instance(p_id), this); +} + +void MultiplayerSpawner::_node_exit(ObjectID p_id) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); + ERR_FAIL_COND(!node); + if (tracked_nodes.has(p_id)) { + tracked_nodes.erase(p_id); + get_multiplayer()->despawn(node, this); + } +} + +int MultiplayerSpawner::get_scene_id(const String &p_scene) const { + for (int i = 0; i < spawnable_scenes.size(); i++) { + Ref<PackedScene> ps = spawnable_scenes[i]; + ERR_CONTINUE(ps.is_null()); + if (ps->get_path() == p_scene) { + return i; + } + } + return INVALID_ID; +} + +int MultiplayerSpawner::get_spawn_id(const ObjectID &p_id) const { + const SpawnInfo *info = tracked_nodes.getptr(p_id); + return info ? info->id : INVALID_ID; +} + +const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const { + const SpawnInfo *info = tracked_nodes.getptr(p_id); + return info ? info->args : Variant(); +} + +Node *MultiplayerSpawner::instantiate_scene(int p_id) { + ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_INDEX_V(p_id, spawnable_scenes.size(), nullptr); + Ref<PackedScene> scene = spawnable_scenes[p_id]; + ERR_FAIL_COND_V(scene.is_null(), nullptr); + return scene->instantiate(); +} + +Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) { + ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + Object *obj = nullptr; + Node *node = nullptr; + if (GDVIRTUAL_CALL(_spawn_custom, p_data, obj)) { + node = Object::cast_to<Node>(obj); + } + return node; +} + +Node *MultiplayerSpawner::spawn(const Variant &p_data) { + ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr); + ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script."); + + Node *parent = get_spawn_node(); + ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node."); + + Node *node = instantiate_custom(p_data); + ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node."); + + _track(node, p_data); + parent->add_child(node, true); + return node; +} diff --git a/scene/multiplayer/multiplayer_spawner.h b/scene/multiplayer/multiplayer_spawner.h new file mode 100644 index 0000000000..63948e39a5 --- /dev/null +++ b/scene/multiplayer/multiplayer_spawner.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* multiplayer_spawner.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_SPAWNER_H +#define MULTIPLAYER_SPAWNER_H + +#include "scene/main/node.h" + +#include "core/variant/typed_array.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/scene_replication_config.h" + +class MultiplayerSpawner : public Node { + GDCLASS(MultiplayerSpawner, Node); + +public: + enum { + INVALID_ID = 0xFF, + }; + +private: + TypedArray<PackedScene> spawnable_scenes; + Set<ResourceUID::ID> spawnable_ids; + NodePath spawn_path; + + struct SpawnInfo { + Variant args; + int id = INVALID_ID; + SpawnInfo(Variant p_args, int p_id) { + id = p_id; + args = p_args; + } + SpawnInfo() {} + }; + + ObjectID spawn_node; + HashMap<ObjectID, SpawnInfo> tracked_nodes; + bool auto_spawn = false; + uint32_t spawn_limit = 0; + + void _update_spawn_node(); + void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID); + void _node_added(Node *p_node); + void _node_exit(ObjectID p_id); + void _node_ready(ObjectID p_id); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; } + TypedArray<PackedScene> get_spawnable_scenes(); + void set_spawnable_scenes(TypedArray<PackedScene> p_scenes); + NodePath get_spawn_path() const; + void set_spawn_path(const NodePath &p_path); + uint32_t get_spawn_limit() const { return spawn_limit; } + void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; } + bool is_auto_spawning() const; + void set_auto_spawning(bool p_enabled); + + const Variant get_spawn_argument(const ObjectID &p_id) const; + int get_spawn_id(const ObjectID &p_id) const; + int get_scene_id(const String &p_path) const; + Node *spawn(const Variant &p_data = Variant()); + Node *instantiate_custom(const Variant &p_data); + Node *instantiate_scene(int p_idx); + + GDVIRTUAL1R(Object *, _spawn_custom, const Variant &); + + MultiplayerSpawner() {} +}; + +#endif // MULTIPLAYER_SPAWNER_H diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp new file mode 100644 index 0000000000..fbe1b99cc9 --- /dev/null +++ b/scene/multiplayer/multiplayer_synchronizer.cpp @@ -0,0 +1,158 @@ +/*************************************************************************/ +/* multiplayer_synchronizer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_synchronizer.h" + +#include "core/config/engine.h" +#include "core/multiplayer/multiplayer_api.h" + +Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) { + if (p_path.get_name_count() == 0) { + return p_obj; + } + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V_MSG(!node || !node->has_node(p_path), nullptr, vformat("Node '%s' not found.", p_path)); + return node->get_node(p_path); +} + +void MultiplayerSynchronizer::_stop() { + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (node) { + get_multiplayer()->replication_stop(node, this); + } +} + +void MultiplayerSynchronizer::_start() { + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (node) { + get_multiplayer()->replication_start(node, this); + } +} + +Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) { + ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + r_variant.resize(p_properties.size()); + r_variant_ptrs.resize(r_variant.size()); + int i = 0; + for (const NodePath &prop : p_properties) { + bool valid = false; + const Object *obj = _get_prop_target(p_obj, prop); + ERR_FAIL_COND_V(!obj, FAILED); + r_variant.write[i] = obj->get(prop.get_concatenated_subnames(), &valid); + r_variant_ptrs.write[i] = &r_variant[i]; + ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); + i++; + } + return OK; +} + +Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) { + ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + int i = 0; + for (const NodePath &prop : p_properties) { + Object *obj = _get_prop_target(p_obj, prop); + ERR_FAIL_COND_V(!obj, FAILED); + obj->set(prop.get_concatenated_subnames(), p_state[i]); + i += 1; + } + return OK; +} + +void MultiplayerSynchronizer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path); + ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); + + ClassDB::bind_method(D_METHOD("set_replication_interval", "milliseconds"), &MultiplayerSynchronizer::set_replication_interval); + ClassDB::bind_method(D_METHOD("get_replication_interval"), &MultiplayerSynchronizer::get_replication_interval); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001"), "set_replication_interval", "get_replication_interval"); + + ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config); + ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config"); +} + +void MultiplayerSynchronizer::_notification(int p_what) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + if (root_path.is_empty()) { + return; + } + if (p_what == NOTIFICATION_ENTER_TREE) { + _start(); + } else if (p_what == NOTIFICATION_EXIT_TREE) { + _stop(); + } +} + +void MultiplayerSynchronizer::set_replication_interval(double p_interval) { + ERR_FAIL_COND_MSG(p_interval < 0, "Interval must be greater or equal to 0 (where 0 means default)"); + interval_msec = uint64_t(p_interval * 1000); +} + +double MultiplayerSynchronizer::get_replication_interval() const { + return double(interval_msec) / 1000.0; +} + +uint64_t MultiplayerSynchronizer::get_replication_interval_msec() const { + return interval_msec; +} + +void MultiplayerSynchronizer::set_replication_config(Ref<SceneReplicationConfig> p_config) { + replication_config = p_config; +} + +Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() { + return replication_config; +} + +void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { + _stop(); + root_path = p_path; + _start(); +} + +NodePath MultiplayerSynchronizer::get_root_path() const { + return root_path; +} + +void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) { + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (!node) { + Node::set_multiplayer_authority(p_peer_id, p_recursive); + return; + } + get_multiplayer()->replication_stop(node, this); + Node::set_multiplayer_authority(p_peer_id, p_recursive); + get_multiplayer()->replication_start(node, this); +} diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/scene/multiplayer/multiplayer_synchronizer.h new file mode 100644 index 0000000000..e856745379 --- /dev/null +++ b/scene/multiplayer/multiplayer_synchronizer.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* multiplayer_synchronizer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_SYNCHRONIZER_H +#define MULTIPLAYER_SYNCHRONIZER_H + +#include "scene/main/node.h" + +#include "scene/resources/scene_replication_config.h" + +class MultiplayerSynchronizer : public Node { + GDCLASS(MultiplayerSynchronizer, Node); + +private: + Ref<SceneReplicationConfig> replication_config; + NodePath root_path; + uint64_t interval_msec = 0; + + static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); + void _start(); + void _stop(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs); + static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state); + + void set_replication_interval(double p_interval); + double get_replication_interval() const; + uint64_t get_replication_interval_msec() const; + + void set_replication_config(Ref<SceneReplicationConfig> p_config); + Ref<SceneReplicationConfig> get_replication_config(); + + void set_root_path(const NodePath &p_path); + NodePath get_root_path() const; + virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override; + + MultiplayerSynchronizer() {} +}; + +#endif // MULTIPLAYER_SYNCHRONIZER_H diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp new file mode 100644 index 0000000000..de4a94470a --- /dev/null +++ b/scene/multiplayer/scene_cache_interface.cpp @@ -0,0 +1,249 @@ +/*************************************************************************/ +/* scene_cache_interface.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_cache_interface.h" + +#include "core/io/marshalls.h" +#include "scene/main/node.h" +#include "scene/main/window.h" + +MultiplayerCacheInterface *SceneCacheInterface::_create(MultiplayerAPI *p_multiplayer) { + return memnew(SceneCacheInterface(p_multiplayer)); +} + +void SceneCacheInterface::make_default() { + MultiplayerAPI::create_default_cache_interface = _create; +} + +void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { + if (p_connected) { + path_get_cache.insert(p_id, PathGetCache()); + } else { + // Cleanup get cache. + path_get_cache.erase(p_id); + // Cleanup sent cache. + // Some refactoring is needed to make this faster and do paths GC. + List<NodePath> keys; + path_send_cache.get_key_list(&keys); + for (const NodePath &E : keys) { + PathSentCache *psc = path_send_cache.getptr(E); + psc->confirmed_peers.erase(p_id); + } + } +} + +void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_COND(!root_node); + ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); + int ofs = 1; + + String methods_md5; + methods_md5.parse_utf8((const char *)(p_packet + ofs), 32); + ofs += 33; + + int id = decode_uint32(&p_packet[ofs]); + ofs += 4; + + String paths; + paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); + + NodePath path = paths; + + if (!path_get_cache.has(p_from)) { + path_get_cache[p_from] = PathGetCache(); + } + + Node *node = root_node->get_node(path); + ERR_FAIL_COND(node == nullptr); + const bool valid_rpc_checksum = multiplayer->get_rpc_md5(node) == methods_md5; + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathGetCache::NodeInfo ni; + ni.path = path; + + path_get_cache[p_from].nodes[id] = ni; + + // Encode path to send ack. + CharString pname = String(path).utf8(); + int len = encode_cstring(pname.get_data(), nullptr); + + Vector<uint8_t> packet; + + packet.resize(1 + 1 + len); + packet.write[0] = MultiplayerAPI::NETWORK_COMMAND_CONFIRM_PATH; + packet.write[1] = valid_rpc_checksum; + encode_cstring(pname.get_data(), &packet.write[2]); + + Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND(multiplayer_peer.is_null()); + + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->set_target_peer(p_from); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); +} + +void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); + + const bool valid_rpc_checksum = p_packet[1]; + + String paths; + paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); + + NodePath path = paths; + + if (valid_rpc_checksum == false) { + ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); + } + + PathSentCache *psc = path_send_cache.getptr(path); + ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); + + Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); + ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); + E->get() = true; +} + +bool SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) { + bool has_all_peers = true; + List<int> peers_to_add; // If one is missing, take note to add it. + + for (const Set<int>::Element *E = multiplayer->get_connected_peers().front(); E; E = E->next()) { + if (p_target < 0 && E->get() == -p_target) { + continue; // Continue, excluded. + } + + if (p_target > 0 && E->get() != p_target) { + continue; // Continue, not for this peer. + } + + Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + + if (!F || !F->get()) { + // Path was not cached, or was cached but is unconfirmed. + if (!F) { + // Not cached at all, take note. + peers_to_add.push_back(E->get()); + } + + has_all_peers = false; + } + } + + if (peers_to_add.size() > 0) { + // Those that need to be added, send a message for this. + + // Encode function name. + const CharString path = String(p_path).utf8(); + const int path_len = encode_cstring(path.get_data(), nullptr); + + // Extract MD5 from rpc methods list. + const String methods_md5 = multiplayer->get_rpc_md5(p_node); + const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder. + + Vector<uint8_t> packet; + packet.resize(1 + 4 + path_len + methods_md5_len); + int ofs = 0; + + packet.write[ofs] = MultiplayerAPI::NETWORK_COMMAND_SIMPLIFY_PATH; + ofs += 1; + + ofs += encode_cstring(methods_md5.utf8().get_data(), &packet.write[ofs]); + + ofs += encode_uint32(psc->id, &packet.write[ofs]); + + ofs += encode_cstring(path.get_data(), &packet.write[ofs]); + + Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_V(multiplayer_peer.is_null(), false); + + for (int &E : peers_to_add) { + multiplayer_peer->set_target_peer(E); // To all of you. + multiplayer_peer->set_transfer_channel(0); + multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE); + multiplayer_peer->put_packet(packet.ptr(), packet.size()); + + psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. + } + } + + return has_all_peers; +} + +bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { + const PathSentCache *psc = path_send_cache.getptr(p_path); + ERR_FAIL_COND_V(!psc, false); + const Map<int, bool>::Element *F = psc->confirmed_peers.find(p_peer); + ERR_FAIL_COND_V(!F, false); // Should never happen. + return F->get(); +} + +bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node, false); + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(p_path); + if (!psc) { + // Path is not cached, create. + path_send_cache[p_path] = PathSentCache(); + psc = path_send_cache.getptr(p_path); + psc->id = last_send_cache_id++; + } + r_id = psc->id; + + return _send_confirm_path(node, p_path, psc, p_peer_id); +} + +Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_COND_V(!root_node, nullptr); + Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); + + Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_cache_id); + ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); + + PathGetCache::NodeInfo *ni = &F->get(); + Node *node = root_node->get_node(ni->path); + if (!node) { + ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); + } + return node; +} + +void SceneCacheInterface::clear() { + path_get_cache.clear(); + path_send_cache.clear(); + last_send_cache_id = 1; +} diff --git a/scene/multiplayer/scene_cache_interface.h b/scene/multiplayer/scene_cache_interface.h new file mode 100644 index 0000000000..91a53cb948 --- /dev/null +++ b/scene/multiplayer/scene_cache_interface.h @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* scene_cache_interface.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_CACHE_INTERFACE_H +#define SCENE_CACHE_INTERFACE_H + +#include "core/multiplayer/multiplayer_api.h" + +class SceneCacheInterface : public MultiplayerCacheInterface { + GDCLASS(SceneCacheInterface, MultiplayerCacheInterface); + +private: + MultiplayerAPI *multiplayer; + + //path sent caches + struct PathSentCache { + Map<int, bool> confirmed_peers; + int id; + }; + + //path get caches + struct PathGetCache { + struct NodeInfo { + NodePath path; + ObjectID instance; + }; + + Map<int, NodeInfo> nodes; + }; + + HashMap<NodePath, PathSentCache> path_send_cache; + Map<int, PathGetCache> path_get_cache; + int last_send_cache_id = 1; + +protected: + bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); + static MultiplayerCacheInterface *_create(MultiplayerAPI *p_multiplayer); + +public: + static void make_default(); + + virtual void clear() override; + virtual void on_peer_change(int p_id, bool p_connected) override; + virtual void process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) override; + virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) override; + + // Returns true if all peers have cached path. + virtual bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id) override; + virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) override; + virtual bool is_cache_confirmed(NodePath p_path, int p_peer) override; + + SceneCacheInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // SCENE_CACHE_INTERFACE_H diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp new file mode 100644 index 0000000000..2088a43ba7 --- /dev/null +++ b/scene/multiplayer/scene_replication_interface.cpp @@ -0,0 +1,411 @@ +/*************************************************************************/ +/* scene_replication_interface.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_replication_interface.h" + +#include "core/io/marshalls.h" +#include "scene/main/node.h" +#include "scene/multiplayer/multiplayer_spawner.h" +#include "scene/multiplayer/multiplayer_synchronizer.h" + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + +MultiplayerReplicationInterface *SceneReplicationInterface::_create(MultiplayerAPI *p_multiplayer) { + return memnew(SceneReplicationInterface(p_multiplayer)); +} + +void SceneReplicationInterface::make_default() { + MultiplayerAPI::create_default_replication_interface = _create; +} + +void SceneReplicationInterface::_free_remotes(int p_id) { + const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id); + const uint32_t *k = nullptr; + while ((k = remotes.next(k))) { + Node *node = rep_state->get_node(remotes.get(*k)); + ERR_CONTINUE(!node); + node->queue_delete(); + } +} + +void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) { + if (p_connected) { + rep_state->on_peer_change(p_id, p_connected); + for (const ObjectID &oid : rep_state->get_spawned_nodes()) { + _send_spawn(rep_state->get_node(oid), rep_state->get_spawner(oid), p_id); + } + for (const ObjectID &oid : rep_state->get_path_only_nodes()) { + Node *node = rep_state->get_node(oid); + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); + ERR_CONTINUE(!node || !sync); + if (sync->is_multiplayer_authority()) { + rep_state->peer_add_node(p_id, oid); + } + } + } else { + _free_remotes(p_id); + rep_state->on_peer_change(p_id, p_connected); + } +} + +void SceneReplicationInterface::on_reset() { + for (int pid : rep_state->get_peers()) { + _free_remotes(pid); + } + rep_state->reset(); +} + +void SceneReplicationInterface::on_network_process() { + uint64_t msec = OS::get_singleton()->get_ticks_msec(); + for (int peer : rep_state->get_peers()) { + _send_sync(peer, msec); + } +} + +Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); + ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); + Error err = rep_state->config_add_spawn(node, spawner); + ERR_FAIL_COND_V(err != OK, err); + return _send_spawn(node, spawner, 0); +} + +Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); + ERR_FAIL_COND_V(!p_obj || !spawner, ERR_INVALID_PARAMETER); + Error err = rep_state->config_del_spawn(node, spawner); + ERR_FAIL_COND_V(err != OK, err); + return _send_despawn(node, 0); +} + +Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); + ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + rep_state->config_add_sync(node, sync); + // Try to apply initial state if spawning (hack to apply if before ready). + if (pending_spawn == p_obj->get_instance_id()) { + pending_spawn = ObjectID(); // Make sure this only happens once. + const List<NodePath> props = sync->get_replication_config()->get_spawn_properties(); + Vector<Variant> vars; + vars.resize(props.size()); + int consumed; + Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed); + ERR_FAIL_COND_V(err, err); + err = MultiplayerSynchronizer::set_state(props, node, vars); + ERR_FAIL_COND_V(err, err); + } else if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { + // Either it's a spawn or a static sync, in any case add it to the list of known nodes. + rep_state->peer_add_node(0, p_obj->get_instance_id()); + } + return OK; +} + +Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_config) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); + MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); + ERR_FAIL_COND_V(!p_obj || !sync, ERR_INVALID_PARAMETER); + return rep_state->config_del_sync(node, sync); +} + +Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) { + ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + peer->set_target_peer(p_peer); + peer->set_transfer_channel(0); + peer->set_transfer_mode(p_reliable ? Multiplayer::TRANSFER_MODE_RELIABLE : Multiplayer::TRANSFER_MODE_UNRELIABLE); + return peer->put_packet(p_buffer, p_size); +} + +Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer) { + ERR_FAIL_COND_V(p_peer < 0, ERR_BUG); + ERR_FAIL_COND_V(!multiplayer, ERR_BUG); + ERR_FAIL_COND_V(!p_spawner || !p_node, ERR_BUG); + + const ObjectID oid = p_node->get_instance_id(); + uint32_t nid = rep_state->ensure_net_id(oid); + + // Prepare custom arg and scene_id + uint8_t scene_id = p_spawner->get_spawn_id(oid); + bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID; + Variant spawn_arg = p_spawner->get_spawn_argument(oid); + int spawn_arg_size = 0; + if (is_custom) { + Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false); + ERR_FAIL_COND_V(err, err); + } + + // Prepare spawn state. + int state_size = 0; + Vector<Variant> state_vars; + Vector<const Variant *> state_varp; + MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid); + if (synchronizer && synchronizer->get_replication_config().is_valid()) { + const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties(); + Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp); + ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state."); + err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), nullptr, state_size); + ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state."); + } + + // Prepare simplified path. + NodePath rel_path = multiplayer->get_root_path().rel_path_to(p_spawner->get_path()); + + int path_id = 0; + multiplayer->send_object_cache(p_spawner, rel_path, p_peer, path_id); + + // Encode name and parent ID. + CharString cname = p_node->get_name().operator String().utf8(); + int nlen = encode_cstring(cname.get_data(), nullptr); + MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_SPAWN; + ptr[1] = scene_id; + int ofs = 2; + ofs += encode_uint32(path_id, &ptr[ofs]); + ofs += encode_uint32(nid, &ptr[ofs]); + ofs += encode_uint32(nlen, &ptr[ofs]); + ofs += encode_cstring(cname.get_data(), &ptr[ofs]); + // Write args + if (is_custom) { + ofs += encode_uint32(spawn_arg_size, &ptr[ofs]); + Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, &ptr[ofs], spawn_arg_size, false); + ERR_FAIL_COND_V(err, err); + ofs += spawn_arg_size; + } + // Write state. + if (state_size) { + Error err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), &ptr[ofs], state_size); + ERR_FAIL_COND_V(err, err); + ofs += state_size; + } + Error err = _send_raw(ptr, ofs, p_peer, true); + ERR_FAIL_COND_V(err, err); + return rep_state->peer_add_node(p_peer, oid); +} + +Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) { + const ObjectID oid = p_node->get_instance_id(); + MAKE_ROOM(5); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_DESPAWN; + int ofs = 1; + uint32_t nid = rep_state->get_net_id(oid); + ofs += encode_uint32(nid, &ptr[ofs]); + Error err = _send_raw(ptr, ofs, p_peer, true); + ERR_FAIL_COND_V(err, err); + return rep_state->peer_del_node(p_peer, oid); +} + +Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { + ERR_FAIL_COND_V_MSG(p_buffer_len < 14, ERR_INVALID_DATA, "Invalid spawn packet received"); + int ofs = 1; // The spawn/despawn command. + uint8_t scene_id = p_buffer[ofs]; + ofs += 1; + uint32_t node_target = decode_uint32(&p_buffer[ofs]); + ofs += 4; + MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_cached_object(p_from, node_target)); + ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); + ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); + + uint32_t net_id = decode_uint32(&p_buffer[ofs]); + ofs += 4; + uint32_t name_len = decode_uint32(&p_buffer[ofs]); + ofs += 4; + ERR_FAIL_COND_V_MSG(name_len > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len)); + ERR_FAIL_COND_V_MSG(name_len < 1, ERR_INVALID_DATA, "Zero spawn name size."); + + // We need to make sure no trickery happens here, but we want to allow autogenerated ("@") node names. + const String name = String::utf8((const char *)&p_buffer[ofs], name_len); + ERR_FAIL_COND_V_MSG(name.validate_node_name() != name, ERR_INVALID_DATA, vformat("Invalid node name received: '%s'. Make sure to add nodes via 'add_child(node, true)' remotely.", name)); + ofs += name_len; + + // Check that we can spawn. + Node *parent = spawner->get_node_or_null(spawner->get_spawn_path()); + ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA); + + Node *node = nullptr; + if (scene_id == MultiplayerSpawner::INVALID_ID) { + // Custom spawn. + ERR_FAIL_COND_V(p_buffer_len - ofs < 4, ERR_INVALID_DATA); + uint32_t arg_size = decode_uint32(&p_buffer[ofs]); + ofs += 4; + ERR_FAIL_COND_V(arg_size > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA); + Variant v; + Error err = MultiplayerAPI::decode_and_decompress_variant(v, &p_buffer[ofs], arg_size, nullptr, false); + ERR_FAIL_COND_V(err != OK, err); + ofs += arg_size; + node = spawner->instantiate_custom(v); + } else { + // Scene based spawn. + node = spawner->instantiate_scene(scene_id); + } + ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED); + node->set_name(name); + rep_state->peer_add_remote(p_from, net_id, node, spawner); + // The initial state will be applied during the sync config (i.e. before _ready). + int state_len = p_buffer_len - ofs; + if (state_len) { + pending_spawn = node->get_instance_id(); + pending_buffer = &p_buffer[ofs]; + pending_buffer_size = state_len; + } + parent->add_child(node); + pending_spawn = ObjectID(); + pending_buffer = nullptr; + pending_buffer_size = 0; + return OK; +} + +Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { + ERR_FAIL_COND_V_MSG(p_buffer_len < 5, ERR_INVALID_DATA, "Invalid spawn packet received"); + int ofs = 1; // The spawn/despawn command. + uint32_t net_id = decode_uint32(&p_buffer[ofs]); + ofs += 4; + Node *node = nullptr; + Error err = rep_state->peer_del_remote(p_from, net_id, &node); + ERR_FAIL_COND_V(err != OK, err); + ERR_FAIL_COND_V(!node, ERR_BUG); + node->queue_delete(); + return OK; +} + +void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { + const Set<ObjectID> &known = rep_state->get_known_nodes(p_peer); + if (known.is_empty()) { + return; + } + MAKE_ROOM(sync_mtu); + uint8_t *ptr = packet_cache.ptrw(); + ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; + int ofs = 1; + ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]); + // Can only send updates for already notified nodes. + // This is a lazy implementation, we could optimize much more here with by grouping by replication config. + for (const ObjectID &oid : known) { + if (!rep_state->update_sync_time(oid, p_msec)) { + continue; // nothing to sync. + } + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); + ERR_CONTINUE(!sync); + Node *node = rep_state->get_node(oid); + ERR_CONTINUE(!node); + int size; + Vector<Variant> vars; + Vector<const Variant *> varp; + const List<NodePath> props = sync->get_replication_config()->get_sync_properties(); + Error err = MultiplayerSynchronizer::get_state(props, node, vars, varp); + ERR_CONTINUE_MSG(err != OK, "Unable to retrieve sync state."); + err = MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), nullptr, size); + ERR_CONTINUE_MSG(err != OK, "Unable to encode sync state."); + // TODO Handle single state above MTU. + ERR_CONTINUE_MSG(size > 3 + 4 + 4 + sync_mtu, vformat("Node states bigger then MTU will not be sent (%d > %d): %s", size, sync_mtu, node->get_path())); + if (ofs + 4 + 4 + size > sync_mtu) { + // Send what we got, and reset write. + _send_raw(packet_cache.ptr(), ofs, p_peer, false); + ofs = 3; + } + if (size) { + uint32_t net_id = rep_state->get_net_id(oid); + if (net_id == 0) { + // First time path based ID. + NodePath rel_path = multiplayer->get_root_path().rel_path_to(sync->get_path()); + int path_id = 0; + multiplayer->send_object_cache(sync, rel_path, p_peer, path_id); + net_id = path_id; + rep_state->set_net_id(oid, net_id | 0x80000000); + } + ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]); + ofs += encode_uint32(size, &ptr[ofs]); + MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size); + ofs += size; + } + } + if (ofs > 3) { + // Got some left over to send. + _send_raw(packet_cache.ptr(), ofs, p_peer, false); + } +} + +Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { + ERR_FAIL_COND_V_MSG(p_buffer_len < 11, ERR_INVALID_DATA, "Invalid sync packet received"); + uint16_t time = decode_uint16(&p_buffer[1]); + int ofs = 3; + rep_state->peer_sync_recv(p_from, time); + while (ofs + 8 < p_buffer_len) { + uint32_t net_id = decode_uint32(&p_buffer[ofs]); + ofs += 4; + uint32_t size = decode_uint32(&p_buffer[ofs]); + ofs += 4; + Node *node = nullptr; + if (net_id & 0x80000000) { + MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_cached_object(p_from, net_id & 0x7FFFFFFF)); + ERR_FAIL_COND_V(!sync || sync->get_multiplayer_authority() != p_from, ERR_UNAUTHORIZED); + node = sync->get_node(sync->get_root_path()); + } else { + node = rep_state->peer_get_remote(p_from, net_id); + } + if (!node) { + // Not received yet. + ofs += size; + continue; + } + const ObjectID oid = node->get_instance_id(); + if (!rep_state->update_last_node_sync(oid, time)) { + // State is too old. + ofs += size; + continue; + } + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); + ERR_FAIL_COND_V(!sync, ERR_BUG); + ERR_FAIL_COND_V(size > uint32_t(p_buffer_len - ofs), ERR_BUG); + const List<NodePath> props = sync->get_replication_config()->get_sync_properties(); + Vector<Variant> vars; + vars.resize(props.size()); + int consumed; + Error err = MultiplayerAPI::decode_and_decompress_variants(vars, &p_buffer[ofs], size, consumed); + ERR_FAIL_COND_V(err, err); + err = MultiplayerSynchronizer::set_state(props, node, vars); + ERR_FAIL_COND_V(err, err); + ofs += size; + } + return OK; +} diff --git a/scene/multiplayer/scene_replication_interface.h b/scene/multiplayer/scene_replication_interface.h new file mode 100644 index 0000000000..855878d029 --- /dev/null +++ b/scene/multiplayer/scene_replication_interface.h @@ -0,0 +1,84 @@ +/*************************************************************************/ +/* scene_replication_interface.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_TREE_REPLICATOR_INTERFACE_H +#define SCENE_TREE_REPLICATOR_INTERFACE_H + +#include "core/multiplayer/multiplayer_api.h" + +#include "scene/multiplayer/scene_replication_state.h" + +class SceneReplicationInterface : public MultiplayerReplicationInterface { + GDCLASS(SceneReplicationInterface, MultiplayerReplicationInterface); + +private: + void _send_sync(int p_peer, uint64_t p_msec); + Error _send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer); + Error _send_despawn(Node *p_node, int p_peer); + Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); + + void _free_remotes(int p_peer); + + Ref<SceneReplicationState> rep_state; + MultiplayerAPI *multiplayer; + PackedByteArray packet_cache; + int sync_mtu = 1350; // Highly dependent on underlying protocol. + + // An hack to apply the initial state before ready. + ObjectID pending_spawn; + const uint8_t *pending_buffer = nullptr; + int pending_buffer_size = 0; + +protected: + static MultiplayerReplicationInterface *_create(MultiplayerAPI *p_multiplayer); + +public: + static void make_default(); + + virtual void on_reset() override; + virtual void on_peer_change(int p_id, bool p_connected) override; + + virtual Error on_spawn(Object *p_obj, Variant p_config) override; + virtual Error on_despawn(Object *p_obj, Variant p_config) override; + virtual Error on_replication_start(Object *p_obj, Variant p_config) override; + virtual Error on_replication_stop(Object *p_obj, Variant p_config) override; + virtual void on_network_process() override; + + virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override; + virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override; + virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override; + + SceneReplicationInterface(MultiplayerAPI *p_multiplayer) { + rep_state.instantiate(); + multiplayer = p_multiplayer; + } +}; + +#endif // SCENE_TREE_REPLICATOR_INTERFACE_H diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp new file mode 100644 index 0000000000..b8dadeff24 --- /dev/null +++ b/scene/multiplayer/scene_replication_state.cpp @@ -0,0 +1,258 @@ +/*************************************************************************/ +/* scene_replication_state.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/multiplayer/scene_replication_state.h" + +#include "core/multiplayer/multiplayer_api.h" +#include "scene/multiplayer/multiplayer_spawner.h" +#include "scene/multiplayer/multiplayer_synchronizer.h" +#include "scene/scene_string_names.h" + +SceneReplicationState::TrackedNode &SceneReplicationState::_track(const ObjectID &p_id) { + if (!tracked_nodes.has(p_id)) { + tracked_nodes[p_id] = TrackedNode(p_id); + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); + node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack), varray(p_id), Node::CONNECT_ONESHOT); + } + return tracked_nodes[p_id]; +} + +void SceneReplicationState::_untrack(const ObjectID &p_id) { + if (tracked_nodes.has(p_id)) { + uint32_t net_id = tracked_nodes[p_id].net_id; + uint32_t peer = tracked_nodes[p_id].remote_peer; + tracked_nodes.erase(p_id); + // If it was spawned by a remote, remove it from the received nodes. + if (peer && peers_info.has(peer)) { + peers_info[peer].recv_nodes.erase(net_id); + } + // If we spawned or synced it, we need to remove it from any peer it was sent to. + if (net_id || peer == 0) { + const int *k = nullptr; + while ((k = peers_info.next(k))) { + peers_info.get(*k).known_nodes.erase(p_id); + } + } + } +} + +const HashMap<uint32_t, ObjectID> SceneReplicationState::peer_get_remotes(int p_peer) const { + return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap<uint32_t, ObjectID>(); +} + +bool SceneReplicationState::update_last_node_sync(const ObjectID &p_id, uint16_t p_time) { + TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND_V(!tnode, false); + if (p_time <= tnode->last_sync && tnode->last_sync - p_time < 32767) { + return false; + } + tnode->last_sync = p_time; + return true; +} + +bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_msec) { + TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND_V(!tnode, false); + MultiplayerSynchronizer *sync = get_synchronizer(p_id); + if (!sync) { + return false; + } + if (tnode->last_sync_msec == p_msec) { + return true; + } + if (p_msec >= tnode->last_sync_msec + sync->get_replication_interval_msec()) { + tnode->last_sync_msec = p_msec; + return true; + } + return false; +} + +const Set<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), Set<ObjectID>()); + return peers_info[p_peer].known_nodes; +} + +uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const { + const TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND_V(!tnode, 0); + return tnode->net_id; +} + +void SceneReplicationState::set_net_id(const ObjectID &p_id, uint32_t p_net_id) { + TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND(!tnode); + tnode->net_id = p_net_id; +} + +uint32_t SceneReplicationState::ensure_net_id(const ObjectID &p_id) { + TrackedNode *tnode = tracked_nodes.getptr(p_id); + ERR_FAIL_COND_V(!tnode, 0); + if (tnode->net_id == 0) { + tnode->net_id = ++last_net_id; + } + return tnode->net_id; +} + +void SceneReplicationState::on_peer_change(int p_peer, bool p_connected) { + if (p_connected) { + peers_info[p_peer] = PeerInfo(); + known_peers.insert(p_peer); + } else { + peers_info.erase(p_peer); + known_peers.erase(p_peer); + } +} + +void SceneReplicationState::reset() { + peers_info.clear(); + known_peers.clear(); + // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned. + const ObjectID *oid = nullptr; + while ((oid = tracked_nodes.next(oid))) { + TrackedNode &tobj = tracked_nodes[*oid]; + tobj.net_id = 0; + tobj.remote_peer = 0; + tobj.last_sync = 0; + } +} + +Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner) { + const ObjectID oid = p_node->get_instance_id(); + TrackedNode &tobj = _track(oid); + ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE); + tobj.spawner = p_spawner->get_instance_id(); + spawned_nodes.insert(oid); + // The spawner may be notified after the synchronizer. + path_only_nodes.erase(oid); + return OK; +} + +Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner) { + const ObjectID oid = p_node->get_instance_id(); + ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER); + TrackedNode &tobj = _track(oid); + ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER); + tobj.spawner = ObjectID(); + spawned_nodes.erase(oid); + return OK; +} + +Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync) { + const ObjectID oid = p_node->get_instance_id(); + TrackedNode &tobj = _track(oid); + ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE); + tobj.synchronizer = p_sync->get_instance_id(); + // If it doesn't have a spawner, we might need to assign ID for this node using it's path. + if (tobj.spawner.is_null()) { + path_only_nodes.insert(oid); + } + return OK; +} + +Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync) { + const ObjectID oid = p_node->get_instance_id(); + ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER); + TrackedNode &tobj = _track(oid); + ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER); + tobj.synchronizer = ObjectID(); + if (path_only_nodes.has(oid)) { + p_node->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack)); + _untrack(oid); + path_only_nodes.erase(oid); + } + return OK; +} + +Error SceneReplicationState::peer_add_node(int p_peer, const ObjectID &p_id) { + if (p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].known_nodes.insert(p_id); + } else { + const int *pid = nullptr; + while ((pid = peers_info.next(pid))) { + peers_info.get(*pid).known_nodes.insert(p_id); + } + } + return OK; +} + +Error SceneReplicationState::peer_del_node(int p_peer, const ObjectID &p_id) { + if (p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].known_nodes.erase(p_id); + } else { + const int *pid = nullptr; + while ((pid = peers_info.next(pid))) { + peers_info.get(*pid).known_nodes.erase(p_id); + } + } + return OK; +} + +Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) { + PeerInfo *info = peers_info.getptr(p_peer); + return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr; +} + +Error SceneReplicationState::peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner) { + ERR_FAIL_COND_V(!p_node || !p_spawner, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAVAILABLE); + PeerInfo &pinfo = peers_info[p_peer]; + ObjectID oid = p_node->get_instance_id(); + TrackedNode &tobj = _track(oid); + tobj.spawner = p_spawner->get_instance_id(); + tobj.net_id = p_net_id; + tobj.remote_peer = p_peer; + tobj.last_sync = pinfo.last_recv_sync; + // Also track as a remote. + ERR_FAIL_COND_V(pinfo.recv_nodes.has(p_net_id), ERR_ALREADY_IN_USE); + pinfo.recv_nodes[p_net_id] = oid; + return OK; +} + +Error SceneReplicationState::peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAUTHORIZED); + PeerInfo &info = peers_info[p_peer]; + ERR_FAIL_COND_V(!info.recv_nodes.has(p_net_id), ERR_UNAUTHORIZED); + *r_node = Object::cast_to<Node>(ObjectDB::get_instance(info.recv_nodes[p_net_id])); + info.recv_nodes.erase(p_net_id); + return OK; +} + +uint16_t SceneReplicationState::peer_sync_next(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), 0); + PeerInfo &info = peers_info[p_peer]; + return ++info.last_sent_sync; +} + +void SceneReplicationState::peer_sync_recv(int p_peer, uint16_t p_time) { + ERR_FAIL_COND(!peers_info.has(p_peer)); + peers_info[p_peer].last_recv_sync = p_time; +} diff --git a/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h new file mode 100644 index 0000000000..18e4d9fa39 --- /dev/null +++ b/scene/multiplayer/scene_replication_state.h @@ -0,0 +1,121 @@ +/*************************************************************************/ +/* scene_replication_state.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_REPLICATON_STATE_H +#define SCENE_REPLICATON_STATE_H + +#include "core/object/ref_counted.h" + +class MultiplayerSpawner; +class MultiplayerSynchronizer; +class Node; + +class SceneReplicationState : public RefCounted { +private: + struct TrackedNode { + ObjectID id; + uint32_t net_id = 0; + uint32_t remote_peer = 0; + ObjectID spawner; + ObjectID synchronizer; + uint16_t last_sync = 0; + uint64_t last_sync_msec = 0; + + bool operator==(const ObjectID &p_other) { return id == p_other; } + + Node *get_node() const { return id.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(id)) : nullptr; } + MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; } + MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(synchronizer)) : nullptr; } + TrackedNode() {} + TrackedNode(const ObjectID &p_id) { id = p_id; } + TrackedNode(const ObjectID &p_id, uint32_t p_net_id) { + id = p_id; + net_id = p_net_id; + } + }; + + struct PeerInfo { + Set<ObjectID> known_nodes; + HashMap<uint32_t, ObjectID> recv_nodes; + uint16_t last_sent_sync = 0; + uint16_t last_recv_sync = 0; + }; + + Set<int> known_peers; + uint32_t last_net_id = 0; + HashMap<ObjectID, TrackedNode> tracked_nodes; + HashMap<int, PeerInfo> peers_info; + Set<ObjectID> spawned_nodes; + Set<ObjectID> path_only_nodes; + + TrackedNode &_track(const ObjectID &p_id); + void _untrack(const ObjectID &p_id); + bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); } + +public: + const Set<int> get_peers() const { return known_peers; } + const Set<ObjectID> get_spawned_nodes() const { return spawned_nodes; } + const Set<ObjectID> get_path_only_nodes() const { return path_only_nodes; } + + MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; } + MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; } + Node *get_node(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_node() : nullptr; } + bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time); + bool update_sync_time(const ObjectID &p_id, uint64_t p_msec); + + const Set<ObjectID> get_known_nodes(int p_peer); + uint32_t get_net_id(const ObjectID &p_id) const; + void set_net_id(const ObjectID &p_id, uint32_t p_net_id); + uint32_t ensure_net_id(const ObjectID &p_id); + + void reset(); + void on_peer_change(int p_peer, bool p_connected); + + Error config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner); + Error config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner); + + Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync); + Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync); + + Error peer_add_node(int p_peer, const ObjectID &p_id); + Error peer_del_node(int p_peer, const ObjectID &p_id); + + const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const; + Node *peer_get_remote(int p_peer, uint32_t p_net_id); + Error peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner); + Error peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node); + + uint16_t peer_sync_next(int p_peer); + void peer_sync_recv(int p_peer, uint16_t p_time); + + SceneReplicationState() {} +}; + +#endif // SCENE_REPLICATON_STATE_H diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp new file mode 100644 index 0000000000..7d7f57b9a1 --- /dev/null +++ b/scene/multiplayer/scene_rpc_interface.cpp @@ -0,0 +1,512 @@ +/*************************************************************************/ +/* scene_rpc_interface.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/multiplayer/scene_rpc_interface.h" + +#include "core/debugger/engine_debugger.h" +#include "core/io/marshalls.h" +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/node.h" +#include "scene/main/window.h" + +MultiplayerRPCInterface *SceneRPCInterface::_create(MultiplayerAPI *p_multiplayer) { + return memnew(SceneRPCInterface(p_multiplayer)); +} + +void SceneRPCInterface::make_default() { + MultiplayerAPI::create_default_rpc_interface = _create; +} + +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("node"); + values.push_back(p_id); + values.push_back(p_what); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#else +_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {} +#endif + +// Returns the packet size stripping the node path added when the node is not yet cached. +int get_packet_len(uint32_t p_node_target, int p_packet_len) { + if (p_node_target & 0x80000000) { + int ofs = p_node_target & 0x7FFFFFFF; + return p_packet_len - (p_packet_len - ofs); + } else { + return p_packet_len; + } +} + +const Multiplayer::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { + const Vector<Multiplayer::RPCConfig> node_config = p_node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + if (node_config[i].name == p_method) { + r_id = ((uint16_t)i) | (1 << 15); + return node_config[i]; + } + } + if (p_node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + if (script_config[i].name == p_method) { + r_id = (uint16_t)i; + return script_config[i]; + } + } + } + return Multiplayer::RPCConfig(); +} + +const Multiplayer::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { + Vector<Multiplayer::RPCConfig> config; + uint16_t id = p_id; + if (id & (1 << 15)) { + id = id & ~(1 << 15); + config = p_node->get_node_rpc_methods(); + } else if (p_node->get_script_instance()) { + config = p_node->get_script_instance()->get_rpc_methods(); + } + if (id < config.size()) { + return config[id]; + } + return Multiplayer::RPCConfig(); +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, Multiplayer::RPCMode mode, int p_remote_id) { + switch (mode) { + case Multiplayer::RPC_MODE_DISABLED: { + return false; + } break; + case Multiplayer::RPC_MODE_ANY_PEER: { + return true; + } break; + case Multiplayer::RPC_MODE_AUTHORITY: { + return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority(); + } break; + } + + return false; +} + +String SceneRPCInterface::get_rpc_md5(const Object *p_obj) const { + const Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node, ""); + String rpc_list; + const Vector<Multiplayer::RPCConfig> node_config = node->get_node_rpc_methods(); + for (int i = 0; i < node_config.size(); i++) { + rpc_list += String(node_config[i].name); + } + if (node->get_script_instance()) { + const Vector<Multiplayer::RPCConfig> script_config = node->get_script_instance()->get_rpc_methods(); + for (int i = 0; i < script_config.size(); i++) { + rpc_list += String(script_config[i].name); + } + } + return rpc_list.md5_text(); +} + +Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_COND_V(!root_node, nullptr); + Node *node = nullptr; + + if (p_node_target & 0x80000000) { + // Use full path (not cached yet). + int ofs = p_node_target & 0x7FFFFFFF; + + ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); + + String paths; + paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); + + NodePath np = paths; + + node = root_node->get_node(np); + + if (!node) { + ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); + } + return node; + } else { + // Use cached path. + return Object::cast_to<Node>(multiplayer->get_cached_object(p_from, p_node_target)); + } +} + +void SceneRPCInterface::process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) { + // Extract packet meta + int packet_min_size = 1; + int name_id_offset = 1; + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + // Compute the meta size, which depends on the compression level. + int node_id_compression = (p_packet[0] & NODE_ID_COMPRESSION_FLAG) >> NODE_ID_COMPRESSION_SHIFT; + int name_id_compression = (p_packet[0] & NAME_ID_COMPRESSION_FLAG) >> NAME_ID_COMPRESSION_SHIFT; + + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + packet_min_size += 1; + name_id_offset += 1; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + packet_min_size += 2; + name_id_offset += 2; + break; + case NETWORK_NODE_ID_COMPRESSION_32: + packet_min_size += 4; + name_id_offset += 4; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the node id compression mode."); + } + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + packet_min_size += 1; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + packet_min_size += 2; + break; + default: + ERR_FAIL_MSG("Was not possible to extract the name id compression mode."); + } + ERR_FAIL_COND_MSG(p_packet_len < packet_min_size, "Invalid packet received. Size too small."); + + uint32_t node_target = 0; + switch (node_id_compression) { + case NETWORK_NODE_ID_COMPRESSION_8: + node_target = p_packet[1]; + break; + case NETWORK_NODE_ID_COMPRESSION_16: + node_target = decode_uint16(p_packet + 1); + break; + case NETWORK_NODE_ID_COMPRESSION_32: + node_target = decode_uint32(p_packet + 1); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); + ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + + uint16_t name_id = 0; + switch (name_id_compression) { + case NETWORK_NAME_ID_COMPRESSION_8: + name_id = p_packet[name_id_offset]; + break; + case NETWORK_NAME_ID_COMPRESSION_16: + name_id = decode_uint16(p_packet + name_id_offset); + break; + default: + // Unreachable, checked before. + CRASH_NOW(); + } + + const int packet_len = get_packet_len(node_target, p_packet_len); + _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); +} + +void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { + ERR_FAIL_COND_MSG(p_offset > p_packet_len, "Invalid packet received. Size too small."); + + // Check that remote can call the RPC on this node. + const Multiplayer::RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); + ERR_FAIL_COND(config.name == StringName()); + + bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); + ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + "."); + + int argc = 0; + + const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG; + if (byte_only_or_no_args) { + if (p_offset < p_packet_len) { + // This packet contains only bytes. + argc = 1; + } + } else { + // Normal variant, takes the argument count from the packet. + ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); + argc = p_packet[p_offset]; + p_offset += 1; + } + + Vector<Variant> args; + Vector<const Variant *> argp; + args.resize(argc); + argp.resize(argc); + +#ifdef DEBUG_ENABLED + _profile_node_data("in_rpc", p_node->get_instance_id()); +#endif + + int out; + MultiplayerAPI::decode_and_decompress_variants(args, &p_packet[p_offset], p_packet_len - p_offset, out, byte_only_or_no_args, multiplayer->is_object_decoding_allowed()); + for (int i = 0; i < argc; i++) { + argp.write[i] = &args[i]; + } + + Callable::CallError ce; + + p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); + error = "RPC - " + error; + ERR_PRINT(error); + } +} + +void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to call RPC while multiplayer peer is not connected yet."); + + ERR_FAIL_COND_MSG(peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to call RPC while multiplayer peer is disconnected."); + + ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments (>255)."); + + if (p_to != 0 && !multiplayer->get_connected_peers().has(ABS(p_to))) { + ERR_FAIL_COND_MSG(p_to == peer->get_unique_id(), "Attempt to call RPC on yourself! Peer unique ID: " + itos(peer->get_unique_id()) + "."); + + ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); + } + + NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); + ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); + + // See if all peers have cached path (if so, call can be fast). + int psc_id; + const bool has_all_peers = multiplayer->send_object_cache(p_from, from_path, p_to, psc_id); + + // Create base packet, lots of hardcode because it must be tight. + + int ofs = 0; + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) \ + packet_cache.resize(m_amount); + + // Encode meta. + uint8_t command_type = MultiplayerAPI::NETWORK_COMMAND_REMOTE_CALL; + uint8_t node_id_compression = UINT8_MAX; + uint8_t name_id_compression = UINT8_MAX; + bool byte_only_or_no_args = false; + + MAKE_ROOM(1); + // The meta is composed along the way, so just set 0 for now. + packet_cache.write[0] = 0; + ofs += 1; + + // Encode Node ID. + if (has_all_peers) { + // Compress the node ID only if all the target peers already know it. + if (psc_id >= 0 && psc_id <= 255) { + // We can encode the id in 1 byte + node_id_compression = NETWORK_NODE_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(psc_id); + ofs += 1; + } else if (psc_id >= 0 && psc_id <= 65535) { + // We can encode the id in 2 bytes + node_id_compression = NETWORK_NODE_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(static_cast<uint16_t>(psc_id), &(packet_cache.write[ofs])); + ofs += 2; + } else { + // Too big, let's use 4 bytes. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + } else { + // The targets don't know the node yet, so we need to use 32 bits int. + node_id_compression = NETWORK_NODE_ID_COMPRESSION_32; + MAKE_ROOM(ofs + 4); + encode_uint32(psc_id, &(packet_cache.write[ofs])); + ofs += 4; + } + + // Encode method ID + if (p_rpc_id <= UINT8_MAX) { + // The ID fits in 1 byte + name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; + MAKE_ROOM(ofs + 1); + packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); + ofs += 1; + } else { + // The ID is larger, let's use 2 bytes + name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; + MAKE_ROOM(ofs + 2); + encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); + ofs += 2; + } + + int len; + Error err = MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, nullptr, len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed()); + ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC arguments. THIS IS LIKELY A BUG IN THE ENGINE!"); + if (byte_only_or_no_args) { + MAKE_ROOM(ofs + len); + } else { + MAKE_ROOM(ofs + 1 + len); + packet_cache.write[ofs] = p_argcount; + ofs += 1; + } + if (len) { + MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, &packet_cache.write[ofs], len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed()); + ofs += len; + } + + ERR_FAIL_COND(command_type > 7); + ERR_FAIL_COND(node_id_compression > 3); + ERR_FAIL_COND(name_id_compression > 1); + + // We can now set the meta + packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); + +#ifdef DEBUG_ENABLED + multiplayer->profile_bandwidth("out", ofs); +#endif + + // Take chance and set transfer mode, since all send methods will use it. + peer->set_transfer_channel(p_config.channel); + peer->set_transfer_mode(p_config.transfer_mode); + + if (has_all_peers) { + // They all have verified paths, so send fast. + peer->set_target_peer(p_to); // To all of you. + peer->put_packet(packet_cache.ptr(), ofs); // A message with love. + } else { + // Unreachable because the node ID is never compressed if the peers doesn't know it. + CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); + + // Not all verified path, so send one by one. + + // Append path at the end, since we will need it for some packets. + CharString pname = String(from_path).utf8(); + int path_len = encode_cstring(pname.get_data(), nullptr); + MAKE_ROOM(ofs + path_len); + encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); + + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Continue, excluded. + } + + if (p_to > 0 && P != p_to) { + continue; // Continue, not for this peer. + } + + bool confirmed = multiplayer->is_cache_confirmed(from_path, P); + + peer->set_target_peer(P); // To this one specifically. + + if (confirmed) { + // This one confirmed path, so use id. + encode_uint32(psc_id, &(packet_cache.write[1])); + peer->put_packet(packet_cache.ptr(), ofs); + } else { + // This one did not confirm path yet, so use entire path (sorry!). + encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. + peer->put_packet(packet_cache.ptr(), ofs + path_len); + } + } + } +} + +void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { + Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_MSG(!peer.is_valid(), "Trying to call an RPC while no multiplayer peer is active."); + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND(!node); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); + ERR_FAIL_COND_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a multiplayer peer which is not connected."); + + int node_id = peer->get_unique_id(); + bool call_local_native = false; + bool call_local_script = false; + uint16_t rpc_id = UINT16_MAX; + const Multiplayer::RPCConfig config = _get_rpc_config(node, p_method, rpc_id); + ERR_FAIL_COND_MSG(config.name == StringName(), + vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, node->get_path())); + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + if (rpc_id & (1 << 15)) { + call_local_native = config.call_local; + } else { + call_local_script = config.call_local; + } + } + + if (p_peer_id != node_id) { +#ifdef DEBUG_ENABLED + _profile_node_data("out_rpc", node->get_instance_id()); +#endif + + _send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); + } + + if (call_local_native) { + Callable::CallError ce; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + node->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + if (call_local_script) { + Callable::CallError ce; + ce.error = Callable::CallError::CALL_OK; + + multiplayer->set_remote_sender_override(peer->get_unique_id()); + node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); + multiplayer->set_remote_sender_override(0); + + if (ce.error != Callable::CallError::CALL_OK) { + String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in script local call: - " + error + "."; + ERR_PRINT(error); + return; + } + } + + ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.call_local, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); +} diff --git a/scene/multiplayer/scene_rpc_interface.h b/scene/multiplayer/scene_rpc_interface.h new file mode 100644 index 0000000000..86e1d0d280 --- /dev/null +++ b/scene/multiplayer/scene_rpc_interface.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* scene_rpc_interface.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_RPC_INTERFACE_H +#define SCENE_RPC_INTERFACE_H + +#include "core/multiplayer/multiplayer.h" +#include "core/multiplayer/multiplayer_api.h" + +class SceneRPCInterface : public MultiplayerRPCInterface { + GDCLASS(SceneRPCInterface, MultiplayerRPCInterface); + +private: + enum NetworkNodeIdCompression { + NETWORK_NODE_ID_COMPRESSION_8 = 0, + NETWORK_NODE_ID_COMPRESSION_16, + NETWORK_NODE_ID_COMPRESSION_32, + }; + + enum NetworkNameIdCompression { + NETWORK_NAME_ID_COMPRESSION_8 = 0, + NETWORK_NAME_ID_COMPRESSION_16, + }; + + // The RPC meta is composed by a single byte that contains (starting from the least significant bit): + // - `NetworkCommands` in the first four bits. + // - `NetworkNodeIdCompression` in the next 2 bits. + // - `NetworkNameIdCompression` in the next 1 bit. + // - `byte_only_or_no_args` in the next 1 bit. + enum { + NODE_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT, // 2 bits for this. + NAME_ID_COMPRESSION_SHIFT = MultiplayerAPI::CMD_FLAG_2_SHIFT, + BYTE_ONLY_OR_NO_ARGS_SHIFT = MultiplayerAPI::CMD_FLAG_3_SHIFT, + }; + + enum { + NODE_ID_COMPRESSION_FLAG = (1 << NODE_ID_COMPRESSION_SHIFT) | (1 << (NODE_ID_COMPRESSION_SHIFT + 1)), // 2 bits for this. + NAME_ID_COMPRESSION_FLAG = (1 << NAME_ID_COMPRESSION_SHIFT), + BYTE_ONLY_OR_NO_ARGS_FLAG = (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT), + }; + + MultiplayerAPI *multiplayer = nullptr; + Vector<uint8_t> packet_cache; + +protected: + static MultiplayerRPCInterface *_create(MultiplayerAPI *p_multiplayer); + + _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id); + void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + + void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Multiplayer::RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); + Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len); + +public: + static void make_default(); + + virtual void rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) override; + virtual void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len) override; + virtual String get_rpc_md5(const Object *p_obj) const override; + + SceneRPCInterface(MultiplayerAPI *p_multiplayer) { multiplayer = p_multiplayer; } +}; + +#endif // SCENE_RPC_INTERFACE_H diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp index 2540a633a9..a9b7e9acbe 100644 --- a/scene/property_utils.cpp +++ b/scene/property_utils.cpp @@ -130,7 +130,7 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const if (p != -1 && p < prop_str.length() - 1) { bool all_digits = true; for (int i = p + 1; i < prop_str.length(); i++) { - if (prop_str[i] < '0' || prop_str[i] > '9') { + if (!is_digit(prop_str[i])) { all_digits = false; break; } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 838cef824b..9ed83eb8c3 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -134,6 +134,11 @@ #include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/main/window.h" +#include "scene/multiplayer/multiplayer_spawner.h" +#include "scene/multiplayer/multiplayer_synchronizer.h" +#include "scene/multiplayer/scene_cache_interface.h" +#include "scene/multiplayer/scene_replication_interface.h" +#include "scene/multiplayer/scene_rpc_interface.h" #include "scene/resources/audio_stream_sample.h" #include "scene/resources/bit_map.h" #include "scene/resources/box_shape_3d.h" @@ -301,6 +306,8 @@ void register_scene_types() { GDREGISTER_CLASS(SubViewport); GDREGISTER_CLASS(ViewportTexture); GDREGISTER_CLASS(HTTPRequest); + GDREGISTER_CLASS(MultiplayerSpawner); + GDREGISTER_CLASS(MultiplayerSynchronizer); GDREGISTER_CLASS(Timer); GDREGISTER_CLASS(CanvasLayer); GDREGISTER_CLASS(CanvasModulate); @@ -822,6 +829,8 @@ void register_scene_types() { GDREGISTER_CLASS(Font); GDREGISTER_CLASS(Curve); + GDREGISTER_CLASS(SceneReplicationConfig); + GDREGISTER_CLASS(TextLine); GDREGISTER_CLASS(TextParagraph); @@ -1050,6 +1059,9 @@ void register_scene_types() { } SceneDebugger::initialize(); + SceneReplicationInterface::make_default(); + SceneRPCInterface::make_default(); + SceneCacheInterface::make_default(); NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE); } diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 6bb710b1d9..c03faa2c2d 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -1951,7 +1951,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } Error ResourceFormatSaverText::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { - if (p_path.ends_with(".sct") && p_resource->get_class() != "PackedScene") { + if (p_path.ends_with(".tscn") && !Ref<PackedScene>(p_resource).is_valid()) { return ERR_FILE_UNRECOGNIZED; } @@ -1960,14 +1960,14 @@ Error ResourceFormatSaverText::save(const String &p_path, const RES &p_resource, } bool ResourceFormatSaverText::recognize(const RES &p_resource) const { - return true; // all recognized! + return true; // All resources recognized! } void ResourceFormatSaverText::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (p_resource->get_class() == "PackedScene") { - p_extensions->push_back("tscn"); //text scene + if (Ref<PackedScene>(p_resource).is_valid()) { + p_extensions->push_back("tscn"); // Text scene. } else { - p_extensions->push_back("tres"); //text resource + p_extensions->push_back("tres"); // Text resource. } } diff --git a/scene/resources/scene_replication_config.cpp b/scene/resources/scene_replication_config.cpp new file mode 100644 index 0000000000..2acc0f1922 --- /dev/null +++ b/scene/resources/scene_replication_config.cpp @@ -0,0 +1,187 @@ +/*************************************************************************/ +/* scene_replication_config.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_replication_config.h" + +#include "core/multiplayer/multiplayer_api.h" +#include "scene/main/node.h" + +bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + + if (name.begins_with("properties/")) { + int idx = name.get_slicec('/', 1).to_int(); + String what = name.get_slicec('/', 2); + + if (properties.size() == idx && what == "path") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::NODE_PATH, false); + NodePath path = p_value; + ERR_FAIL_COND_V(path.is_empty() || path.get_subname_count() == 0, false); + add_property(path); + return true; + } + ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); + ERR_FAIL_INDEX_V(idx, properties.size(), false); + ReplicationProperty &prop = properties[idx]; + if (what == "sync") { + prop.sync = p_value; + sync_props.push_back(prop.name); + return true; + } else if (what == "spawn") { + prop.spawn = p_value; + spawn_props.push_back(prop.name); + return true; + } + } + return false; +} + +bool SceneReplicationConfig::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name; + + if (name.begins_with("properties/")) { + int idx = name.get_slicec('/', 1).to_int(); + String what = name.get_slicec('/', 2); + ERR_FAIL_INDEX_V(idx, properties.size(), false); + const ReplicationProperty &prop = properties[idx]; + if (what == "path") { + r_ret = prop.name; + return true; + } else if (what == "sync") { + r_ret = prop.sync; + return true; + } else if (what == "spawn") { + r_ret = prop.spawn; + return true; + } + } + return false; +} + +void SceneReplicationConfig::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < properties.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/spawn", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + } +} + +TypedArray<NodePath> SceneReplicationConfig::get_properties() const { + TypedArray<NodePath> paths; + for (const ReplicationProperty &prop : properties) { + paths.push_back(prop.name); + } + return paths; +} + +void SceneReplicationConfig::add_property(const NodePath &p_path, int p_index) { + ERR_FAIL_COND(properties.find(p_path)); + + if (p_index < 0 || p_index == properties.size()) { + properties.push_back(ReplicationProperty(p_path)); + return; + } + + ERR_FAIL_INDEX(p_index, properties.size()); + + List<ReplicationProperty>::Element *I = properties.front(); + int c = 0; + while (c < p_index) { + I = I->next(); + c++; + } + properties.insert_before(I, ReplicationProperty(p_path)); +} + +void SceneReplicationConfig::remove_property(const NodePath &p_path) { + properties.erase(p_path); +} + +int SceneReplicationConfig::property_get_index(const NodePath &p_path) const { + for (int i = 0; i < properties.size(); i++) { + if (properties[i].name == p_path) { + return i; + } + } + ERR_FAIL_V(-1); +} + +bool SceneReplicationConfig::property_get_spawn(const NodePath &p_path) { + List<ReplicationProperty>::Element *E = properties.find(p_path); + ERR_FAIL_COND_V(!E, false); + return E->get().spawn; +} + +void SceneReplicationConfig::property_set_spawn(const NodePath &p_path, bool p_enabled) { + List<ReplicationProperty>::Element *E = properties.find(p_path); + ERR_FAIL_COND(!E); + if (E->get().spawn == p_enabled) { + return; + } + E->get().spawn = p_enabled; + spawn_props.clear(); + for (const ReplicationProperty &prop : properties) { + if (prop.spawn) { + spawn_props.push_back(p_path); + } + } +} + +bool SceneReplicationConfig::property_get_sync(const NodePath &p_path) { + List<ReplicationProperty>::Element *E = properties.find(p_path); + ERR_FAIL_COND_V(!E, false); + return E->get().sync; +} + +void SceneReplicationConfig::property_set_sync(const NodePath &p_path, bool p_enabled) { + List<ReplicationProperty>::Element *E = properties.find(p_path); + ERR_FAIL_COND(!E); + if (E->get().sync == p_enabled) { + return; + } + E->get().sync = p_enabled; + sync_props.clear(); + for (const ReplicationProperty &prop : properties) { + if (prop.sync) { + sync_props.push_back(p_path); + } + } +} + +void SceneReplicationConfig::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_properties"), &SceneReplicationConfig::get_properties); + ClassDB::bind_method(D_METHOD("add_property", "path", "index"), &SceneReplicationConfig::add_property, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_property", "path"), &SceneReplicationConfig::remove_property); + ClassDB::bind_method(D_METHOD("property_get_index", "path"), &SceneReplicationConfig::property_get_index); + ClassDB::bind_method(D_METHOD("property_get_spawn", "path"), &SceneReplicationConfig::property_get_spawn); + ClassDB::bind_method(D_METHOD("property_set_spawn", "path", "enabled"), &SceneReplicationConfig::property_set_spawn); + ClassDB::bind_method(D_METHOD("property_get_sync", "path"), &SceneReplicationConfig::property_get_sync); + ClassDB::bind_method(D_METHOD("property_set_sync", "path", "enabled"), &SceneReplicationConfig::property_set_sync); +} diff --git a/scene/resources/scene_replication_config.h b/scene/resources/scene_replication_config.h new file mode 100644 index 0000000000..b791be9414 --- /dev/null +++ b/scene/resources/scene_replication_config.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* scene_replication_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_REPLICATION_CONFIG_H +#define SCENE_REPLICATION_CONFIG_H + +#include "core/io/resource.h" + +#include "core/variant/typed_array.h" + +class SceneReplicationConfig : public Resource { + GDCLASS(SceneReplicationConfig, Resource); + OBJ_SAVE_TYPE(SceneReplicationConfig); + RES_BASE_EXTENSION("repl"); + +private: + struct ReplicationProperty { + NodePath name; + bool spawn = true; + bool sync = true; + + bool operator==(const ReplicationProperty &p_to) { + return name == p_to.name; + } + + ReplicationProperty() {} + + ReplicationProperty(const NodePath &p_name) { + name = p_name; + } + }; + + List<ReplicationProperty> properties; + List<NodePath> spawn_props; + List<NodePath> sync_props; + +protected: + static void _bind_methods(); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + TypedArray<NodePath> get_properties() const; + + void add_property(const NodePath &p_path, int p_index = -1); + void remove_property(const NodePath &p_path); + + int property_get_index(const NodePath &p_path) const; + bool property_get_spawn(const NodePath &p_path); + void property_set_spawn(const NodePath &p_path, bool p_enabled); + + bool property_get_sync(const NodePath &p_path); + void property_set_sync(const NodePath &p_path, bool p_enabled); + + const List<NodePath> &get_spawn_properties() { return spawn_props; } + const List<NodePath> &get_sync_properties() { return sync_props; } + + SceneReplicationConfig() {} +}; + +#endif // SCENE_REPLICATION_CONFIG_H diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index e0aa21ac37..f1eddd8ffc 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -116,14 +116,6 @@ void SyntaxHighlighter::_bind_methods() { //////////////////////////////////////////////////////////////////////////////// -static bool _is_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -static bool _is_hex_symbol(char32_t c) { - return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; @@ -166,7 +158,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { color = font_color; bool is_char = !is_symbol(str[j]); bool is_a_symbol = is_symbol(str[j]); - bool is_number = (str[j] >= '0' && str[j] <= '9'); + bool is_number = is_digit(str[j]); /* color regions */ if (is_a_symbol || in_region != -1) { @@ -304,7 +296,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } // Allow ABCDEF in hex notation. - if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { + if (is_hex_notation && (is_hex_digit(str[j]) || is_number)) { is_number = true; } else { is_hex_notation = false; @@ -321,7 +313,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } } - if (!in_word && _is_char(str[j]) && !is_number) { + if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) { in_word = true; } diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index ee66a61da8..0ee0e4b33e 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1902,7 +1902,7 @@ void GradientTexture2D::_queue_update() { return; } update_pending = true; - call_deferred("_update"); + call_deferred(SNAME("_update")); } void GradientTexture2D::_update() { diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 2854cbe30d..1f77cc0570 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -4410,7 +4410,7 @@ void TileSetAtlasSource::_clear_tiles_outside_texture() { void TileSetAtlasSource::_queue_update_padded_texture() { padded_texture_needs_update = true; - call_deferred("_update_padded_texture"); + call_deferred(SNAME("_update_padded_texture")); } void TileSetAtlasSource::_update_padded_texture() { diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 0c7f069004..dae61c8609 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -131,7 +131,7 @@ void VisualShaderNode::set_input_port_default_value(int p_port, const Variant &p } break; case Variant::VECTOR2: { Vector2 pv = p_prev_value; - value = Vector3(pv.x, pv.y, 0.0); + value = Vector3(pv.x, pv.y, pv.y); } break; case Variant::VECTOR3: { value = p_prev_value; @@ -250,7 +250,7 @@ int VisualShaderNode::get_expanded_output_port_count() const { case PORT_TYPE_VECTOR_2D: { count2 += 2; } break; - case PORT_TYPE_VECTOR: { + case PORT_TYPE_VECTOR_3D: { count2 += 3; } break; default: @@ -289,7 +289,7 @@ String VisualShaderNode::generate_global(Shader::Mode p_mode, VisualShader::Type return String(); } -String VisualShaderNode::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String VisualShaderNode::generate_global_per_node(Shader::Mode p_mode, int p_id) const { return String(); } @@ -359,7 +359,7 @@ void VisualShaderNode::_bind_methods() { BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR); BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR_INT); BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR_2D); - BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR); + BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR_3D); BIND_ENUM_CONSTANT(PORT_TYPE_BOOLEAN); BIND_ENUM_CONSTANT(PORT_TYPE_TRANSFORM); BIND_ENUM_CONSTANT(PORT_TYPE_SAMPLER); @@ -472,7 +472,7 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: return code; } -String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, int p_id) const { String ret; if (GDVIRTUAL_CALL(_get_global_code, p_mode, ret)) { String code = "// " + get_caption() + "\n"; @@ -1088,7 +1088,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port case VisualShaderNode::PORT_TYPE_VECTOR_2D: { code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ", 0.0);\n"; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } break; default: { @@ -1106,10 +1106,6 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port return final_code; } -#define IS_INITIAL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z')) - -#define IS_SYMBOL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z') || ((m_d) >= '0' && (m_d) <= '9') || (m_d) == '_') - String VisualShader::validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const { String name = p_port_name; @@ -1117,7 +1113,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN return String(); } - while (name.length() && !IS_INITIAL_CHAR(name[0])) { + while (name.length() && !is_ascii_char(name[0])) { name = name.substr(1, name.length() - 1); } @@ -1125,7 +1121,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN String valid_name; for (int i = 0; i < name.length(); i++) { - if (IS_SYMBOL_CHAR(name[i])) { + if (is_ascii_identifier_char(name[i])) { valid_name += String::chr(name[i]); } else if (name[i] == ' ') { valid_name += "_"; @@ -1162,14 +1158,14 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN String VisualShader::validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const { String name = p_name; //validate name first - while (name.length() && !IS_INITIAL_CHAR(name[0])) { + while (name.length() && !is_ascii_char(name[0])) { name = name.substr(1, name.length() - 1); } if (!name.is_empty()) { String valid_name; for (int i = 0; i < name.length(); i++) { - if (IS_SYMBOL_CHAR(name[i])) { + if (is_ascii_identifier_char(name[i])) { valid_name += String::chr(name[i]); } else if (name[i] == ' ') { valid_name += "_"; @@ -1206,7 +1202,7 @@ String VisualShader::validate_uniform_name(const String &p_name, const Ref<Visua if (exists) { //remove numbers, put new and try again attempt++; - while (name.length() && name[name.length() - 1] >= '0' && name[name.length() - 1] <= '9') { + while (name.length() && is_digit(name[name.length() - 1])) { name = name.substr(0, name.length() - 1); } ERR_FAIL_COND_V(name.is_empty(), String()); @@ -1489,7 +1485,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui class_name = vsnode->get_script_instance()->get_script()->get_path(); } if (!r_classes.has(class_name)) { - global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node); + global_code_per_node += vsnode->generate_global_per_node(get_mode(), node); for (int i = 0; i < TYPE_MAX; i++) { global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node); } @@ -1553,7 +1549,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { inputs[i] = "dot(" + src_var + ", vec2(0.333333, 0.333333))"; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "dot(" + src_var + ", vec3(0.333333, 0.333333, 0.333333))"; } break; default: @@ -1571,7 +1567,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { inputs[i] = "dot(float(" + src_var + "), vec2(0.333333, 0.333333))"; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "dot(float(" + src_var + "), vec3(0.333333, 0.333333, 0.333333))"; } break; default: @@ -1589,7 +1585,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { inputs[i] = "all(bvec2(" + src_var + "))"; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "all(bvec3(" + src_var + "))"; } break; default: @@ -1607,7 +1603,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_BOOLEAN: { inputs[i] = "vec2(" + src_var + " ? 1.0 : 0.0)"; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { inputs[i] = "vec2(" + src_var + ".xy)"; } break; default: @@ -1615,7 +1611,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui } } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { switch (out_type) { case VisualShaderNode::PORT_TYPE_SCALAR: { inputs[i] = "vec3(" + src_var + ")"; @@ -1699,7 +1695,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { output_count += 2; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { output_count += 3; } break; default: @@ -1726,7 +1722,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: outputs[i] = "vec2 " + var_name; break; - case VisualShaderNode::PORT_TYPE_VECTOR: + case VisualShaderNode::PORT_TYPE_VECTOR_3D: outputs[i] = "vec3 " + var_name; break; case VisualShaderNode::PORT_TYPE_BOOLEAN: @@ -1743,7 +1739,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { j += 2; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { j += 3; } break; default: @@ -1765,7 +1761,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: code += " vec2 " + outputs[i] + ";\n"; break; - case VisualShaderNode::PORT_TYPE_VECTOR: + case VisualShaderNode::PORT_TYPE_VECTOR_3D: code += " vec3 " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_BOOLEAN: @@ -1782,7 +1778,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui case VisualShaderNode::PORT_TYPE_VECTOR_2D: { j += 2; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { j += 3; } break; default: @@ -1826,7 +1822,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui i += 2; } break; - case VisualShaderNode::PORT_TYPE_VECTOR: { + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component if (!new_line_inserted) { code += "\n"; @@ -2150,7 +2146,11 @@ void VisualShader::_update_shader() const { global_compute_code += " return __rand_from_seed(seed) * (to - from) + from;\n"; global_compute_code += "}\n\n"; - global_compute_code += "vec3 __randv_range(inout uint seed, vec3 from, vec3 to) {\n"; + global_compute_code += "vec2 __randv2_range(inout uint seed, vec2 from, vec2 to) {\n"; + global_compute_code += " return vec2(__randf_range(seed, from.x, to.x), __randf_range(seed, from.y, to.y));\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "vec3 __randv3_range(inout uint seed, vec3 from, vec3 to) {\n"; global_compute_code += " return vec3(__randf_range(seed, from.x, to.x), __randf_range(seed, from.y, to.y), __randf_range(seed, from.z, to.z));\n"; global_compute_code += "}\n\n"; @@ -2326,17 +2326,17 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Node3D // Node3D, Vertex - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "instance_id", "INSTANCE_ID" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "instance_custom", "INSTANCE_CUSTOM.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "instance_custom", "INSTANCE_CUSTOM.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "instance_custom_alpha", "INSTANCE_CUSTOM.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, @@ -2350,15 +2350,15 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, // Node3D, Fragment - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, @@ -2376,18 +2376,18 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "depth_texture", "DEPTH_TEXTURE" }, // Node3D, Light - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light", "LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_color", "LIGHT_COLOR" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "attenuation", "ATTENUATION" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "backlight", "BACKLIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "diffuse", "DIFFUSE_LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular", "SPECULAR_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "backlight", "BACKLIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "diffuse", "DIFFUSE_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular", "SPECULAR_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "metallic", "METALLIC" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, @@ -2404,7 +2404,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, @@ -2413,13 +2413,13 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen", "SCREEN_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "instance_custom", "INSTANCE_CUSTOM.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "instance_custom", "INSTANCE_CUSTOM.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "instance_custom_alpha", "INSTANCE_CUSTOM.a" }, // Canvas Item, Fragment - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, @@ -2430,41 +2430,41 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "normal_texture", "NORMAL_TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "screen_texture", "SCREEN_TEXTURE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular_shininess", "SPECULAR_SHININESS.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, // Canvas Item, Light - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light", "LIGHT.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_color", "LIGHT_COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_color_alpha", "LIGHT_COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_position", "LIGHT_POSITION" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vertex", "LIGHT_VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow", "SHADOW_MODULATE.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_position", "LIGHT_POSITION" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_vertex", "LIGHT_VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "shadow", "SHADOW_MODULATE.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "shadow_alpha", "SHADOW_MODULATE.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular_shininess", "SPECULAR_SHININESS.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, // Particles, Start - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, @@ -2474,13 +2474,13 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles, Start (Custom) - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, @@ -2490,13 +2490,13 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles, Process - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, @@ -2506,13 +2506,13 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles, Process (Custom) - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, @@ -2522,15 +2522,15 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles, Collide - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "collision_depth", "COLLISION_DEPTH" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "collision_normal", "COLLISION_NORMAL" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "collision_normal", "COLLISION_NORMAL" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "custom", "CUSTOM.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, @@ -2543,27 +2543,27 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_cubemap_pass", "AT_CUBEMAP_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_half_res_pass", "AT_HALF_RES_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_quarter_res_pass", "AT_QUARTER_RES_PASS" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "eyedir", "EYEDIR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "half_res_color", "HALF_RES_COLOR.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eyedir", "EYEDIR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "half_res_color", "HALF_RES_COLOR.rgb" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "half_res_alpha", "HALF_RES_COLOR.a" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light0_color", "LIGHT0_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light0_direction", "LIGHT0_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light0_color", "LIGHT0_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light0_direction", "LIGHT0_DIRECTION" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light0_enabled", "LIGHT0_ENABLED" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light0_energy", "LIGHT0_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light1_color", "LIGHT1_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light1_direction", "LIGHT1_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light1_color", "LIGHT1_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light1_direction", "LIGHT1_DIRECTION" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light1_enabled", "LIGHT1_ENABLED" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light1_energy", "LIGHT1_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light2_color", "LIGHT2_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light2_direction", "LIGHT2_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light2_color", "LIGHT2_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light2_direction", "LIGHT2_DIRECTION" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light2_enabled", "LIGHT2_ENABLED" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light2_energy", "LIGHT2_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light3_color", "LIGHT3_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light3_direction", "LIGHT3_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light3_color", "LIGHT3_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light3_direction", "LIGHT3_DIRECTION" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light3_enabled", "LIGHT3_ENABLED" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light3_energy", "LIGHT3_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "position", "POSITION" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "quarter_res_color", "QUARTER_RES_COLOR.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "position", "POSITION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "quarter_res_color", "QUARTER_RES_COLOR.rgb" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "quarter_res_alpha", "QUARTER_RES_COLOR.a" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SAMPLER, "radiance", "RADIANCE" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, @@ -2572,10 +2572,10 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { // Fog, Fog - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "world_position", "WORLD_POSITION" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "object_position", "OBJECT_POSITION" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "uvw", "UVW" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "extents", "EXTENTS" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "world_position", "WORLD_POSITION" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "object_position", "OBJECT_POSITION" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "uvw", "UVW" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "extents", "EXTENTS" }, { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "sdf", "SDF" }, { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, @@ -2585,25 +2585,25 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { // Spatial, Vertex - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "vec3(0.0, 1.0, 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "vec3(1.0, 0.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "vec3(0.0, 1.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Spatial, Fragment - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "vec3(0.0, 1.0, 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "vec3(1.0, 0.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "vec3(0.0, 1.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, @@ -2611,8 +2611,8 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { // Spatial, Light - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, @@ -2622,25 +2622,25 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Fragment - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Light - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fragcoord", "FRAGCOORD.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0, 0.0, 1.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, @@ -2722,7 +2722,7 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T case PORT_TYPE_VECTOR_2D: { code = " " + p_output_vars[0] + " = vec2(0.0);\n"; } break; - case PORT_TYPE_VECTOR: { + case PORT_TYPE_VECTOR_3D: { code = " " + p_output_vars[0] + " = vec3(0.0);\n"; } break; case PORT_TYPE_BOOLEAN: { @@ -2964,12 +2964,12 @@ VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_output_port case UniformType::UNIFORM_TYPE_VECTOR2: return PortType::PORT_TYPE_VECTOR_2D; case UniformType::UNIFORM_TYPE_VECTOR3: - return PortType::PORT_TYPE_VECTOR; + return PortType::PORT_TYPE_VECTOR_3D; case UniformType::UNIFORM_TYPE_TRANSFORM: return PortType::PORT_TYPE_TRANSFORM; case UniformType::UNIFORM_TYPE_COLOR: if (p_port == 0) { - return PortType::PORT_TYPE_VECTOR; + return PortType::PORT_TYPE_VECTOR_3D; } else if (p_port == 1) { return PORT_TYPE_SCALAR; } @@ -3065,11 +3065,11 @@ VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_port_type_b case UniformType::UNIFORM_TYPE_VECTOR2: return PORT_TYPE_VECTOR_2D; case UniformType::UNIFORM_TYPE_VECTOR3: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case UniformType::UNIFORM_TYPE_TRANSFORM: return PORT_TYPE_TRANSFORM; case UniformType::UNIFORM_TYPE_COLOR: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3135,29 +3135,29 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { //////////////////////////////////////////////////////////////////////// // Node3D, Vertex. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_view_matrix", "MODELVIEW_MATRIX" }, //////////////////////////////////////////////////////////////////////// // Node3D, Fragment. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "metallic", "METALLIC" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular", "SPECULAR" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "emission", "EMISSION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "emission", "EMISSION" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "ao", "AO" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal_map", "NORMAL_MAP" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal_map", "NORMAL_MAP" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normal_map_depth", "NORMAL_MAP_DEPTH" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "rim", "RIM" }, @@ -3167,15 +3167,15 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "anisotropy", "ANISOTROPY" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "anisotropy_flow", "ANISOTROPY_FLOW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "subsurf_scatter", "SSS_STRENGTH" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "backlight", "BACKLIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "backlight", "BACKLIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha_scissor_threshold", "ALPHA_SCISSOR_THRESHOLD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "ao_light_affect", "AO_LIGHT_AFFECT" }, //////////////////////////////////////////////////////////////////////// // Node3D, Light. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "diffuse", "DIFFUSE_LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular", "SPECULAR_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "diffuse", "DIFFUSE_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular", "SPECULAR_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, //////////////////////////////////////////////////////////////////////// @@ -3185,39 +3185,39 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { //////////////////////////////////////////////////////////////////////// { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, //////////////////////////////////////////////////////////////////////// // Canvas Item, Fragment. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal_map", "NORMAL_MAP" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal_map", "NORMAL_MAP" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normal_map_depth", "NORMAL_MAP_DEPTH" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vertex", "LIGHT_VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_vertex", "LIGHT_VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "shadow_vertex", "SHADOW_VERTEX" }, //////////////////////////////////////////////////////////////////////// // Canvas Item, Light. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light", "LIGHT.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, //////////////////////////////////////////////////////////////////////// // Sky, Sky. //////////////////////////////////////////////////////////////////////// - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "color", "COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "fog", "FOG.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "fog", "FOG.rgb" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "fog_alpha", "FOG.a" }, //////////////////////////////////////////////////////////////////////// // Fog, Fog. //////////////////////////////////////////////////////////////////////// { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "density", "DENSITY" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR, "emission", "EMISSION" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "emission", "EMISSION" }, //////////////////////////////////////////////////////////////////////// { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, @@ -4151,7 +4151,7 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad case PORT_TYPE_VECTOR_2D: tk = "vec2(0.0, 0.0)"; break; - case PORT_TYPE_VECTOR: + case PORT_TYPE_VECTOR_3D: tk = "vec3(0.0, 0.0, 0.0)"; break; case PORT_TYPE_BOOLEAN: diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 9f877e08a5..d3b5365893 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -215,7 +215,7 @@ public: PORT_TYPE_SCALAR, PORT_TYPE_SCALAR_INT, PORT_TYPE_VECTOR_2D, - PORT_TYPE_VECTOR, + PORT_TYPE_VECTOR_3D, PORT_TYPE_BOOLEAN, PORT_TYPE_TRANSFORM, PORT_TYPE_SAMPLER, @@ -275,7 +275,7 @@ public: virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; - virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const; virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; // If no output is connected, the output var passed will be empty. If no input is connected and input is NIL, the input var passed will be empty. virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const = 0; @@ -335,7 +335,7 @@ protected: void _set_input_port_default_value(int p_port, const Variant &p_value); virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; - virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const override; static void _bind_methods(); diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 9a714fd3dd..f2479199ee 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -37,7 +37,7 @@ VisualShaderNodeVectorBase::PortType VisualShaderNodeVectorBase::get_input_port_ case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -49,7 +49,7 @@ VisualShaderNodeVectorBase::PortType VisualShaderNodeVectorBase::get_output_port case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -291,7 +291,7 @@ int VisualShaderNodeColorConstant::get_input_port_count() const { } VisualShaderNodeColorConstant::PortType VisualShaderNodeColorConstant::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeColorConstant::get_input_port_name(int p_port) const { @@ -303,7 +303,7 @@ int VisualShaderNodeColorConstant::get_output_port_count() const { } VisualShaderNodeColorConstant::PortType VisualShaderNodeColorConstant::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; } String VisualShaderNodeColorConstant::get_output_port_name(int p_port) const { @@ -426,7 +426,7 @@ int VisualShaderNodeVec3Constant::get_input_port_count() const { } VisualShaderNodeVec3Constant::PortType VisualShaderNodeVec3Constant::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeVec3Constant::get_input_port_name(int p_port) const { @@ -438,7 +438,7 @@ int VisualShaderNodeVec3Constant::get_output_port_count() const { } VisualShaderNodeVec3Constant::PortType VisualShaderNodeVec3Constant::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeVec3Constant::get_output_port_name(int p_port) const { @@ -488,7 +488,7 @@ int VisualShaderNodeTransformConstant::get_input_port_count() const { } VisualShaderNodeTransformConstant::PortType VisualShaderNodeTransformConstant::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeTransformConstant::get_input_port_name(int p_port) const { @@ -591,7 +591,7 @@ VisualShaderNodeTexture::PortType VisualShaderNodeTexture::get_output_port_type( if (p_port == 0 && source == SOURCE_DEPTH) { return PORT_TYPE_SCALAR; } - return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; } String VisualShaderNodeTexture::get_output_port_name(int p_port) const { @@ -1064,7 +1064,7 @@ int VisualShaderNodeCurveXYZTexture::get_output_port_count() const { } VisualShaderNodeCurveXYZTexture::PortType VisualShaderNodeCurveXYZTexture::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeCurveXYZTexture::get_output_port_name(int p_port) const { @@ -1135,7 +1135,7 @@ int VisualShaderNodeSample3D::get_input_port_count() const { VisualShaderNodeSample3D::PortType VisualShaderNodeSample3D::get_input_port_type(int p_port) const { switch (p_port) { case 0: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 1: return PORT_TYPE_SCALAR; case 2: @@ -1161,7 +1161,7 @@ int VisualShaderNodeSample3D::get_output_port_count() const { } VisualShaderNodeSample3D::PortType VisualShaderNodeSample3D::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; } String VisualShaderNodeSample3D::get_output_port_name(int p_port) const { @@ -1398,7 +1398,7 @@ int VisualShaderNodeCubemap::get_input_port_count() const { VisualShaderNodeCubemap::PortType VisualShaderNodeCubemap::get_input_port_type(int p_port) const { switch (p_port) { case 0: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 1: return PORT_TYPE_SCALAR; case 2: @@ -1426,7 +1426,7 @@ int VisualShaderNodeCubemap::get_output_port_count() const { } VisualShaderNodeCubemap::PortType VisualShaderNodeCubemap::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; } String VisualShaderNodeCubemap::get_output_port_name(int p_port) const { @@ -2020,7 +2020,7 @@ int VisualShaderNodeColorOp::get_input_port_count() const { } VisualShaderNodeColorOp::PortType VisualShaderNodeColorOp::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeColorOp::get_input_port_name(int p_port) const { @@ -2032,7 +2032,7 @@ int VisualShaderNodeColorOp::get_output_port_count() const { } VisualShaderNodeColorOp::PortType VisualShaderNodeColorOp::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeColorOp::get_output_port_name(int p_port) const { @@ -2293,7 +2293,7 @@ int VisualShaderNodeTransformVecMult::get_input_port_count() const { } VisualShaderNodeTransformVecMult::PortType VisualShaderNodeTransformVecMult::get_input_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_TRANSFORM : PORT_TYPE_VECTOR; + return p_port == 0 ? PORT_TYPE_TRANSFORM : PORT_TYPE_VECTOR_3D; } String VisualShaderNodeTransformVecMult::get_input_port_name(int p_port) const { @@ -2305,7 +2305,7 @@ int VisualShaderNodeTransformVecMult::get_output_port_count() const { } VisualShaderNodeTransformVecMult::PortType VisualShaderNodeTransformVecMult::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeTransformVecMult::get_output_port_name(int p_port) const { @@ -2815,7 +2815,7 @@ int VisualShaderNodeColorFunc::get_input_port_count() const { } VisualShaderNodeColorFunc::PortType VisualShaderNodeColorFunc::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeColorFunc::get_input_port_name(int p_port) const { @@ -2827,7 +2827,7 @@ int VisualShaderNodeColorFunc::get_output_port_count() const { } VisualShaderNodeColorFunc::PortType VisualShaderNodeColorFunc::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeColorFunc::get_output_port_name(int p_port) const { @@ -3124,7 +3124,7 @@ int VisualShaderNodeDotProduct::get_input_port_count() const { } VisualShaderNodeDotProduct::PortType VisualShaderNodeDotProduct::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeDotProduct::get_input_port_name(int p_port) const { @@ -3258,7 +3258,7 @@ VisualShaderNodeDerivativeFunc::PortType VisualShaderNodeDerivativeFunc::get_inp case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3278,7 +3278,7 @@ VisualShaderNodeDerivativeFunc::PortType VisualShaderNodeDerivativeFunc::get_out case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3389,7 +3389,7 @@ VisualShaderNodeClamp::PortType VisualShaderNodeClamp::get_input_port_type(int p case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3418,7 +3418,7 @@ VisualShaderNodeClamp::PortType VisualShaderNodeClamp::get_output_port_type(int case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3570,7 +3570,7 @@ int VisualShaderNodeOuterProduct::get_input_port_count() const { } VisualShaderNodeOuterProduct::PortType VisualShaderNodeOuterProduct::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeOuterProduct::get_input_port_name(int p_port) const { @@ -3625,10 +3625,10 @@ VisualShaderNodeStep::PortType VisualShaderNodeStep::get_input_port_type(int p_p } break; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: if (p_port == 1) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } break; default: @@ -3658,9 +3658,9 @@ VisualShaderNodeStep::PortType VisualShaderNodeStep::get_output_port_type(int p_ case OP_TYPE_VECTOR_2D_SCALAR: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3757,10 +3757,10 @@ VisualShaderNodeSmoothStep::PortType VisualShaderNodeSmoothStep::get_input_port_ } break; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: if (p_port == 2) { - return PORT_TYPE_VECTOR; // x + return PORT_TYPE_VECTOR_3D; // x } break; default: @@ -3792,9 +3792,9 @@ VisualShaderNodeSmoothStep::PortType VisualShaderNodeSmoothStep::get_output_port case OP_TYPE_VECTOR_2D_SCALAR: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -3954,7 +3954,7 @@ VisualShaderNodeVectorRefract::PortType VisualShaderNodeVectorRefract::get_input return PORT_TYPE_SCALAR; } - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeVectorRefract::get_input_port_name(int p_port) const { @@ -3974,7 +3974,7 @@ int VisualShaderNodeVectorRefract::get_output_port_count() const { } VisualShaderNodeVectorRefract::PortType VisualShaderNodeVectorRefract::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeVectorRefract::get_output_port_name(int p_port) const { @@ -4011,12 +4011,12 @@ VisualShaderNodeMix::PortType VisualShaderNodeMix::get_input_port_type(int p_por } return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: if (p_port == 2) { break; } - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -4044,9 +4044,9 @@ VisualShaderNodeMix::PortType VisualShaderNodeMix::get_output_port_type(int p_po case OP_TYPE_VECTOR_2D_SCALAR: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_VECTOR_3D_SCALAR: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -4245,7 +4245,7 @@ int VisualShaderNodeTransformCompose::get_input_port_count() const { } VisualShaderNodeTransformCompose::PortType VisualShaderNodeTransformCompose::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeTransformCompose::get_input_port_name(int p_port) const { @@ -4402,7 +4402,7 @@ int VisualShaderNodeTransformDecompose::get_output_port_count() const { } VisualShaderNodeTransformDecompose::PortType VisualShaderNodeTransformDecompose::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeTransformDecompose::get_output_port_name(int p_port) const { @@ -4933,7 +4933,7 @@ int VisualShaderNodeColorUniform::get_input_port_count() const { } VisualShaderNodeColorUniform::PortType VisualShaderNodeColorUniform::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeColorUniform::get_input_port_name(int p_port) const { @@ -4945,7 +4945,7 @@ int VisualShaderNodeColorUniform::get_output_port_count() const { } VisualShaderNodeColorUniform::PortType VisualShaderNodeColorUniform::get_output_port_type(int p_port) const { - return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; + return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR; } String VisualShaderNodeColorUniform::get_output_port_name(int p_port) const { @@ -5137,7 +5137,7 @@ int VisualShaderNodeVec3Uniform::get_input_port_count() const { } VisualShaderNodeVec3Uniform::PortType VisualShaderNodeVec3Uniform::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeVec3Uniform::get_input_port_name(int p_port) const { @@ -5149,7 +5149,7 @@ int VisualShaderNodeVec3Uniform::get_output_port_count() const { } VisualShaderNodeVec3Uniform::PortType VisualShaderNodeVec3Uniform::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeVec3Uniform::get_output_port_name(int p_port) const { @@ -5237,7 +5237,7 @@ int VisualShaderNodeTransformUniform::get_input_port_count() const { } VisualShaderNodeTransformUniform::PortType VisualShaderNodeTransformUniform::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeTransformUniform::get_input_port_name(int p_port) const { @@ -5358,7 +5358,7 @@ int VisualShaderNodeTextureUniform::get_output_port_count() const { VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniform::get_output_port_type(int p_port) const { switch (p_port) { case 0: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 1: return PORT_TYPE_SCALAR; case 2: @@ -5680,7 +5680,7 @@ int VisualShaderNodeTextureUniformTriplanar::get_input_port_count() const { VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniformTriplanar::get_input_port_type(int p_port) const { if (p_port == 0 || p_port == 1) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } return PORT_TYPE_SCALAR; } @@ -5694,7 +5694,7 @@ String VisualShaderNodeTextureUniformTriplanar::get_input_port_name(int p_port) return ""; } -String VisualShaderNodeTextureUniformTriplanar::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String VisualShaderNodeTextureUniformTriplanar::generate_global_per_node(Shader::Mode p_mode, int p_id) const { String code; code += "// TRIPLANAR FUNCTION GLOBAL CODE\n"; @@ -5996,7 +5996,7 @@ VisualShaderNodeIf::PortType VisualShaderNodeIf::get_input_port_type(int p_port) if (p_port == 0 || p_port == 1 || p_port == 2) { return PORT_TYPE_SCALAR; } - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeIf::get_input_port_name(int p_port) const { @@ -6023,7 +6023,7 @@ int VisualShaderNodeIf::get_output_port_count() const { } VisualShaderNodeIf::PortType VisualShaderNodeIf::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeIf::get_output_port_name(int p_port) const { @@ -6078,7 +6078,7 @@ VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_input_port_type(int case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_BOOLEAN: return PORT_TYPE_BOOLEAN; case OP_TYPE_TRANSFORM: @@ -6114,7 +6114,7 @@ VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_output_port_type(in case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case OP_TYPE_BOOLEAN: return PORT_TYPE_BOOLEAN; case OP_TYPE_TRANSFORM: @@ -6224,15 +6224,15 @@ int VisualShaderNodeFresnel::get_input_port_count() const { VisualShaderNodeFresnel::PortType VisualShaderNodeFresnel::get_input_port_type(int p_port) const { switch (p_port) { case 0: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 1: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 2: return PORT_TYPE_BOOLEAN; case 3: return PORT_TYPE_SCALAR; default: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } } @@ -6418,7 +6418,7 @@ VisualShaderNodeCompare::PortType VisualShaderNodeCompare::get_input_port_type(i case CTYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case CTYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case CTYPE_BOOLEAN: return PORT_TYPE_BOOLEAN; case CTYPE_TRANSFORM: @@ -6670,7 +6670,7 @@ VisualShaderNodeMultiplyAdd::PortType VisualShaderNodeMultiplyAdd::get_input_por case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } @@ -6697,7 +6697,7 @@ VisualShaderNodeMultiplyAdd::PortType VisualShaderNodeMultiplyAdd::get_output_po case OP_TYPE_VECTOR_2D: return PORT_TYPE_VECTOR_2D; case OP_TYPE_VECTOR_3D: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; default: break; } diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 5c7a674d34..eeeb91a3ee 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -2136,7 +2136,7 @@ public: virtual bool is_input_port_default(int p_port, Shader::Mode p_mode) const override; - virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const override; virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index fbac92a06d..1885211d57 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -39,7 +39,10 @@ int VisualShaderNodeParticleEmitter::get_output_port_count() const { } VisualShaderNodeParticleEmitter::PortType VisualShaderNodeParticleEmitter::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + if (mode_2d) { + return PORT_TYPE_VECTOR_2D; + } + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeParticleEmitter::get_output_port_name(int p_port) const { @@ -54,6 +57,9 @@ bool VisualShaderNodeParticleEmitter::has_output_port_preview(int p_port) const } void VisualShaderNodeParticleEmitter::set_mode_2d(bool p_enabled) { + if (mode_2d == p_enabled) { + return; + } mode_2d = p_enabled; emit_changed(); } @@ -111,7 +117,7 @@ String VisualShaderNodeParticleSphereEmitter::get_input_port_name(int p_port) co return String(); } -String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, int p_id) const { String code; code += "vec2 __get_random_point_in_circle(inout uint seed, float radius, float inner_radius) {\n"; @@ -129,7 +135,7 @@ String VisualShaderNodeParticleSphereEmitter::generate_code(Shader::Mode p_mode, String code; if (mode_2d) { - code += " " + p_output_vars[0] + " = vec3(__get_random_point_in_circle(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + "), 0.0);\n"; + code += " " + p_output_vars[0] + " = __get_random_point_in_circle(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; } else { code += " " + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; } @@ -154,11 +160,27 @@ int VisualShaderNodeParticleBoxEmitter::get_input_port_count() const { VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleBoxEmitter::get_input_port_type(int p_port) const { if (p_port == 0) { - return PORT_TYPE_VECTOR; + if (mode_2d) { + return PORT_TYPE_VECTOR_2D; + } + return PORT_TYPE_VECTOR_3D; } return PORT_TYPE_SCALAR; } +void VisualShaderNodeParticleBoxEmitter::set_mode_2d(bool p_enabled) { + if (mode_2d == p_enabled) { + return; + } + if (p_enabled) { + set_input_port_default_value(0, Vector2(), get_input_port_default_value(0)); + } else { + set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); + } + mode_2d = p_enabled; + emit_changed(); +} + String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const { if (p_port == 0) { return "extents"; @@ -166,7 +188,7 @@ String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const return String(); } -String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, int p_id) const { String code; code += "vec2 __get_random_point_in_box2d(inout uint seed, vec2 extents) {\n"; @@ -185,7 +207,7 @@ String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode String VisualShaderNodeParticleBoxEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; if (mode_2d) { - code += " " + p_output_vars[0] + " = vec3(__get_random_point_in_box2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ".xy), 0.0);\n"; + code += " " + p_output_vars[0] + " = __get_random_point_in_box2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; } else { code += " " + p_output_vars[0] + " = __get_random_point_in_box3d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; } @@ -221,7 +243,7 @@ String VisualShaderNodeParticleRingEmitter::get_input_port_name(int p_port) cons return String(); } -String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { +String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, int p_id) const { String code; code += "vec2 __get_random_point_on_ring2d(inout uint seed, float radius, float inner_radius) {\n"; @@ -243,7 +265,7 @@ String VisualShaderNodeParticleRingEmitter::generate_code(Shader::Mode p_mode, V String code; if (mode_2d) { - code = " " + p_output_vars[0] + " = vec3(__get_random_point_on_ring2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + "), 0.0);\n"; + code = " " + p_output_vars[0] + " = __get_random_point_on_ring2d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; } else { code = " " + p_output_vars[0] + " = __get_random_point_on_ring3d(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n"; } @@ -269,18 +291,24 @@ int VisualShaderNodeParticleMeshEmitter::get_output_port_count() const { VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_output_port_type(int p_port) const { switch (p_port) { - case 0: - return PORT_TYPE_VECTOR; // position - case 1: - return PORT_TYPE_VECTOR; // normal - case 2: - return PORT_TYPE_VECTOR; // color - case 3: - return PORT_TYPE_SCALAR; // alpha - case 4: - return PORT_TYPE_VECTOR; // uv - case 5: - return PORT_TYPE_VECTOR; // uv2 + case 0: // position + if (mode_2d) { + return PORT_TYPE_VECTOR_2D; + } + return PORT_TYPE_VECTOR_3D; + case 1: // normal + if (mode_2d) { + return PORT_TYPE_VECTOR_2D; + } + return PORT_TYPE_VECTOR_3D; + case 2: // color + return PORT_TYPE_VECTOR_3D; + case 3: // alpha + return PORT_TYPE_SCALAR; + case 4: // uv + return PORT_TYPE_VECTOR_2D; + case 5: // uv2 + return PORT_TYPE_VECTOR_2D; } return PORT_TYPE_SCALAR; } @@ -341,18 +369,22 @@ String VisualShaderNodeParticleMeshEmitter::generate_global(Shader::Mode p_mode, return code; } -String VisualShaderNodeParticleMeshEmitter::_generate_code(VisualShader::Type p_type, int p_id, const String *p_output_vars, int p_index, const String &p_texture_name, bool p_ignore_mode2d) const { +String VisualShaderNodeParticleMeshEmitter::_generate_code(VisualShader::Type p_type, int p_id, const String *p_output_vars, int p_index, const String &p_texture_name, PortType p_port_type) const { String code; if (is_output_port_connected(p_index)) { - if (mode_2d && !p_ignore_mode2d) { - code += " " + p_output_vars[p_index] + " = vec3("; - code += "texelFetch("; - code += make_unique_id(p_type, p_id, p_texture_name) + ", "; - code += "ivec2(__scalar_ibuff, 0), 0).xy, 0.0);\n"; - } else { - code += " " + p_output_vars[p_index] + " = texelFetch("; - code += make_unique_id(p_type, p_id, p_texture_name) + ", "; - code += "ivec2(__scalar_ibuff, 0), 0).xyz;\n"; + switch (p_port_type) { + case PORT_TYPE_VECTOR_2D: { + code += vformat(" %s = texelFetch(%s, ivec2(__scalar_ibuff, 0), 0).xy;\n", p_output_vars[p_index], make_unique_id(p_type, p_id, p_texture_name)); + } break; + case PORT_TYPE_VECTOR_3D: { + if (mode_2d) { + code += vformat(" %s = texelFetch(%s, ivec2(__scalar_ibuff, 0), 0).xy;\n", p_output_vars[p_index], make_unique_id(p_type, p_id, p_texture_name)); + } else { + code += vformat(" %s = texelFetch(%s, ivec2(__scalar_ibuff, 0), 0).xyz;\n", p_output_vars[p_index], make_unique_id(p_type, p_id, p_texture_name)); + } + } break; + default: + break; } } return code; @@ -362,27 +394,22 @@ String VisualShaderNodeParticleMeshEmitter::generate_code(Shader::Mode p_mode, V String code; code += " __scalar_ibuff = int(__rand_from_seed(__seed) * 65535.0) % " + itos(position_texture->get_width()) + ";\n"; - code += _generate_code(p_type, p_id, p_output_vars, 0, "mesh_vx"); - code += _generate_code(p_type, p_id, p_output_vars, 1, "mesh_nm"); + code += _generate_code(p_type, p_id, p_output_vars, 0, "mesh_vx", VisualShaderNode::PORT_TYPE_VECTOR_3D); + code += _generate_code(p_type, p_id, p_output_vars, 1, "mesh_nm", VisualShaderNode::PORT_TYPE_VECTOR_3D); if (is_output_port_connected(2) || is_output_port_connected(3)) { - code += " __vec4_buff = texelFetch("; - code += make_unique_id(p_type, p_id, "mesh_col") + ", "; - code += "ivec2(__scalar_ibuff, 0), 0);\n"; + code += vformat(" __vec4_buff = texelFetch(%s, ivec2(__scalar_ibuff, 0), 0);\n", make_unique_id(p_type, p_id, "mesh_col")); + if (is_output_port_connected(2)) { code += " " + p_output_vars[2] + " = __vec4_buff.rgb;\n"; - } else { - code += " " + p_output_vars[2] + " = vec3(0.0);\n"; } if (is_output_port_connected(3)) { code += " " + p_output_vars[3] + " = __vec4_buff.a;\n"; - } else { - code += " " + p_output_vars[3] + " = 0.0;\n"; } } - code += _generate_code(p_type, p_id, p_output_vars, 4, "mesh_uv", true); - code += _generate_code(p_type, p_id, p_output_vars, 5, "mesh_uv2", true); + code += _generate_code(p_type, p_id, p_output_vars, 4, "mesh_uv", VisualShaderNode::PORT_TYPE_VECTOR_2D); + code += _generate_code(p_type, p_id, p_output_vars, 5, "mesh_uv2", VisualShaderNode::PORT_TYPE_VECTOR_2D); return code; } @@ -731,7 +758,7 @@ int VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_count() const { VisualShaderNodeParticleMultiplyByAxisAngle::PortType VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_type(int p_port) const { if (p_port == 0 || p_port == 1) { // position, rotation_axis - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } return PORT_TYPE_SCALAR; // angle (degrees/radians) } @@ -762,7 +789,7 @@ int VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_count() const { } VisualShaderNodeParticleMultiplyByAxisAngle::PortType VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_name(int p_port) const { @@ -815,7 +842,7 @@ int VisualShaderNodeParticleConeVelocity::get_input_port_count() const { VisualShaderNodeParticleConeVelocity::PortType VisualShaderNodeParticleConeVelocity::get_input_port_type(int p_port) const { if (p_port == 0) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } else if (p_port == 1) { return PORT_TYPE_SCALAR; } @@ -836,7 +863,7 @@ int VisualShaderNodeParticleConeVelocity::get_output_port_count() const { } VisualShaderNodeParticleConeVelocity::PortType VisualShaderNodeParticleConeVelocity::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeParticleConeVelocity::get_output_port_name(int p_port) const { @@ -876,10 +903,11 @@ void VisualShaderNodeParticleRandomness::_bind_methods() { ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeParticleRandomness::set_op_type); ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeParticleRandomness::get_op_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector"), "set_op_type", "get_op_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector3"), "set_op_type", "get_op_type"); BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); - BIND_ENUM_CONSTANT(OP_TYPE_VECTOR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); BIND_ENUM_CONSTANT(OP_TYPE_MAX); } @@ -898,8 +926,13 @@ int VisualShaderNodeParticleRandomness::get_output_port_count() const { } VisualShaderNodeParticleRandomness::PortType VisualShaderNodeParticleRandomness::get_output_port_type(int p_port) const { - if (op_type == OP_TYPE_VECTOR) { - return PORT_TYPE_VECTOR; + switch (op_type) { + case OP_TYPE_VECTOR_2D: + return PORT_TYPE_VECTOR_2D; + case OP_TYPE_VECTOR_3D: + return PORT_TYPE_VECTOR_3D; + default: + break; } return PORT_TYPE_SCALAR; } @@ -913,8 +946,13 @@ int VisualShaderNodeParticleRandomness::get_input_port_count() const { } VisualShaderNodeParticleRandomness::PortType VisualShaderNodeParticleRandomness::get_input_port_type(int p_port) const { - if (op_type == OP_TYPE_VECTOR) { - return PORT_TYPE_VECTOR; + switch (op_type) { + case OP_TYPE_VECTOR_2D: + return PORT_TYPE_VECTOR_2D; + case OP_TYPE_VECTOR_3D: + return PORT_TYPE_VECTOR_3D; + default: + break; } return PORT_TYPE_SCALAR; } @@ -930,10 +968,18 @@ String VisualShaderNodeParticleRandomness::get_input_port_name(int p_port) const String VisualShaderNodeParticleRandomness::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - if (op_type == OP_TYPE_SCALAR) { - code += vformat(" %s = __randf_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); - } else if (op_type == OP_TYPE_VECTOR) { - code += vformat(" %s = __randv_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + switch (op_type) { + case OP_TYPE_SCALAR: { + code += vformat(" %s = __randf_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_2D: { + code += vformat(" %s = __randv2_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_3D: { + code += vformat(" %s = __randv3_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + } break; + default: + break; } return code; } @@ -943,12 +989,21 @@ void VisualShaderNodeParticleRandomness::set_op_type(OpType p_op_type) { if (op_type == p_op_type) { return; } - if (p_op_type == OP_TYPE_SCALAR) { - set_input_port_default_value(0, 0.0); - set_input_port_default_value(1, 1.0); - } else { - set_input_port_default_value(0, Vector3(-1.0, -1.0, -1.0)); - set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0)); + switch (p_op_type) { + case OP_TYPE_SCALAR: { + set_input_port_default_value(0, 0.0, get_input_port_default_value(0)); + set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); + } break; + case OP_TYPE_VECTOR_2D: { + set_input_port_default_value(0, Vector2(), get_input_port_default_value(0)); + set_input_port_default_value(1, Vector2(), get_input_port_default_value(1)); + } break; + case OP_TYPE_VECTOR_3D: { + set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); + set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); + } break; + default: + break; } op_type = p_op_type; emit_changed(); @@ -963,7 +1018,7 @@ bool VisualShaderNodeParticleRandomness::has_output_port_preview(int p_port) con } VisualShaderNodeParticleRandomness::VisualShaderNodeParticleRandomness() { - set_input_port_default_value(0, 0.0); + set_input_port_default_value(0, -1.0); set_input_port_default_value(1, 1.0); } @@ -996,7 +1051,7 @@ int VisualShaderNodeParticleAccelerator::get_output_port_count() const { } VisualShaderNodeParticleAccelerator::PortType VisualShaderNodeParticleAccelerator::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } String VisualShaderNodeParticleAccelerator::get_output_port_name(int p_port) const { @@ -1009,11 +1064,11 @@ int VisualShaderNodeParticleAccelerator::get_input_port_count() const { VisualShaderNodeParticleAccelerator::PortType VisualShaderNodeParticleAccelerator::get_input_port_type(int p_port) const { if (p_port == 0) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } else if (p_port == 1) { return PORT_TYPE_SCALAR; } else if (p_port == 2) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; } return PORT_TYPE_SCALAR; } @@ -1106,19 +1161,19 @@ VisualShaderNodeParticleOutput::PortType VisualShaderNodeParticleOutput::get_inp switch (p_port) { case 0: if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { - return PORT_TYPE_VECTOR; // custom.rgb + return PORT_TYPE_VECTOR_3D; // custom.rgb } return PORT_TYPE_BOOLEAN; // active case 1: if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { break; // custom.a (scalar) } - return PORT_TYPE_VECTOR; // velocity + return PORT_TYPE_VECTOR_3D; // velocity case 2: - return PORT_TYPE_VECTOR; // color & velocity + return PORT_TYPE_VECTOR_3D; // color & velocity case 3: if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { - return PORT_TYPE_VECTOR; // color + return PORT_TYPE_VECTOR_3D; // color } break; // alpha (scalar) case 4: @@ -1131,18 +1186,18 @@ VisualShaderNodeParticleOutput::PortType VisualShaderNodeParticleOutput::get_inp if (shader_type == VisualShader::TYPE_COLLIDE) { return PORT_TYPE_TRANSFORM; // transform } - return PORT_TYPE_VECTOR; // position + return PORT_TYPE_VECTOR_3D; // position case 5: if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { return PORT_TYPE_TRANSFORM; // transform } if (shader_type == VisualShader::TYPE_PROCESS) { - return PORT_TYPE_VECTOR; // rotation_axis + return PORT_TYPE_VECTOR_3D; // rotation_axis } break; // scale (scalar) case 6: if (shader_type == VisualShader::TYPE_START) { - return PORT_TYPE_VECTOR; // rotation_axis + return PORT_TYPE_VECTOR_3D; // rotation_axis } break; case 7: @@ -1372,13 +1427,13 @@ VisualShaderNodeParticleEmit::PortType VisualShaderNodeParticleEmit::get_input_p case 1: return PORT_TYPE_TRANSFORM; case 2: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 3: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 4: return PORT_TYPE_SCALAR; case 5: - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_3D; case 6: return PORT_TYPE_SCALAR; } diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index ce0d896c01..0b91cba5e0 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -48,7 +48,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual bool has_output_port_preview(int p_port) const override; - void set_mode_2d(bool p_enabled); + virtual void set_mode_2d(bool p_enabled); bool is_mode_2d() const; Vector<StringName> get_editable_properties() const override; @@ -68,7 +68,7 @@ public: virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) const override; - virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const override; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeParticleSphereEmitter(); @@ -83,8 +83,9 @@ public: virtual int get_input_port_count() const override; virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) const override; + virtual void set_mode_2d(bool p_enabled) override; - virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const override; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeParticleBoxEmitter(); @@ -100,7 +101,7 @@ public: virtual PortType get_input_port_type(int p_port) const override; virtual String get_input_port_name(int p_port) const override; - virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_global_per_node(Shader::Mode p_mode, int p_id) const override; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeParticleRingEmitter(); @@ -118,7 +119,7 @@ class VisualShaderNodeParticleMeshEmitter : public VisualShaderNodeParticleEmitt Ref<ImageTexture> uv_texture; Ref<ImageTexture> uv2_texture; - String _generate_code(VisualShader::Type p_type, int p_id, const String *p_output_vars, int p_index, const String &p_texture_name, bool p_ignore_mode2d = false) const; + String _generate_code(VisualShader::Type p_type, int p_id, const String *p_output_vars, int p_index, const String &p_texture_name, PortType p_port_type) const; void _update_texture(const Vector<Vector2> &p_array, Ref<ImageTexture> &r_texture); void _update_texture(const Vector<Vector3> &p_array, Ref<ImageTexture> &r_texture); @@ -213,7 +214,8 @@ class VisualShaderNodeParticleRandomness : public VisualShaderNode { public: enum OpType { OP_TYPE_SCALAR, - OP_TYPE_VECTOR, + OP_TYPE_VECTOR_2D, + OP_TYPE_VECTOR_3D, OP_TYPE_MAX, }; diff --git a/scene/resources/visual_shader_sdf_nodes.cpp b/scene/resources/visual_shader_sdf_nodes.cpp index 6654e2319b..cbd3ebd83b 100644 --- a/scene/resources/visual_shader_sdf_nodes.cpp +++ b/scene/resources/visual_shader_sdf_nodes.cpp @@ -41,7 +41,7 @@ int VisualShaderNodeSDFToScreenUV::get_input_port_count() const { } VisualShaderNodeSDFToScreenUV::PortType VisualShaderNodeSDFToScreenUV::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeSDFToScreenUV::get_input_port_name(int p_port) const { @@ -53,7 +53,7 @@ int VisualShaderNodeSDFToScreenUV::get_output_port_count() const { } VisualShaderNodeSDFToScreenUV::PortType VisualShaderNodeSDFToScreenUV::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeSDFToScreenUV::get_output_port_name(int p_port) const { @@ -61,7 +61,7 @@ String VisualShaderNodeSDFToScreenUV::get_output_port_name(int p_port) const { } String VisualShaderNodeSDFToScreenUV::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - return " " + p_output_vars[0] + " = vec3(sdf_to_screen_uv(" + (p_input_vars[0].is_empty() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; + return " " + p_output_vars[0] + " = sdf_to_screen_uv(" + (p_input_vars[0].is_empty() ? "vec2(0.0)" : p_input_vars[0]) + ");\n"; } VisualShaderNodeSDFToScreenUV::VisualShaderNodeSDFToScreenUV() { @@ -78,7 +78,7 @@ int VisualShaderNodeScreenUVToSDF::get_input_port_count() const { } VisualShaderNodeScreenUVToSDF::PortType VisualShaderNodeScreenUVToSDF::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeScreenUVToSDF::get_input_port_name(int p_port) const { @@ -90,7 +90,7 @@ int VisualShaderNodeScreenUVToSDF::get_output_port_count() const { } VisualShaderNodeScreenUVToSDF::PortType VisualShaderNodeScreenUVToSDF::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeScreenUVToSDF::get_output_port_name(int p_port) const { @@ -105,7 +105,7 @@ bool VisualShaderNodeScreenUVToSDF::is_input_port_default(int p_port, Shader::Mo } String VisualShaderNodeScreenUVToSDF::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - return " " + p_output_vars[0] + " = vec3(screen_uv_to_sdf(" + (p_input_vars[0].is_empty() ? "SCREEN_UV" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; + return " " + p_output_vars[0] + " = screen_uv_to_sdf(" + (p_input_vars[0].is_empty() ? "SCREEN_UV" : p_input_vars[0]) + ");\n"; } VisualShaderNodeScreenUVToSDF::VisualShaderNodeScreenUVToSDF() { @@ -122,7 +122,7 @@ int VisualShaderNodeTextureSDF::get_input_port_count() const { } VisualShaderNodeTextureSDF::PortType VisualShaderNodeTextureSDF::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeTextureSDF::get_input_port_name(int p_port) const { @@ -142,7 +142,7 @@ String VisualShaderNodeTextureSDF::get_output_port_name(int p_port) const { } String VisualShaderNodeTextureSDF::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - return " " + p_output_vars[0] + " = texture_sdf(" + (p_input_vars[0].is_empty() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + ");\n"; + return " " + p_output_vars[0] + " = texture_sdf(" + (p_input_vars[0].is_empty() ? "vec2(0.0)" : p_input_vars[0]) + ");\n"; } VisualShaderNodeTextureSDF::VisualShaderNodeTextureSDF() { @@ -159,7 +159,7 @@ int VisualShaderNodeTextureSDFNormal::get_input_port_count() const { } VisualShaderNodeTextureSDFNormal::PortType VisualShaderNodeTextureSDFNormal::get_input_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeTextureSDFNormal::get_input_port_name(int p_port) const { @@ -171,7 +171,7 @@ int VisualShaderNodeTextureSDFNormal::get_output_port_count() const { } VisualShaderNodeTextureSDFNormal::PortType VisualShaderNodeTextureSDFNormal::get_output_port_type(int p_port) const { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } String VisualShaderNodeTextureSDFNormal::get_output_port_name(int p_port) const { @@ -179,7 +179,7 @@ String VisualShaderNodeTextureSDFNormal::get_output_port_name(int p_port) const } String VisualShaderNodeTextureSDFNormal::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - return " " + p_output_vars[0] + " = vec3(texture_sdf_normal(" + (p_input_vars[0].is_empty() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; + return " " + p_output_vars[0] + " = texture_sdf_normal(" + (p_input_vars[0].is_empty() ? "vec2(0.0)" : p_input_vars[0]) + ");\n"; } VisualShaderNodeTextureSDFNormal::VisualShaderNodeTextureSDFNormal() { @@ -197,7 +197,7 @@ int VisualShaderNodeSDFRaymarch::get_input_port_count() const { VisualShaderNodeSDFRaymarch::PortType VisualShaderNodeSDFRaymarch::get_input_port_type(int p_port) const { if (p_port == 0 || p_port == 1) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } return PORT_TYPE_SCALAR; } @@ -221,7 +221,7 @@ VisualShaderNodeSDFRaymarch::PortType VisualShaderNodeSDFRaymarch::get_output_po } else if (p_port == 1) { return PORT_TYPE_BOOLEAN; } else if (p_port == 2) { - return PORT_TYPE_VECTOR; + return PORT_TYPE_VECTOR_2D; } return PORT_TYPE_SCALAR; } @@ -245,13 +245,13 @@ String VisualShaderNodeSDFRaymarch::generate_code(Shader::Mode p_mode, VisualSha if (p_input_vars[0].is_empty()) { code += " vec2 __from_pos = vec2(0.0f);\n"; } else { - code += " vec2 __from_pos = " + p_input_vars[0] + ".xy;\n"; + code += " vec2 __from_pos = " + p_input_vars[0] + ";\n"; } if (p_input_vars[1].is_empty()) { code += " vec2 __to_pos = vec2(0.0f);\n"; } else { - code += " vec2 __to_pos = " + p_input_vars[1] + ".xy;\n"; + code += " vec2 __to_pos = " + p_input_vars[1] + ";\n"; } code += "\n vec2 __at = __from_pos;\n"; @@ -271,7 +271,7 @@ String VisualShaderNodeSDFRaymarch::generate_code(Shader::Mode p_mode, VisualSha code += " float __dist = min(__max_dist, __accum);\n"; code += " " + p_output_vars[0] + " = __dist;\n"; code += " " + p_output_vars[1] + " = __accum < __max_dist;\n"; - code += " " + p_output_vars[2] + " = vec3(__from_pos + __dir * __dist, 0.0f);\n"; + code += " " + p_output_vars[2] + " = __from_pos + __dir * __dist;\n"; code += " }\n"; |