Merge pull request #90534 from Geometror/vs-reroute-node

[VisualShader] Add reroute node and improve port drawing
This commit is contained in:
Rémi Verschelde 2024-05-13 12:05:25 +02:00
commit dcd6db8680
No known key found for this signature in database
GPG Key ID: C3336907360768E1
13 changed files with 505 additions and 78 deletions

View File

@ -267,6 +267,9 @@
</method>
</methods>
<members>
<member name="ignore_invalid_connection_type" type="bool" setter="set_ignore_invalid_connection_type" getter="is_ignoring_valid_connection_type" default="false">
If [code]true[/code], you can connect ports with different types, even if the connection was not explicitly allowed in the parent [GraphEdit].
</member>
<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="0" />
<member name="title" type="String" setter="set_title" getter="get_title" default="&quot;&quot;">
The text displayed in the GraphNode's title bar.

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VisualShaderNodeReroute" inherits="VisualShaderNode" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A node that allows rerouting a connection within the visual shader graph.
</brief_description>
<description>
Automatically adapts its port type to the type of the incoming connection and ensures valid connections.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_port_type" qualifiers="const">
<return type="int" enum="VisualShaderNode.PortType" />
<description>
Returns the port type of the reroute node.
</description>
</method>
</methods>
</class>

View File

@ -45,6 +45,7 @@
#include "editor/plugins/curve_editor_plugin.h"
#include "editor/plugins/shader_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/animation/tween.h"
#include "scene/gui/button.h"
#include "scene/gui/check_box.h"
#include "scene/gui/code_edit.h"
@ -104,6 +105,83 @@ void VisualShaderNodePlugin::_bind_methods() {
///////////////////
void VSGraphNode::_draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color, const Color &p_rim_color) {
Ref<Texture2D> port_icon = p_left ? get_slot_custom_icon_left(p_slot_index) : get_slot_custom_icon_right(p_slot_index);
Point2 icon_offset;
if (!port_icon.is_valid()) {
port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
}
icon_offset = -port_icon->get_size() * 0.5;
// Draw "shadow"/outline in the connection rim color.
draw_texture_rect(port_icon, Rect2(p_pos + icon_offset - Size2(2, 2), port_icon->get_size() + Size2(4, 4)), false, p_rim_color);
draw_texture(port_icon, p_pos + icon_offset, p_color);
}
void VSGraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) {
Color rim_color = get_theme_color(SNAME("connection_rim_color"), SNAME("GraphEdit"));
_draw_port(p_slot_index, p_pos, p_left, p_color, rim_color);
}
///////////////////
void VSRerouteNode::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
connect("mouse_entered", callable_mp(this, &VSRerouteNode::_on_mouse_entered));
connect("mouse_exited", callable_mp(this, &VSRerouteNode::_on_mouse_exited));
} break;
case NOTIFICATION_DRAW: {
Vector2 offset = Vector2(0, -16);
Color drag_bg_color = get_theme_color(SNAME("drag_background"), SNAME("VSRerouteNode"));
draw_circle(get_size() * 0.5 + offset, 16, Color(drag_bg_color, selected ? 1 : icon_opacity));
Ref<Texture2D> icon = get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"));
Point2 icon_offset = -icon->get_size() * 0.5 + get_size() * 0.5 + offset;
draw_texture(icon, icon_offset, Color(1, 1, 1, selected ? 1 : icon_opacity));
} break;
}
}
void VSRerouteNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) {
Color rim_color = selected ? get_theme_color("selected_rim_color", "VSRerouteNode") : get_theme_color("connection_rim_color", "GraphEdit");
_draw_port(p_slot_index, p_pos, p_left, p_color, rim_color);
}
VSRerouteNode::VSRerouteNode() {
Label *title_lbl = Object::cast_to<Label>(get_titlebar_hbox()->get_child(0));
title_lbl->hide();
const Size2 size = Size2(32, 32) * EDSCALE;
Control *slot_area = memnew(Control);
slot_area->set_custom_minimum_size(size);
slot_area->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
add_child(slot_area);
// Lay the input and output ports on top of each other to create the illusion of a single port.
add_theme_constant_override("port_h_offset", size.width / 2);
}
void VSRerouteNode::set_icon_opacity(float p_opacity) {
icon_opacity = p_opacity;
queue_redraw();
}
void VSRerouteNode::_on_mouse_entered() {
Ref<Tween> tween = create_tween();
tween->tween_method(callable_mp(this, &VSRerouteNode::set_icon_opacity), 0.0, 1.0, FADE_ANIMATION_LENGTH_SEC);
}
void VSRerouteNode::_on_mouse_exited() {
Ref<Tween> tween = create_tween();
tween->tween_method(callable_mp(this, &VSRerouteNode::set_icon_opacity), 1.0, 0.0, FADE_ANIMATION_LENGTH_SEC);
}
///////////////////
VisualShaderGraphPlugin::VisualShaderGraphPlugin() {
vs_msdf_fonts_theme.instantiate();
}
@ -376,6 +454,15 @@ void VisualShaderGraphPlugin::set_frame_autoshrink_enabled(VisualShader::Type p_
frame->set_autoshrink_enabled(p_enable);
}
void VisualShaderGraphPlugin::update_reroute_nodes() {
for (const KeyValue<int, Link> &E : links) {
Ref<VisualShaderNodeReroute> reroute_node = Object::cast_to<VisualShaderNodeReroute>(E.value.visual_node);
if (reroute_node.is_valid()) {
update_node(visual_shader->get_shader_type(), E.key);
}
}
}
Ref<Script> VisualShaderGraphPlugin::get_node_script(int p_node_id) const {
if (!links.has(p_node_id)) {
return Ref<Script>();
@ -572,6 +659,9 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
bool is_expression = expression_node.is_valid();
String expression = "";
Ref<VisualShaderNodeReroute> reroute_node = vsnode;
bool is_reroute = reroute_node.is_valid();
Ref<VisualShaderNodeCustom> custom_node = vsnode;
if (custom_node.is_valid()) {
custom_node->_set_initialized(true);
@ -582,8 +672,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
GraphFrame *frame = memnew(GraphFrame);
frame->set_title(vsnode->get_caption());
node = frame;
} else if (is_reroute) {
VSRerouteNode *reroute_gnode = memnew(VSRerouteNode);
reroute_gnode->set_ignore_invalid_connection_type(true);
node = reroute_gnode;
} else {
GraphNode *gnode = memnew(GraphNode);
VSGraphNode *gnode = memnew(VSGraphNode);
gnode->set_title(vsnode->get_caption());
node = gnode;
}
@ -598,7 +692,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
node->set_theme(vs_msdf_fonts_theme);
// Set the node's titlebar color based on its category.
if (vsnode->get_category() != VisualShaderNode::CATEGORY_NONE) {
if (vsnode->get_category() != VisualShaderNode::CATEGORY_NONE && !is_frame && !is_reroute) {
Ref<StyleBoxFlat> sb_colored = editor->get_theme_stylebox("titlebar", "GraphNode")->duplicate();
sb_colored->set_bg_color(category_color[vsnode->get_category()]);
node->add_theme_style_override("titlebar", sb_colored);
@ -685,9 +779,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
return;
}
if (!is_reroute) {
Control *content_offset = memnew(Control);
content_offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE));
node->add_child(content_offset);
}
if (is_group) {
port_offset += 1;
@ -696,7 +792,9 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
// Set the minimum width of a node based on the preview size to avoid a resize when toggling the preview.
Ref<StyleBoxFlat> graph_node_stylebox = graph->get_theme_stylebox("panel", "GraphNode");
int port_preview_size = EDITOR_GET("editors/visual_editors/visual_shader/port_preview_size");
if (!is_frame && !is_reroute) {
node->set_custom_minimum_size(Size2((Math::ceil(graph_node_stylebox->get_minimum_size().width) + port_preview_size) * EDSCALE, 0));
}
Ref<VisualShaderNodeParticleEmit> emit = vsnode;
if (emit.is_valid()) {
@ -1129,7 +1227,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
port_offset++;
}
if (!is_first_hbox) {
if (!is_first_hbox && !is_reroute) {
node->add_child(hb);
if (curve_xyz.is_valid()) {
node->move_child(hb, 1 + expanded_port_counter);
@ -1140,9 +1238,9 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
continue;
}
int idx = 1;
if (!is_first_hbox) {
idx = i + port_offset;
int idx = is_first_hbox ? 1 : i + port_offset;
if (is_reroute) {
idx = 0;
}
if (!is_frame) {
GraphNode *graph_node = Object::cast_to<GraphNode>(node);
@ -1243,7 +1341,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
if (vsnode->get_output_port_for_preview() >= 0) {
has_relative_parameter_instances = is_node_has_parameter_instances_relatively(p_type, p_id);
show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview(), !has_relative_parameter_instances);
} else {
} else if (!is_reroute) {
offset = memnew(Control);
offset->set_custom_minimum_size(Size2(0, 4 * EDSCALE));
node->add_child(offset);
@ -1342,6 +1440,13 @@ void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_fro
}
if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) {
// Update reroute nodes since their port type might have changed.
Ref<VisualShaderNodeReroute> reroute_to = visual_shader->get_node(p_type, p_to_node);
Ref<VisualShaderNodeReroute> reroute_from = visual_shader->get_node(p_type, p_from_node);
if (reroute_to.is_valid() || reroute_from.is_valid()) {
update_reroute_nodes();
}
graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port);
connections.push_back({ p_from_node, p_from_port, p_to_node, p_to_port });
@ -1954,6 +2059,7 @@ void VisualShaderEditor::_update_options_menu() {
static Vector<String> type_filter_exceptions;
if (type_filter_exceptions.is_empty()) {
type_filter_exceptions.append("VisualShaderNodeExpression");
type_filter_exceptions.append("VisualShaderNodeReroute");
}
for (int i = 0; i < add_options.size(); i++) {
@ -3478,6 +3584,8 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
frame->set_size(Size2(320 * EDSCALE, 180 * EDSCALE));
}
Ref<VisualShaderNodeReroute> reroute = vsnode;
bool created_expression_port = false;
// A node is inserted in an already present connection.
@ -3488,6 +3596,61 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, to_node, to_slot);
}
// Create a connection from the output port of an existing node to the new one.
if (from_node != -1 && from_slot != -1) {
VisualShaderNode::PortType output_port_type = visual_shader->get_node(type, from_node)->get_output_port_type(from_slot);
if (expr && expr->is_editable()) {
expr->add_input_port(0, output_port_type, "input0");
created_expression_port = true;
}
if (vsnode->get_input_port_count() > 0 || created_expression_port) {
int _to_node = id_to_use;
if (created_expression_port) {
int _to_slot = 0;
undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
} else {
int _to_slot = -1;
// Attempting to connect to the default input port or to the first correct port (if it's not found).
for (int i = 0; i < vsnode->get_input_port_count(); i++) {
if (visual_shader->is_port_types_compatible(output_port_type, vsnode->get_input_port_type(i)) || reroute.is_valid()) {
if (i == vsnode->get_default_input_port(output_port_type)) {
_to_slot = i;
break;
} else if (_to_slot == -1) {
_to_slot = i;
}
}
}
if (_to_slot >= 0) {
undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
}
}
if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) {
if (is_texture2d) {
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeTexture::SOURCE_PORT);
}
if (is_texture3d || is_texture2d_array) {
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeSample3D::SOURCE_PORT);
}
if (is_cubemap) {
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeCubemap::SOURCE_PORT);
}
}
}
}
// Create a connection from the new node to an input port of an existing one.
if (to_node != -1 && to_slot != -1) {
VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot);
@ -3548,7 +3711,7 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
// Attempting to connect to the first correct port.
for (int i = 0; i < vsnode->get_output_port_count(); i++) {
if (visual_shader->is_port_types_compatible(vsnode->get_output_port_type(i), input_port_type)) {
if (visual_shader->is_port_types_compatible(vsnode->get_output_port_type(i), input_port_type) || reroute.is_valid()) {
undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, _from_node, i, to_node, to_slot);
undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, _from_node, i, to_node, to_slot);
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, _from_node, i, to_node, to_slot);
@ -3560,60 +3723,6 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
}
}
// Create a connection from the output port of an existing node to the new one.
if (from_node != -1 && from_slot != -1) {
VisualShaderNode::PortType output_port_type = visual_shader->get_node(type, from_node)->get_output_port_type(from_slot);
if (expr && expr->is_editable()) {
expr->add_input_port(0, output_port_type, "input0");
created_expression_port = true;
}
if (vsnode->get_input_port_count() > 0 || created_expression_port) {
int _to_node = id_to_use;
if (created_expression_port) {
int _to_slot = 0;
undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
} else {
int _to_slot = -1;
// Attempting to connect to the default input port or to the first correct port (if it's not found).
for (int i = 0; i < vsnode->get_input_port_count(); i++) {
if (visual_shader->is_port_types_compatible(output_port_type, vsnode->get_input_port_type(i))) {
if (i == vsnode->get_default_input_port(output_port_type)) {
_to_slot = i;
break;
} else if (_to_slot == -1) {
_to_slot = i;
}
}
}
if (_to_slot >= 0) {
undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot);
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot);
}
}
if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) {
if (is_texture2d) {
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeTexture::SOURCE_PORT);
}
if (is_texture3d || is_texture2d_array) {
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeSample3D::SOURCE_PORT);
}
if (is_cubemap) {
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeCubemap::SOURCE_PORT);
}
}
}
}
_member_cancel();
if (is_parameter) {
@ -3836,6 +3945,9 @@ void VisualShaderEditor::_connection_request(const String &p_from, int p_from_in
undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index);
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from, p_from_index, to, p_to_index);
undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index);
undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, from);
undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, from);
undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, to);
undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, to);
undo_redo->commit_action();
@ -3931,10 +4043,12 @@ bool VisualShaderEditor::_check_node_drop_on_connection(const Vector2 &p_positio
VisualShaderNode::PortType original_port_type_from = visual_shader->get_node(shader_type, String(intersecting_connection->from_node).to_int())->get_output_port_type(intersecting_connection->from_port);
VisualShaderNode::PortType original_port_type_to = visual_shader->get_node(shader_type, String(intersecting_connection->to_node).to_int())->get_input_port_type(intersecting_connection->to_port);
Ref<VisualShaderNodeReroute> reroute_node = selected_vsnode;
// Searching for the default port or the first compatible input port of the node to insert.
int _to_port = -1;
for (int i = 0; i < selected_vsnode->get_input_port_count(); i++) {
if (visual_shader->is_port_types_compatible(original_port_type_from, selected_vsnode->get_input_port_type(i))) {
if (visual_shader->is_port_types_compatible(original_port_type_from, selected_vsnode->get_input_port_type(i)) || reroute_node.is_valid()) {
if (i == selected_vsnode->get_default_input_port(original_port_type_from)) {
_to_port = i;
break;
@ -3947,7 +4061,7 @@ bool VisualShaderEditor::_check_node_drop_on_connection(const Vector2 &p_positio
// Searching for the first compatible output port of the node to insert.
int _from_port = -1;
for (int i = 0; i < selected_vsnode->get_output_port_count(); i++) {
if (visual_shader->is_port_types_compatible(selected_vsnode->get_output_port_type(i), original_port_type_to)) {
if (visual_shader->is_port_types_compatible(selected_vsnode->get_output_port_type(i), original_port_type_to) || reroute_node.is_valid()) {
_from_port = i;
break;
}
@ -4529,6 +4643,8 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) {
Ref<GraphEdit::Connection> closest_connection = graph->get_closest_connection_at_point(menu_point);
if (closest_connection.is_valid()) {
clicked_connection = closest_connection;
saved_node_pos = graph->get_local_mouse_position();
saved_node_pos_dirty = true;
connection_popup_menu->set_position(gpos);
connection_popup_menu->reset_size();
connection_popup_menu->popup();
@ -5628,6 +5744,25 @@ void VisualShaderEditor::_connection_menu_id_pressed(int p_idx) {
connection_node_insert_requested = true;
_show_members_dialog(true, input_port_type, output_port_type);
} break;
case ConnectionMenuOptions::INSERT_NEW_REROUTE: {
from_node = String(clicked_connection->from_node).to_int();
from_slot = clicked_connection->from_port;
to_node = String(clicked_connection->to_node).to_int();
to_slot = clicked_connection->to_port;
// Manual offset to place the port exactly at the mouse position.
saved_node_pos -= Vector2(11 * EDSCALE * graph->get_zoom(), 50 * EDSCALE * graph->get_zoom());
// Find reroute addoptions.
int idx = -1;
for (int i = 0; i < add_options.size(); i++) {
if (add_options[i].name == "Reroute") {
idx = i;
break;
}
}
_add_node(idx, add_options[idx].ops);
} break;
default:
break;
}
@ -6123,6 +6258,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_child(connection_popup_menu);
connection_popup_menu->add_item(TTR("Disconnect"), ConnectionMenuOptions::DISCONNECT);
connection_popup_menu->add_item(TTR("Insert New Node"), ConnectionMenuOptions::INSERT_NEW_NODE);
connection_popup_menu->add_item(TTR("Insert New Reroute"), ConnectionMenuOptions::INSERT_NEW_REROUTE);
connection_popup_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_connection_menu_id_pressed));
///////////////////////////////////////
@ -6962,6 +7098,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("VaryingSetter", "Special", "VisualShaderNodeVaryingSetter", TTR("Set varying parameter."), {}, -1, TYPE_FLAGS_VERTEX | TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("VaryingGetter", "Special", "VisualShaderNodeVaryingGetter", TTR("Get varying parameter."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM));
add_options.push_back(AddOption("VaryingSetter", "Special", "VisualShaderNodeVaryingSetter", TTR("Set varying parameter."), {}, -1, TYPE_FLAGS_VERTEX | TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM));
add_options.push_back(AddOption("Reroute", "Special", "VisualShaderNodeReroute", TTR("Reroute connections freely, can be used to connect multiple input ports to single output port.")));
custom_node_option_idx = add_options.size();

View File

@ -66,6 +66,34 @@ public:
virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node);
};
class VSGraphNode : public GraphNode {
GDCLASS(VSGraphNode, GraphNode);
protected:
void _draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color, const Color &p_rim_color);
virtual void draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) override;
};
class VSRerouteNode : public VSGraphNode {
GDCLASS(VSRerouteNode, GraphNode);
const float FADE_ANIMATION_LENGTH_SEC = 0.3;
float icon_opacity = 0.0;
protected:
void _notification(int p_what);
virtual void draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) override;
public:
VSRerouteNode();
void set_icon_opacity(float p_opacity);
void _on_mouse_entered();
void _on_mouse_exited();
};
class VisualShaderGraphPlugin : public RefCounted {
GDCLASS(VisualShaderGraphPlugin, RefCounted);
@ -140,6 +168,7 @@ public:
void set_frame_color_enabled(VisualShader::Type p_type, int p_node_id, bool p_enable);
void set_frame_color(VisualShader::Type p_type, int p_node_id, const Color &p_color);
void set_frame_autoshrink_enabled(VisualShader::Type p_type, int p_node_id, bool p_enable);
void update_reroute_nodes();
int get_constant_index(float p_constant) const;
Ref<Script> get_node_script(int p_node_id) const;
void update_theme();
@ -297,6 +326,7 @@ class VisualShaderEditor : public VBoxContainer {
enum ConnectionMenuOptions {
INSERT_NEW_NODE,
INSERT_NEW_REROUTE,
DISCONNECT,
};

View File

@ -1650,7 +1650,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_stylebox("titlebar_selected", "GraphFrame", make_empty_stylebox(4, 4, 4, 4));
p_theme->set_color("resizer_color", "GraphFrame", gn_decoration_color);
// GraphFrame's title Label
// GraphFrame's title Label.
p_theme->set_type_variation("GraphFrameTitleLabel", "Label");
p_theme->set_stylebox("normal", "GraphFrameTitleLabel", memnew(StyleBoxEmpty));
p_theme->set_font_size("font_size", "GraphFrameTitleLabel", 22);
@ -1663,6 +1663,21 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_constant("shadow_outline_size", "GraphFrameTitleLabel", 1 * EDSCALE);
p_theme->set_constant("line_spacing", "GraphFrameTitleLabel", 3 * EDSCALE);
}
// VisualShader reroute node.
{
Ref<StyleBox> vs_reroute_panel_style = make_empty_stylebox();
Ref<StyleBox> vs_reroute_titlebar_style = vs_reroute_panel_style->duplicate();
vs_reroute_titlebar_style->set_content_margin_all(16);
p_theme->set_stylebox("panel", "VSRerouteNode", vs_reroute_panel_style);
p_theme->set_stylebox("panel_selected", "VSRerouteNode", vs_reroute_panel_style);
p_theme->set_stylebox("titlebar", "VSRerouteNode", vs_reroute_titlebar_style);
p_theme->set_stylebox("titlebar_selected", "VSRerouteNode", vs_reroute_titlebar_style);
p_theme->set_stylebox("slot", "VSRerouteNode", make_empty_stylebox());
p_theme->set_color("drag_background", "VSRerouteNode", p_config.dark_theme ? Color(0.19, 0.21, 0.24) : Color(0.8, 0.8, 0.8));
p_theme->set_color("selected_rim_color", "VSRerouteNode", p_config.dark_theme ? Color(1, 1, 1) : Color(0, 0, 0));
}
}
// ColorPicker and related nodes.

View File

@ -1059,7 +1059,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
int type = graph_node->get_output_port_type(j);
if ((type == connecting_type ||
if ((type == connecting_type || graph_node->is_ignoring_valid_connection_type() ||
valid_connection_types.has(ConnectionType(type, connecting_type))) &&
is_in_output_hotzone(graph_node, j, mpos, port_size)) {
if (!is_node_hover_valid(graph_node->get_name(), j, connecting_from_node, connecting_from_port_index)) {
@ -1084,7 +1084,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
int type = graph_node->get_input_port_type(j);
if ((type == connecting_type || valid_connection_types.has(ConnectionType(connecting_type, type))) &&
if ((type == connecting_type || graph_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(connecting_type, type))) &&
is_in_input_hotzone(graph_node, j, mpos, port_size)) {
if (!is_node_hover_valid(connecting_from_node, connecting_from_port_index, graph_node->get_name(), j)) {
continue;
@ -1117,6 +1117,8 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
emit_signal(SNAME("connection_from_empty"), connecting_from_node, connecting_from_port_index, mb->get_position());
}
}
} else {
set_selected(get_node_or_null(NodePath(connecting_from_node)));
}
if (connecting) {
@ -1636,12 +1638,12 @@ void GraphEdit::_draw_grid() {
void GraphEdit::set_selected(Node *p_child) {
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i));
if (!graph_node) {
GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i));
if (!graph_element) {
continue;
}
graph_node->set_selected(graph_node == p_child);
graph_element->set_selected(graph_element == p_child);
}
}

View File

@ -604,6 +604,14 @@ void GraphNode::set_slot_draw_stylebox(int p_slot_index, bool p_enable) {
emit_signal(SNAME("slot_updated"), p_slot_index);
}
void GraphNode::set_ignore_invalid_connection_type(bool p_ignore) {
ignore_invalid_connection_type = p_ignore;
}
bool GraphNode::is_ignoring_valid_connection_type() const {
return ignore_invalid_connection_type;
}
Size2 GraphNode::get_minimum_size() const {
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
@ -859,6 +867,9 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox);
ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox);
ClassDB::bind_method(D_METHOD("set_ignore_invalid_connection_type", "ignore"), &GraphNode::set_ignore_invalid_connection_type);
ClassDB::bind_method(D_METHOD("is_ignoring_valid_connection_type"), &GraphNode::is_ignoring_valid_connection_type);
ClassDB::bind_method(D_METHOD("get_input_port_count"), &GraphNode::get_input_port_count);
ClassDB::bind_method(D_METHOD("get_input_port_position", "port_idx"), &GraphNode::get_input_port_position);
ClassDB::bind_method(D_METHOD("get_input_port_type", "port_idx"), &GraphNode::get_input_port_type);
@ -874,6 +885,8 @@ void GraphNode::_bind_methods() {
GDVIRTUAL_BIND(_draw_port, "slot_index", "position", "left", "color")
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_invalid_connection_type"), "set_ignore_invalid_connection_type", "is_ignoring_valid_connection_type");
ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "slot_index")));
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel);

View File

@ -95,6 +95,8 @@ class GraphNode : public GraphElement {
bool port_pos_dirty = true;
bool ignore_invalid_connection_type = false;
void _port_pos_update();
protected:
@ -147,6 +149,9 @@ public:
bool is_slot_draw_stylebox(int p_slot_index) const;
void set_slot_draw_stylebox(int p_slot_index, bool p_enable);
void set_ignore_invalid_connection_type(bool p_ignore);
bool is_ignoring_valid_connection_type() const;
int get_input_port_count();
Vector2 get_input_port_position(int p_port_idx);
int get_input_port_type(int p_port_idx);

View File

@ -744,6 +744,7 @@ void register_scene_types() {
GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVarying);
GDREGISTER_CLASS(VisualShaderNodeVaryingSetter);
GDREGISTER_CLASS(VisualShaderNodeVaryingGetter);
GDREGISTER_CLASS(VisualShaderNodeReroute);
GDREGISTER_CLASS(VisualShaderNodeSDFToScreenUV);
GDREGISTER_CLASS(VisualShaderNodeScreenUVToSDF);

View File

@ -1101,6 +1101,42 @@ bool VisualShader::is_nodes_connected_relatively(const Graph *p_graph, int p_nod
return result;
}
bool VisualShader::_check_reroute_subgraph(Type p_type, int p_target_port_type, int p_reroute_node, List<int> *r_visited_reroute_nodes) const {
const Graph *g = &graph[p_type];
// BFS to check whether connecting to the given subgraph (rooted at p_reroute_node) is valid.
List<int> queue;
queue.push_back(p_reroute_node);
if (r_visited_reroute_nodes != nullptr) {
r_visited_reroute_nodes->push_back(p_reroute_node);
}
while (!queue.is_empty()) {
int current_node_id = queue.front()->get();
VisualShader::Node current_node = g->nodes[current_node_id];
queue.pop_front();
for (const int &next_node_id : current_node.next_connected_nodes) {
Ref<VisualShaderNodeReroute> next_vsnode = g->nodes[next_node_id].node;
if (next_vsnode.is_valid()) {
queue.push_back(next_node_id);
if (r_visited_reroute_nodes != nullptr) {
r_visited_reroute_nodes->push_back(next_node_id);
}
continue;
}
// Check whether all ports connected with the reroute node are compatible.
for (const Connection &c : g->connections) {
VisualShaderNode::PortType to_port_type = g->nodes[next_node_id].node->get_input_port_type(c.to_port);
if (c.from_node == current_node_id &&
c.to_node == next_node_id &&
!is_port_types_compatible(p_target_port_type, to_port_type)) {
return false;
}
}
}
}
return true;
}
bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const {
ERR_FAIL_INDEX_V(p_type, TYPE_MAX, false);
const Graph *g = &graph[p_type];
@ -1128,7 +1164,12 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po
VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port);
VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port);
if (!is_port_types_compatible(from_port_type, to_port_type)) {
Ref<VisualShaderNodeReroute> to_node_reroute = g->nodes[p_to_node].node;
if (to_node_reroute.is_valid()) {
if (!_check_reroute_subgraph(p_type, from_port_type, p_to_node)) {
return false;
}
} else if (!is_port_types_compatible(from_port_type, to_port_type)) {
return false;
}
@ -1141,7 +1182,6 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po
if (is_nodes_connected_relatively(g, p_from_node, p_to_node)) {
return false;
}
return true;
}
@ -1179,6 +1219,28 @@ void VisualShader::detach_node_from_frame(Type p_type, int p_node) {
g->nodes[p_node].node->set_frame(-1);
}
String VisualShader::get_reroute_parameter_name(Type p_type, int p_reroute_node) const {
ERR_FAIL_INDEX_V(p_type, TYPE_MAX, "");
const Graph *g = &graph[p_type];
ERR_FAIL_COND_V(!g->nodes.has(p_reroute_node), "");
const VisualShader::Node *node = &g->nodes[p_reroute_node];
while (node->prev_connected_nodes.size() > 0) {
int connected_node_id = node->prev_connected_nodes[0];
node = &g->nodes[connected_node_id];
Ref<VisualShaderNodeParameter> parameter_node = node->node;
if (parameter_node.is_valid() && parameter_node->get_output_port_type(0) == VisualShaderNode::PORT_TYPE_SAMPLER) {
return parameter_node->get_parameter_name();
}
Ref<VisualShaderNodeInput> input_node = node->node;
if (input_node.is_valid() && input_node->get_output_port_type(0) == VisualShaderNode::PORT_TYPE_SAMPLER) {
return input_node->get_input_real_name();
}
}
return "";
}
void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
ERR_FAIL_INDEX(p_type, TYPE_MAX);
Graph *g = &graph[p_type];
@ -1217,10 +1279,30 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,
ERR_FAIL_COND_V(!g->nodes.has(p_to_node), ERR_INVALID_PARAMETER);
ERR_FAIL_INDEX_V(p_to_port, g->nodes[p_to_node].node->get_input_port_count(), ERR_INVALID_PARAMETER);
Ref<VisualShaderNodeReroute> from_node_reroute = g->nodes[p_from_node].node;
Ref<VisualShaderNodeReroute> to_node_reroute = g->nodes[p_to_node].node;
// Allow connection with incompatible port types only if the reroute node isn't connected to anything.
VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port);
VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port);
bool port_types_are_compatible = is_port_types_compatible(from_port_type, to_port_type);
ERR_FAIL_COND_V_MSG(!is_port_types_compatible(from_port_type, to_port_type), ERR_INVALID_PARAMETER, "Incompatible port types (scalar/vec/bool) with transform.");
if (to_node_reroute.is_valid()) {
List<int> visited_reroute_nodes;
port_types_are_compatible = _check_reroute_subgraph(p_type, from_port_type, p_to_node, &visited_reroute_nodes);
if (port_types_are_compatible) {
// Set the port type of all reroute nodes.
for (const int &E : visited_reroute_nodes) {
Ref<VisualShaderNodeReroute> reroute_node = g->nodes[E].node;
reroute_node->_set_port_type(from_port_type);
}
}
} else if (from_node_reroute.is_valid() && !from_node_reroute->is_input_port_connected(0)) {
from_node_reroute->_set_port_type(to_port_type);
port_types_are_compatible = true;
}
ERR_FAIL_COND_V_MSG(!port_types_are_compatible, ERR_INVALID_PARAMETER, "Incompatible port types.");
for (const Connection &E : g->connections) {
if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) {
@ -1904,13 +1986,19 @@ Error VisualShader::_write_node(Type type, StringBuilder *p_global_code, StringB
if (in_type == VisualShaderNode::PORT_TYPE_SAMPLER && out_type == VisualShaderNode::PORT_TYPE_SAMPLER) {
VisualShaderNode *ptr = const_cast<VisualShaderNode *>(graph[type].nodes[from_node].node.ptr());
// FIXME: This needs to be refactored at some point.
if (ptr->has_method("get_input_real_name")) {
inputs[i] = ptr->call("get_input_real_name");
} else if (ptr->has_method("get_parameter_name")) {
inputs[i] = ptr->call("get_parameter_name");
} else {
Ref<VisualShaderNodeReroute> reroute = graph[type].nodes[from_node].node;
if (reroute.is_valid()) {
inputs[i] = get_reroute_parameter_name(type, from_node);
} else {
inputs[i] = "";
}
}
} else if (in_type == out_type) {
inputs[i] = src_var;
} else {

View File

@ -163,6 +163,8 @@ private:
void _input_type_changed(Type p_type, int p_id);
bool has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const;
bool _check_reroute_subgraph(Type p_type, int p_target_port_type, int p_reroute_node, List<int> *r_visited_reroute_nodes = nullptr) const;
protected:
virtual void _update_shader() const override;
static void _bind_methods();
@ -229,6 +231,8 @@ public: // internal methods
void attach_node_to_frame(Type p_type, int p_node, int p_frame);
void detach_node_from_frame(Type p_type, int p_node);
String get_reroute_parameter_name(Type p_type, int p_reroute_node) const;
void rebuild();
void get_node_connections(Type p_type, List<Connection> *r_connections) const;

View File

@ -8150,3 +8150,82 @@ VisualShaderNodeRotationByAxis::VisualShaderNodeRotationByAxis() {
simple_decl = false;
}
String VisualShaderNodeReroute::get_caption() const {
return "Reroute";
}
int VisualShaderNodeReroute::get_input_port_count() const {
return 1;
}
VisualShaderNodeReroute::PortType VisualShaderNodeReroute::get_input_port_type(int p_port) const {
return input_port_type;
}
String VisualShaderNodeReroute::get_input_port_name(int p_port) const {
return String();
}
int VisualShaderNodeReroute::get_output_port_count() const {
return 1;
}
VisualShaderNodeReroute::PortType VisualShaderNodeReroute::get_output_port_type(int p_port) const {
return input_port_type;
}
String VisualShaderNodeReroute::get_output_port_name(int p_port) const {
return String();
}
String VisualShaderNodeReroute::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;
for (int i = 0; i < get_output_port_count(); i++) {
if (input_port_type == PORT_TYPE_SAMPLER) {
continue;
}
String input = p_input_vars[0];
if (input.is_empty()) {
code += vformat(" %s;\n", p_output_vars[i]);
continue;
}
code += vformat(" %s = %s;\n", p_output_vars[i], input);
}
return code;
}
void VisualShaderNodeReroute::_set_port_type(PortType p_type) {
input_port_type = p_type;
switch (p_type) {
case PORT_TYPE_SCALAR:
set_input_port_default_value(0, 0.0);
break;
case PORT_TYPE_VECTOR_2D:
set_input_port_default_value(0, Vector2());
break;
case PORT_TYPE_VECTOR_3D:
set_input_port_default_value(0, Vector3());
break;
case PORT_TYPE_VECTOR_4D:
set_input_port_default_value(0, Vector4());
break;
case PORT_TYPE_TRANSFORM:
set_input_port_default_value(0, Transform3D());
break;
default:
break;
}
}
void VisualShaderNodeReroute::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_port_type", "port_type"), &VisualShaderNodeReroute::_set_port_type);
ClassDB::bind_method(D_METHOD("get_port_type"), &VisualShaderNodeReroute::get_port_type);
ADD_PROPERTY(PropertyInfo(Variant::INT, "port_type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_port_type", "get_port_type");
}
VisualShaderNodeReroute::VisualShaderNodeReroute() {
set_input_port_default_value(0, 0.0);
}

View File

@ -3092,4 +3092,35 @@ public:
VisualShaderNodeRotationByAxis();
};
class VisualShaderNodeReroute : public VisualShaderNode {
GDCLASS(VisualShaderNodeReroute, VisualShaderNode);
PortType input_port_type = PORT_TYPE_SCALAR;
protected:
static void _bind_methods();
public:
virtual String get_caption() const override;
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 int get_output_port_count() const override;
virtual PortType get_output_port_type(int p_port) const override;
virtual String get_output_port_name(int p_port) const override;
virtual bool has_output_port_preview(int p_port) const override { return false; }
virtual bool is_output_port_expandable(int p_port) const override { return false; }
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 Category get_category() const override { return CATEGORY_SPECIAL; }
void _set_port_type(PortType p_type);
PortType get_port_type() const { return input_port_type; }
VisualShaderNodeReroute();
};
#endif // VISUAL_SHADER_NODES_H