Skip to content

Commit

Permalink
[VisualShader] Add reroute node and improve port drawing
Browse files Browse the repository at this point in the history
  • Loading branch information
Geometror committed May 13, 2024
1 parent bdc0316 commit 6277684
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 78 deletions.
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

0 comments on commit 6277684

Please sign in to comment.