summaryrefslogtreecommitdiffstats
path: root/scene/gui/graph_edit_arranger.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui/graph_edit_arranger.cpp')
-rw-r--r--scene/gui/graph_edit_arranger.cpp565
1 files changed, 565 insertions, 0 deletions
diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp
new file mode 100644
index 0000000000..f4d9dcbf95
--- /dev/null
+++ b/scene/gui/graph_edit_arranger.cpp
@@ -0,0 +1,565 @@
+/**************************************************************************/
+/* graph_edit_arranger.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 "graph_edit_arranger.h"
+
+#include "scene/gui/graph_edit.h"
+
+void GraphEditArranger::arrange_nodes() {
+ ERR_FAIL_COND(!graph_edit);
+
+ if (!arranging_graph) {
+ arranging_graph = true;
+ } else {
+ return;
+ }
+
+ Dictionary node_names;
+ HashSet<StringName> selected_nodes;
+
+ bool arrange_entire_graph = true;
+ for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
+ GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
+ if (!graph_element) {
+ continue;
+ }
+
+ node_names[graph_element->get_name()] = graph_element;
+
+ if (graph_element->is_selected()) {
+ arrange_entire_graph = false;
+ }
+ }
+
+ HashMap<StringName, HashSet<StringName>> upper_neighbours;
+ HashMap<StringName, Pair<int, int>> port_info;
+ Vector2 origin(FLT_MAX, FLT_MAX);
+
+ float gap_v = 100.0f;
+ float gap_h = 100.0f;
+
+ for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
+ GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
+ if (!graph_element) {
+ continue;
+ }
+
+ if (graph_element->is_selected() || arrange_entire_graph) {
+ selected_nodes.insert(graph_element->get_name());
+ HashSet<StringName> s;
+ List<GraphEdit::Connection> connection_list;
+ graph_edit->get_connection_list(&connection_list);
+ for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
+ GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from_node]);
+ if (E->get().to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to_node != E->get().from_node) {
+ if (!s.has(p_from->get_name())) {
+ s.insert(p_from->get_name());
+ }
+ String s_connection = String(p_from->get_name()) + " " + String(E->get().to_node);
+ StringName _connection(s_connection);
+ Pair<int, int> ports(E->get().from_port, E->get().to_port);
+ if (port_info.has(_connection)) {
+ Pair<int, int> p_ports = port_info[_connection];
+ if (p_ports.first < ports.first) {
+ ports = p_ports;
+ }
+ }
+ port_info.insert(_connection, ports);
+ }
+ }
+ upper_neighbours.insert(graph_element->get_name(), s);
+ }
+ }
+
+ if (!selected_nodes.size()) {
+ arranging_graph = false;
+ return;
+ }
+
+ HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours);
+ _crossing_minimisation(layers, upper_neighbours);
+
+ Dictionary root, align, sink, shift;
+ _horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes);
+
+ HashMap<StringName, Vector2> new_positions;
+ Vector2 default_position(FLT_MAX, FLT_MAX);
+ Dictionary inner_shift;
+ HashSet<StringName> block_heads;
+
+ for (const StringName &E : selected_nodes) {
+ inner_shift[E] = 0.0f;
+ sink[E] = E;
+ shift[E] = FLT_MAX;
+ new_positions.insert(E, default_position);
+ if ((StringName)root[E] == E) {
+ block_heads.insert(E);
+ }
+ }
+
+ _calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info);
+
+ for (const StringName &E : block_heads) {
+ _place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
+ }
+ origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]);
+ origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x;
+
+ for (const StringName &E : block_heads) {
+ StringName u = E;
+ float start_from = origin.y + new_positions[E].y;
+ do {
+ Vector2 cal_pos;
+ cal_pos.y = start_from + (real_t)inner_shift[u];
+ new_positions.insert(u, cal_pos);
+ u = align[u];
+ } while (u != E);
+ }
+
+ // Compute horizontal coordinates individually for layers to get uniform gap.
+ float start_from = origin.x;
+ float largest_node_size = 0.0f;
+
+ for (unsigned int i = 0; i < layers.size(); i++) {
+ Vector<StringName> layer = layers[i];
+ for (int j = 0; j < layer.size(); j++) {
+ float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x;
+ largest_node_size = MAX(largest_node_size, current_node_size);
+ }
+
+ for (int j = 0; j < layer.size(); j++) {
+ float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x;
+ Vector2 cal_pos = new_positions[layer[j]];
+
+ if (current_node_size == largest_node_size) {
+ cal_pos.x = start_from;
+ } else {
+ float current_node_start_pos = start_from;
+ if (current_node_size < largest_node_size / 2) {
+ if (!(i || j)) {
+ start_from -= (largest_node_size - current_node_size);
+ }
+ current_node_start_pos = start_from + largest_node_size - current_node_size;
+ }
+ cal_pos.x = current_node_start_pos;
+ }
+ new_positions.insert(layer[j], cal_pos);
+ }
+
+ start_from += largest_node_size + gap_h;
+ largest_node_size = 0.0f;
+ }
+
+ graph_edit->emit_signal(SNAME("begin_node_move"));
+ for (const StringName &E : selected_nodes) {
+ GraphNode *graph_node = Object::cast_to<GraphNode>(node_names[E]);
+ graph_node->set_drag(true);
+ Vector2 pos = (new_positions[E]);
+
+ if (graph_edit->is_snapping_enabled()) {
+ float snapping_distance = graph_edit->get_snapping_distance();
+ pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ }
+ graph_node->set_position_offset(pos);
+ graph_node->set_drag(false);
+ }
+ graph_edit->emit_signal(SNAME("end_node_move"));
+ arranging_graph = false;
+}
+
+int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) {
+ switch (p_operation) {
+ case GraphEditArranger::IS_EQUAL: {
+ for (const StringName &E : r_u) {
+ if (!r_v.has(E)) {
+ return 0;
+ }
+ }
+ return r_u.size() == r_v.size();
+ } break;
+ case GraphEditArranger::IS_SUBSET: {
+ if (r_u.size() == r_v.size() && !r_u.size()) {
+ return 1;
+ }
+ for (const StringName &E : r_u) {
+ if (!r_v.has(E)) {
+ return 0;
+ }
+ }
+ return 1;
+ } break;
+ case GraphEditArranger::DIFFERENCE: {
+ for (HashSet<StringName>::Iterator E = r_u.begin(); E;) {
+ HashSet<StringName>::Iterator N = E;
+ ++N;
+ if (r_v.has(*E)) {
+ r_u.remove(E);
+ }
+ E = N;
+ }
+ return r_u.size();
+ } break;
+ case GraphEditArranger::UNION: {
+ for (const StringName &E : r_v) {
+ if (!r_u.has(E)) {
+ r_u.insert(E);
+ }
+ }
+ return r_u.size();
+ } break;
+ default:
+ break;
+ }
+ return -1;
+}
+
+HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
+ HashMap<int, Vector<StringName>> l;
+
+ HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
+ int current_layer = 0;
+ bool selected = false;
+
+ while (!_set_operations(GraphEditArranger::IS_EQUAL, q, u)) {
+ _set_operations(GraphEditArranger::DIFFERENCE, p, u);
+ for (const StringName &E : p) {
+ HashSet<StringName> n = r_upper_neighbours[E];
+ if (_set_operations(GraphEditArranger::IS_SUBSET, n, z)) {
+ Vector<StringName> t;
+ t.push_back(E);
+ if (!l.has(current_layer)) {
+ l.insert(current_layer, Vector<StringName>{});
+ }
+ selected = true;
+ t.append_array(l[current_layer]);
+ l.insert(current_layer, t);
+ HashSet<StringName> V;
+ V.insert(E);
+ _set_operations(GraphEditArranger::UNION, u, V);
+ }
+ }
+ if (!selected) {
+ current_layer++;
+ uint32_t previous_size_z = z.size();
+ _set_operations(GraphEditArranger::UNION, z, u);
+ if (z.size() == previous_size_z) {
+ WARN_PRINT("Graph contains cycle(s). The cycle(s) will not be rearranged accurately.");
+ Vector<StringName> t;
+ if (l.has(0)) {
+ t.append_array(l[0]);
+ }
+ for (const StringName &E : p) {
+ t.push_back(E);
+ }
+ l.insert(0, t);
+ break;
+ }
+ }
+ selected = false;
+ }
+
+ return l;
+}
+
+Vector<StringName> GraphEditArranger::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) {
+ if (!r_layer.size()) {
+ return Vector<StringName>();
+ }
+
+ StringName p = r_layer[Math::random(0, r_layer.size() - 1)];
+ Vector<StringName> left;
+ Vector<StringName> right;
+
+ for (int i = 0; i < r_layer.size(); i++) {
+ if (p != r_layer[i]) {
+ StringName q = r_layer[i];
+ int cross_pq = r_crossings[p][q];
+ int cross_qp = r_crossings[q][p];
+ if (cross_pq > cross_qp) {
+ left.push_back(q);
+ } else {
+ right.push_back(q);
+ }
+ }
+ }
+
+ left.push_back(p);
+ left.append_array(right);
+ return left;
+}
+
+void GraphEditArranger::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) {
+ for (const StringName &E : r_selected_nodes) {
+ r_root[E] = E;
+ r_align[E] = E;
+ }
+
+ if (r_layers.size() == 1) {
+ return;
+ }
+
+ for (unsigned int i = 1; i < r_layers.size(); i++) {
+ Vector<StringName> lower_layer = r_layers[i];
+ Vector<StringName> upper_layer = r_layers[i - 1];
+ int r = -1;
+
+ for (int j = 0; j < lower_layer.size(); j++) {
+ Vector<Pair<int, StringName>> up;
+ StringName current_node = lower_layer[j];
+ for (int k = 0; k < upper_layer.size(); k++) {
+ StringName adjacent_neighbour = upper_layer[k];
+ if (r_upper_neighbours[current_node].has(adjacent_neighbour)) {
+ up.push_back(Pair<int, StringName>(k, adjacent_neighbour));
+ }
+ }
+
+ int start = (up.size() - 1) / 2;
+ int end = (up.size() - 1) % 2 ? start + 1 : start;
+ for (int p = start; p <= end; p++) {
+ StringName Align = r_align[current_node];
+ if (Align == current_node && r < up[p].first) {
+ r_align[up[p].second] = lower_layer[j];
+ r_root[current_node] = r_root[up[p].second];
+ r_align[current_node] = r_root[up[p].second];
+ r = up[p].first;
+ }
+ }
+ }
+ }
+}
+
+void GraphEditArranger::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
+ if (r_layers.size() == 1) {
+ return;
+ }
+
+ for (unsigned int i = 1; i < r_layers.size(); i++) {
+ Vector<StringName> upper_layer = r_layers[i - 1];
+ Vector<StringName> lower_layer = r_layers[i];
+ HashMap<StringName, Dictionary> c;
+
+ for (int j = 0; j < lower_layer.size(); j++) {
+ StringName p = lower_layer[j];
+ Dictionary d;
+
+ for (int k = 0; k < lower_layer.size(); k++) {
+ unsigned int crossings = 0;
+ StringName q = lower_layer[k];
+
+ if (j != k) {
+ for (int h = 1; h < upper_layer.size(); h++) {
+ if (r_upper_neighbours[p].has(upper_layer[h])) {
+ for (int g = 0; g < h; g++) {
+ if (r_upper_neighbours[q].has(upper_layer[g])) {
+ crossings++;
+ }
+ }
+ }
+ }
+ }
+ d[q] = crossings;
+ }
+ c.insert(p, d);
+ }
+
+ r_layers.insert(i, _split(lower_layer, c));
+ }
+}
+
+void GraphEditArranger::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
+ for (const StringName &E : r_block_heads) {
+ real_t left = 0;
+ StringName u = E;
+ StringName v = r_align[u];
+ while (u != v && (StringName)r_root[u] != v) {
+ String _connection = String(u) + " " + String(v);
+
+ GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[u]);
+ GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[v]);
+
+ Pair<int, int> ports = r_port_info[_connection];
+ int port_from = ports.first;
+ int port_to = ports.second;
+
+ Vector2 pos_from = gnode_from->get_connection_output_position(port_from) * graph_edit->get_zoom();
+ Vector2 pos_to = gnode_to->get_connection_input_position(port_to) * graph_edit->get_zoom();
+
+ real_t s = (real_t)r_inner_shifts[u] + (pos_from.y - pos_to.y) / graph_edit->get_zoom();
+ r_inner_shifts[v] = s;
+ left = MIN(left, s);
+
+ u = v;
+ v = (StringName)r_align[v];
+ }
+
+ u = E;
+ do {
+ r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left;
+ u = (StringName)r_align[u];
+ } while (u != E);
+ }
+}
+
+float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) {
+#define MAX_ORDER 2147483647
+#define ORDER(node, layers) \
+ for (unsigned int i = 0; i < layers.size(); i++) { \
+ int index = layers[i].find(node); \
+ if (index > 0) { \
+ order = index; \
+ break; \
+ } \
+ order = MAX_ORDER; \
+ }
+
+ int order = MAX_ORDER;
+ float threshold = p_current_threshold;
+ if (p_v == p_w) {
+ int min_order = MAX_ORDER;
+ GraphEdit::Connection incoming;
+ List<GraphEdit::Connection> connection_list;
+ graph_edit->get_connection_list(&connection_list);
+ for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
+ if (E->get().to_node == p_w) {
+ ORDER(E->get().from_node, r_layers);
+ if (min_order > order) {
+ min_order = order;
+ incoming = E->get();
+ }
+ }
+ }
+
+ if (incoming.from_node != StringName()) {
+ GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[incoming.from_node]);
+ GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[p_w]);
+ Vector2 pos_from = gnode_from->get_connection_output_position(incoming.from_port) * graph_edit->get_zoom();
+ Vector2 pos_to = gnode_to->get_connection_input_position(incoming.to_port) * graph_edit->get_zoom();
+
+ // If connected block node is selected, calculate thershold or add current block to list.
+ if (gnode_from->is_selected()) {
+ Vector2 connected_block_pos = r_node_positions[r_root[incoming.from_node]];
+ if (connected_block_pos.y != FLT_MAX) {
+ //Connected block is placed, calculate threshold.
+ threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming.from_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y;
+ }
+ }
+ }
+ }
+ if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) {
+ // This time, pick an outgoing edge and repeat as above!
+ int min_order = MAX_ORDER;
+ GraphEdit::Connection outgoing;
+ List<GraphEdit::Connection> connection_list;
+ graph_edit->get_connection_list(&connection_list);
+ for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
+ if (E->get().from_node == p_w) {
+ ORDER(E->get().to_node, r_layers);
+ if (min_order > order) {
+ min_order = order;
+ outgoing = E->get();
+ }
+ }
+ }
+
+ if (outgoing.to_node != StringName()) {
+ GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[p_w]);
+ GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[outgoing.to_node]);
+ Vector2 pos_from = gnode_from->get_connection_output_position(outgoing.from_port) * graph_edit->get_zoom();
+ Vector2 pos_to = gnode_to->get_connection_input_position(outgoing.to_port) * graph_edit->get_zoom();
+
+ // If connected block node is selected, calculate thershold or add current block to list.
+ if (gnode_to->is_selected()) {
+ Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to_node]];
+ if (connected_block_pos.y != FLT_MAX) {
+ //Connected block is placed. Calculate threshold
+ threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing.to_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y;
+ }
+ }
+ }
+ }
+#undef MAX_ORDER
+#undef ORDER
+ return threshold;
+}
+
+void GraphEditArranger::_place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) {
+#define PRED(node, layers) \
+ for (unsigned int i = 0; i < layers.size(); i++) { \
+ int index = layers[i].find(node); \
+ if (index > 0) { \
+ predecessor = layers[i][index - 1]; \
+ break; \
+ } \
+ predecessor = StringName(); \
+ }
+
+ StringName predecessor;
+ StringName successor;
+ Vector2 pos = r_node_positions[p_v];
+
+ if (pos.y == FLT_MAX) {
+ pos.y = 0;
+ bool initial = false;
+ StringName w = p_v;
+ real_t threshold = FLT_MIN;
+ do {
+ PRED(w, r_layers);
+ if (predecessor != StringName()) {
+ StringName u = r_root[predecessor];
+ _place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions);
+ threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
+ if ((StringName)r_sink[p_v] == p_v) {
+ r_sink[p_v] = r_sink[u];
+ }
+
+ Vector2 predecessor_root_pos = r_node_positions[u];
+ Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size();
+ if (r_sink[p_v] != r_sink[u]) {
+ real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta;
+ r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]);
+ } else {
+ real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta;
+ sb = MAX(sb, threshold);
+ if (initial) {
+ pos.y = sb;
+ } else {
+ pos.y = MAX(pos.y, sb);
+ }
+ initial = false;
+ }
+ }
+ threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
+ w = r_align[w];
+ } while (w != p_v);
+ r_node_positions.insert(p_v, pos);
+ }
+
+#undef PRED
+}