Skip to content

Commit

Permalink
Further improvements to intelligent patching mode 4.
Browse files Browse the repository at this point in the history
Made the algorithm a lot more flexible by applying some heuristics to
estimate whether the selected objects are primarily layed out in a
vertical (row-based) or in a horizontal (column-based) configuration.

Thus, in addition to the common case of vertical connections, the
algorithm now also deals with horizontal arrangements which occur in
some use cases such as ladder structures.

Furthermore, the algorithm tries to cope with situations where the
target object comes *before* the source object, by reversing the order
of the primary coordinate. E.g., this will allow you to make back
connections running from bottom to top instead of the usual
downward-pointing connections.

This means that mode 4 offers more possibilities for connections now,
which comes at the cost of permitting some corner cases where the
results are less predictable. But the results should still be a lot more
predictable than with the old implementation.
  • Loading branch information
agraef committed Sep 29, 2024
1 parent 78708d5 commit e24d83a
Showing 1 changed file with 122 additions and 36 deletions.
158 changes: 122 additions & 36 deletions pd/src/g_editor.c
Original file line number Diff line number Diff line change
Expand Up @@ -4453,13 +4453,16 @@ static int grid_coord(int x)
return (int)((x+PIX_GRID/2)/PIX_GRID);
}

static int sel_order = 0; // 0 = vertical, 1 = horizontal
static int sel_order_r = 1; // -1 = reverse rows or columns

static int check_wrap(t_gobj *x, int *last)
{
t_text *y = (t_text *)(x);
if (grid_coord(y->te_xpix) < *last) {
t_text *y = (t_text *)x;
if (grid_coord(sel_order?y->te_ypix:y->te_xpix) < *last) {
return 1;
} else {
*last = grid_coord(y->te_xpix);
*last = grid_coord(sel_order?y->te_ypix:y->te_xpix);
return 0;
}
}
Expand All @@ -4469,32 +4472,108 @@ typedef struct _sel_map {
t_selection *sel;
} t_sel_map;

static int analyze_order(t_sel_map *sel, int n_selected,
t_gobj *y1, t_gobj *y2, int *ip1, int *ip2)
{
int i1 = -1, i2 = -1, count1 = 0, count2 = 0, skip = 1, last = -1;
for (int i = 0; i < n_selected; i++)
{
t_gobj *x = sel[i].sel->sel_what;
if (!skip && check_wrap(x, &last)) {
if (i1 != -1 || i2 != -1) {
skip = 1;
last = -1;
}
}
if (x == y1) {
i1 = i;
count1 = 1; skip = 0;
} else if (x == y2) {
i2 = i;
count2 = 1; skip = 0;
} else if (!skip) {
if (i1 != -1 && i2 < i1)
count1++;
if (i2 != -1 && i1 < i2)
count2++;
}
}
*ip1 = i1; *ip2 = i2;
return count2 > count1 ? count2 : count1;
}

static int sel_compare(const void *x, const void *y)
{
t_sel_map *xs = (t_sel_map*)x;
t_sel_map *ys = (t_sel_map*)y;
t_text *xt =(t_text*)xs->sel->sel_what;
t_text *yt =(t_text*)ys->sel->sel_what;
if (grid_coord(xt->te_ypix) == grid_coord(yt->te_ypix))
return grid_coord(xt->te_xpix) - grid_coord(yt->te_xpix);
else
return grid_coord(xt->te_ypix) - grid_coord(yt->te_ypix);
if (sel_order) {
if (grid_coord(xt->te_xpix) == grid_coord(yt->te_xpix))
return grid_coord(xt->te_ypix) - grid_coord(yt->te_ypix);
else
return sel_order_r *
(grid_coord(xt->te_xpix) - grid_coord(yt->te_xpix));
} else {
if (grid_coord(xt->te_ypix) == grid_coord(yt->te_ypix))
return grid_coord(xt->te_xpix) - grid_coord(yt->te_xpix);
else
return sel_order_r *
(grid_coord(xt->te_ypix) - grid_coord(yt->te_ypix));
}
}

void canvas_sort_selection_according_to_location(t_canvas *x)
void canvas_sort_selection_according_to_location(t_canvas *x, t_gobj *y1, t_gobj *y2)
{
int n_selected = glist_selectionindex(x, 0, 1); // get all selected objects
//fprintf(stderr,"n_selected = %d\n", n_selected);
t_selection *traverse;
t_sel_map sel[n_selected];
// vertical order = top-to-bottom, left-to-right
// horizontal order = left-to-right, top-to-bottom
t_sel_map sel_v[n_selected], sel_h[n_selected];
int i = 0;
for (traverse = x->gl_editor->e_selection; traverse; traverse = traverse->sel_next)
{
sel[i].sel = traverse;
sel[i].i = i;
sel_v[i].sel = traverse;
sel_v[i].i = i;
sel_h[i].sel = traverse;
sel_h[i].i = i;
i++;
}
qsort(sel, n_selected, sizeof(t_sel_map), sel_compare);
// compute both orders
int i1 = -1, i2 = -1;
sel_order_r = 1;
sel_order = 0;
qsort(sel_v, n_selected, sizeof(t_sel_map), sel_compare);
int count_v = analyze_order(sel_v, n_selected, y1, y2, &i1, &i2);
int offs_v = i2 - i1;
sel_order = 1;
qsort(sel_h, n_selected, sizeof(t_sel_map), sel_compare);
int count_h = analyze_order(sel_h, n_selected, y1, y2, &i1, &i2);
int offs_h = i2 - i1;
// Assess whether the vertical or the horizontal order is most likely,
// based on the number of columns and rows in the resulting pairing of
// objects, respectively. Break ties in favor of the vertical order.
sel_order = 0;
t_sel_map *sel = sel_v;
int offs = offs_v;
if (count_v < count_h) {
sel_order = 1;
sel = sel_h;
offs = offs_h;
}
if (offs < 0) {
// If i2 < i1 then the target y2 comes before source y1 in the
// computed order. Our algorithm assumes that i1 < i2. We can try to
// fix up the order on the fly by reversing the primary coordinate.
// This won't do any good if both y1 and y2 are at the same primary
// coordinate, but then the whole bipartite pairing of sources and
// targets won't work anyway, and the algorithm will detect this error
// condition later.
sel_order_r = -1;
qsort(sel, n_selected, sizeof(t_sel_map), sel_compare);
}
// apply the constructed order
x->gl_editor->e_selection = sel[0].sel;
for (i = 0; i < n_selected-1; i++)
{
Expand Down Expand Up @@ -4981,8 +5060,13 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit)
Since both y1 and y2 are selected, there's no a-priori way to tell
which of the selected objects will be originating and which will be
receiving connections, so a visual top-to-bottom and left-to-right
order is used instead. To keep things simple, let's imagine that
receiving connections, so the selection is sorted based on the
positions of the objects on the canvas instead. The present
implementation assumes that the selection can be organized into
rows or columns with the originating object in one of these, and
the receiving object in another. A heuristic is used to determine
which configuration is most likely, based on the layout and the
positions of y1 and y2. To keep things simple, let's imagine that
the objects are arranged in two rows a, b as depicted below, where
y1 = a[i] and y2 = b[j]:
Expand Down Expand Up @@ -5010,34 +5094,36 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit)
b[j+1], ... as possible, using the same inlet and outlet numbers
as in the initial connection.
Note that in any case, the operation will fail if y2 comes *before*
y1 in the visual order, since connections will always go to objects
which come later in that order. In addition, the algorithm enforces
a strict top-to-bottom, left-to-right order of doing connections.
This limits the possible connections, but makes the results more
predictable and more likely to resemble the user's intentions.
In the vertical, row-based order sketched out above, the algorithm
enforces a strict top-to-bottom, left-to-right order of doing
connections. Likewise, in the horizontal, column-based order,
connections are done from left-to-right, top-to-bottom. This limits
the possible connections, but makes the results more predictable
and more likely to resemble the user's intentions.
Option C is only taken if the ctrl key is pressed while doing the
initial connection. Otherwise, the operation automatically chooses
fan-out or fan-in depending on which option gives you the larger
number of connections, breaking ties in favor of fan-out.
CAVEAT: This operation is not 100% foolproof. :) The current
CAVEAT: This operation is not 100% foolproof. The current
implementation doesn't really try to arrange the selected objects
into two neat rows as depicted above, it only goes by the
precomputed linear top-to-bottom, left-to-right order. In the real
world, patches tend to be messy, with objects rarely being lined up
perfectly. The algorithm tries to account for this by rounding
coordinates to a 10 pixel grid (see grid_coord() above), but you
can help it along by employing the tidy-up operation in the edit
menu to line things up properly beforehand.
It also helps if you avoid ambiguities by selecting minimal 'a' and
'b' sets of objects which will give you the connections that you
want. In particular, if the algorithm gives you a fan-out, but you
actually wanted a fan-in instead, try deselecting all 'b' objects
except the one that you want to fan into. In the same way, a
fan-out can be forced by selecting only a single 'a' object. */
into two neat rows or columns as depicted above, it only goes by
the precomputed linear selection order. In the real world, patches
tend to be messy, with objects rarely being lined up perfectly. The
algorithm tries to account for this by rounding coordinates to a
grid (see grid_coord() above), but you can also help it along by
employing the tidy-up operation in the edit menu to line things up
beforehand.
It also helps if the positions of the selected objects at least
roughly follow the row- or column-based layout assumed by the
algorithm. Moreover, you can avoid ambiguities by selecting minimal
'a' and 'b' sets of objects which will give you the connections
that you want. In particular, if the algorithm gives you a fan-out,
but you actually wanted a fan-in instead, try deselecting all 'b'
objects except the one that you want to fan into. In the same way,
a fan-out can be forced by selecting only a single 'a' object. */
else if (x->gl_editor->e_selection->sel_next->sel_next &&
glist_isselected(x, y1) && glist_isselected(x, y2))
{
Expand Down Expand Up @@ -5083,7 +5169,7 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit)
int i, i1 = -1, i2 = -1;
int last_x = -1;
// re-order the selection
canvas_sort_selection_according_to_location(x);
canvas_sort_selection_according_to_location(x, y1, y2);

if (glob_ctrl) {
// OPTION C (N:N multi-connect)
Expand Down

0 comments on commit e24d83a

Please sign in to comment.