Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[VisualShader] Add reroute node and improve port drawing #90534

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/classes/GraphNode.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions doc/classes/VisualShaderNodeReroute.xml
Original file line number Diff line number Diff line change
@@ -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>
273 changes: 205 additions & 68 deletions editor/plugins/visual_shader_editor_plugin.cpp

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions editor/plugins/visual_shader_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ class VisualShaderNodePlugin : public RefCounted {
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);

Expand Down Expand Up @@ -140,6 +168,7 @@ class VisualShaderGraphPlugin : public RefCounted {
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();
Expand Down Expand Up @@ -297,6 +326,7 @@ class VisualShaderEditor : public VBoxContainer {

enum ConnectionMenuOptions {
INSERT_NEW_NODE,
INSERT_NEW_REROUTE,
DISCONNECT,
};

Expand Down
17 changes: 16 additions & 1 deletion editor/themes/editor_theme_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.
Expand Down
12 changes: 7 additions & 5 deletions scene/gui/graph_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}

Expand Down
13 changes: 13 additions & 0 deletions scene/gui/graph_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions scene/gui/graph_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class GraphNode : public GraphElement {

bool port_pos_dirty = true;

bool ignore_invalid_connection_type = false;

void _port_pos_update();

protected:
Expand Down Expand Up @@ -147,6 +149,9 @@ class GraphNode : public GraphElement {
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);
Expand Down
1 change: 1 addition & 0 deletions scene/register_scene_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
96 changes: 92 additions & 4 deletions scene/resources/visual_shader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);

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(!is_port_types_compatible(from_port_type, to_port_type), ERR_INVALID_PARAMETER, "Incompatible port types (scalar/vec/bool) with transform.");
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) {
Expand Down Expand Up @@ -1904,12 +1986,18 @@ 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 {
inputs[i] = "";
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;
Expand Down
4 changes: 4 additions & 0 deletions scene/resources/visual_shader.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ class VisualShader : public Shader {
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();
Expand Down Expand Up @@ -229,6 +231,8 @@ class VisualShader : public Shader {
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;

Expand Down
Loading
Loading