Code

adapt code to new Maybe/bbox regime
[inkscape.git] / src / dialogs / unclump.cpp
1 #define __UNCLUMP_C__
3 /*
4  * Unclumping objects
5  *
6  * Authors:
7  *   bulia byak
8  *
9  * Copyright (C) 2005 Authors
10  * Released under GNU GPL
11  */
14 #include <map>
15 #include "libnr/nr-matrix-ops.h"
16 #include "sp-item.h"
19 // Taking bbox of an item is an expensive operation, and we need to do it many times, so here we
20 // cache the centers, widths, and heights of items
22 //FIXME: make a class with these cashes as members instead of globals 
23 std::map<const gchar *, NR::Point> c_cache;
24 std::map<const gchar *, NR::Point> wh_cache;
26 /**
27 Center of bbox of item
28 */
29 NR::Point
30 unclump_center (SPItem *item)
31 {
32     std::map<const gchar *, NR::Point>::iterator i = c_cache.find(SP_OBJECT_ID(item));
33     if ( i != c_cache.end() ) {
34         return i->second;
35     }
37     NR::Maybe<NR::Rect> r = item->getBounds(sp_item_i2d_affine(item));
38     if (r) {
39         NR::Point const c = r->midpoint();
40         c_cache[SP_OBJECT_ID(item)] = c;
41         return c; 
42     } else {
43         // FIXME
44         return NR::Point(0, 0);
45     }
46 }
48 NR::Point
49 unclump_wh (SPItem *item)
50 {
51     NR::Point wh;
52     std::map<const gchar *, NR::Point>::iterator i = wh_cache.find(SP_OBJECT_ID(item));
53     if ( i != wh_cache.end() ) {
54         wh = i->second;
55     } else {
56         NR::Maybe<NR::Rect> r = item->getBounds(sp_item_i2d_affine(item));
57         if (r) {
58             wh = r->dimensions();
59             wh_cache[SP_OBJECT_ID(item)] = wh;
60         } else {
61             wh = NR::Point(0, 0);
62         }
63     }
65     return wh;
66 }
68 /**
69 Distance between "edges" of item1 and item2. An item is considered to be an ellipse inscribed into its w/h, 
70 so its radius (distance from center to edge) depends on the w/h and the angle towards the other item.
71 May be negative if the edge of item1 is between the center and the edge of item2.
72 */
73 double
74 unclump_dist (SPItem *item1, SPItem *item2)
75 {
76         NR::Point c1 = unclump_center (item1);
77         NR::Point c2 = unclump_center (item2);
79         NR::Point wh1 = unclump_wh (item1);
80         NR::Point wh2 = unclump_wh (item2);
82         // angle from each item's center to the other's, unsqueezed by its w/h, normalized to 0..pi/2
83         double a1 = atan2 ((c2 - c1)[NR::Y], (c2 - c1)[NR::X] * wh1[NR::Y]/wh1[NR::X]);
84         a1 = fabs (a1);
85         if (a1 > M_PI/2) a1 = M_PI - a1;
87         double a2 = atan2 ((c1 - c2)[NR::Y], (c1 - c2)[NR::X] * wh2[NR::Y]/wh2[NR::X]);
88         a2 = fabs (a2);
89         if (a2 > M_PI/2) a2 = M_PI - a2;
91         // get the radius of each item for the given angle
92         double r1 = 0.5 * (wh1[NR::X] + (wh1[NR::Y] - wh1[NR::X]) * (a1/(M_PI/2)));
93         double r2 = 0.5 * (wh2[NR::X] + (wh2[NR::Y] - wh2[NR::X]) * (a2/(M_PI/2)));
95         // dist between centers minus angle-adjusted radii
96         double dist_r =  (NR::L2 (c2 - c1) - r1 - r2);
98         double stretch1 = wh1[NR::Y]/wh1[NR::X];
99         double stretch2 = wh2[NR::Y]/wh2[NR::X];
101         if ((stretch1 > 1.5 || stretch1 < 0.66) && (stretch2 > 1.5 || stretch2 < 0.66)) {
103                 std::vector<double> dists;
104                 dists.push_back (dist_r);
106                 // If both objects are not circle-like, find dists between four corners
107                 std::vector<NR::Point> c1_points(2);
108                 {
109                         double y_closest;
110                         if (c2[NR::Y] > c1[NR::Y] + wh1[NR::Y]/2) {
111                                 y_closest = c1[NR::Y] + wh1[NR::Y]/2;
112                         } else if (c2[NR::Y] < c1[NR::Y] - wh1[NR::Y]/2) {
113                                 y_closest = c1[NR::Y] - wh1[NR::Y]/2;
114                         } else {
115                                 y_closest = c2[NR::Y];
116                         }
117                         c1_points[0] = NR::Point (c1[NR::X], y_closest);
118                         double x_closest;
119                         if (c2[NR::X] > c1[NR::X] + wh1[NR::X]/2) {
120                                 x_closest = c1[NR::X] + wh1[NR::X]/2;
121                         } else if (c2[NR::X] < c1[NR::X] - wh1[NR::X]/2) {
122                                 x_closest = c1[NR::X] - wh1[NR::X]/2;
123                         } else {
124                                 x_closest = c2[NR::X];
125                         }
126                         c1_points[1] = NR::Point (x_closest, c1[NR::Y]);
127                 }
130                 std::vector<NR::Point> c2_points(2);
131                 {
132                         double y_closest;
133                         if (c1[NR::Y] > c2[NR::Y] + wh2[NR::Y]/2) {
134                                 y_closest = c2[NR::Y] + wh2[NR::Y]/2;
135                         } else if (c1[NR::Y] < c2[NR::Y] - wh2[NR::Y]/2) {
136                                 y_closest = c2[NR::Y] - wh2[NR::Y]/2;
137                         } else {
138                                 y_closest = c1[NR::Y];
139                         }
140                         c2_points[0] = NR::Point (c2[NR::X], y_closest);
141                         double x_closest;
142                         if (c1[NR::X] > c2[NR::X] + wh2[NR::X]/2) {
143                                 x_closest = c2[NR::X] + wh2[NR::X]/2;
144                         } else if (c1[NR::X] < c2[NR::X] - wh2[NR::X]/2) {
145                                 x_closest = c2[NR::X] - wh2[NR::X]/2;
146                         } else {
147                                 x_closest = c1[NR::X];
148                         }
149                         c2_points[1] = NR::Point (x_closest, c2[NR::Y]);
150                 }
152                 for (int i = 0; i < 2; i ++) {
153                         for (int j = 0; j < 2; j ++) {
154                                 dists.push_back (NR::L2 (c1_points[i] - c2_points[j]));
155                         }
156                 }
158                 // return the minimum of all dists
159                 return *std::min_element(dists.begin(), dists.end());
160         } else {
161                 return dist_r;
162         }
165 /**
166 Average unclump_dist from item to others
167 */
168 double unclump_average (SPItem *item, GSList *others)
170     int n = 0;
171     double sum = 0;
173     for (GSList *i = others; i != NULL; i = i->next) {
174         SPItem *other = SP_ITEM (i->data);
176         if (other == item)
177             continue;
179         n++;
180         sum += unclump_dist (item, other);
181     }
183     if (n != 0)
184         return sum/n;
185     else
186         return 0;
189 /**
190 Closest to item among others
191  */
192 SPItem *unclump_closest (SPItem *item, GSList *others)
194     double min = HUGE_VAL;
195     SPItem *closest = NULL;
197     for (GSList *i = others; i != NULL; i = i->next) {
198         SPItem *other = SP_ITEM (i->data);
200         if (other == item)
201             continue;
203         double dist = unclump_dist (item, other);
204         if (dist < min && fabs (dist) < 1e6) {
205             min = dist;
206             closest = other;
207         }
208     }
210     return closest;
213 /**
214 Most distant from item among others
215  */
216 SPItem *unclump_farest (SPItem *item, GSList *others)
218     double max = -HUGE_VAL;
219     SPItem *farest = NULL;
221     for (GSList *i = others; i != NULL; i = i->next) {
222         SPItem *other = SP_ITEM (i->data);
224         if (other == item)
225             continue;
227         double dist = unclump_dist (item, other);
228         if (dist > max && fabs (dist) < 1e6) {
229             max = dist;
230             farest = other;
231         }
232     }
234     return farest;
237 /**
238 Removes from the \a rest list those items that are "behind" \a closest as seen from \a item,
239 i.e. those on the other side of the line through \a closest perpendicular to the direction from \a
240 item to \a closest. Returns a newly created list which must be freed.
241  */
242 GSList *
243 unclump_remove_behind (SPItem *item, SPItem *closest, GSList *rest)
245     NR::Point it = unclump_center (item);
246     NR::Point p1 = unclump_center (closest);
248     // perpendicular through closest to the direction to item:
249     NR::Point perp = NR::rot90(it - p1);
250     NR::Point p2 = p1 + perp;
252     // get the standard Ax + By + C = 0 form for p1-p2:
253     double A = p1[NR::Y] - p2[NR::Y];
254     double B = p2[NR::X] - p1[NR::X];
255     double C = p2[NR::Y] * p1[NR::X] - p1[NR::Y] * p2[NR::X];
257     // substitute the item into it:
258     double val_item = A * it[NR::X] + B * it[NR::Y] + C;
260     GSList *out = NULL;
262     for (GSList *i = rest; i != NULL; i = i->next) {
263         SPItem *other = SP_ITEM (i->data);
265         if (other == item)
266             continue;
268         NR::Point o = unclump_center (other);
269         double val_other = A * o[NR::X] + B * o[NR::Y] + C;
271         if (val_item * val_other <= 1e-6) {
272             // different signs, which means item and other are on the different sides of p1-p2 line; skip
273         } else {
274             out = g_slist_prepend (out, other);
275         }
276     }
278     return out;
281 /**
282 Moves \a what away from \a from by \a dist
283  */
284 void
285 unclump_push (SPItem *from, SPItem *what, double dist)
287     NR::Point it = unclump_center (what);
288     NR::Point p = unclump_center (from);
289     NR::Point by = dist * NR::unit_vector (- (p - it));
291     NR::Matrix move = NR::Matrix (NR::translate (by));
293     std::map<const gchar *, NR::Point>::iterator i = c_cache.find(SP_OBJECT_ID(what));
294     if ( i != c_cache.end() ) {
295         i->second *= move;
296     }
298     //g_print ("push %s at %g,%g from %g,%g by %g,%g, dist %g\n", SP_OBJECT_ID(what), it[NR::X],it[NR::Y], p[NR::X],p[NR::Y], by[NR::X],by[NR::Y], dist);
300     sp_item_set_i2d_affine(what, sp_item_i2d_affine(what) * move);
301     sp_item_write_transform(what, SP_OBJECT_REPR(what), what->transform, NULL);
304 /**
305 Moves \a what towards \a to by \a dist
306  */
307 void
308 unclump_pull (SPItem *to, SPItem *what, double dist)
310     NR::Point it = unclump_center (what);
311     NR::Point p = unclump_center (to);
312     NR::Point by = dist * NR::unit_vector (p - it);
314     NR::Matrix move = NR::Matrix (NR::translate (by));
316     std::map<const gchar *, NR::Point>::iterator i = c_cache.find(SP_OBJECT_ID(what));
317     if ( i != c_cache.end() ) {
318         i->second *= move;
319     }
321     //g_print ("pull %s at %g,%g to %g,%g by %g,%g, dist %g\n", SP_OBJECT_ID(what), it[NR::X],it[NR::Y], p[NR::X],p[NR::Y], by[NR::X],by[NR::Y], dist);
323     sp_item_set_i2d_affine(what, sp_item_i2d_affine(what) * move);
324     sp_item_write_transform(what, SP_OBJECT_REPR(what), what->transform, NULL);
328 /**
329 Unclumps the items in \a items, reducing local unevenness in their distribution. Produces an effect
330 similar to "engraver dots". The only distribution which is unchanged by unclumping is a hexagonal
331 grid. May be called repeatedly for stronger effect. 
332  */
333 void
334 unclump (GSList *items)
336     c_cache.clear();
337     wh_cache.clear();
339     for (GSList *i = items; i != NULL; i = i->next) { //  for each original/clone x: 
340         SPItem *item = SP_ITEM (i->data);
342         GSList *nei = NULL;
344         GSList *rest = g_slist_copy (items);
345         rest = g_slist_remove (rest, item);
347         while (rest != NULL) {
348             SPItem *closest = unclump_closest (item, rest);
349             if (closest) {
350                 nei = g_slist_prepend (nei, closest);
351                 rest = g_slist_remove (rest, closest);
352                 GSList *new_rest = unclump_remove_behind (item, closest, rest);
353                 g_slist_free (rest);
354                 rest = new_rest;
355             } else {
356                 g_slist_free (rest);
357                 break;
358             }
359         } 
361         if (g_slist_length (nei) >= 2) {
362             double ave = unclump_average (item, nei);
364             SPItem *closest = unclump_closest (item, nei);
365             SPItem *farest = unclump_farest (item, nei);
367             double dist_closest = unclump_dist (closest, item);
368             double dist_farest = unclump_dist (farest, item);
370             //g_print ("NEI %d for item %s    closest %s at %g  farest %s at %g  ave %g\n", g_slist_length(nei), SP_OBJECT_ID(item), SP_OBJECT_ID(closest), dist_closest, SP_OBJECT_ID(farest), dist_farest, ave);
372             if (fabs (ave) < 1e6 && fabs (dist_closest) < 1e6 && fabs (dist_farest) < 1e6) { // otherwise the items are bogus
373                 // increase these coefficients to make unclumping more aggressive and less stable
374                 // the pull coefficient is a bit bigger to counteract the long-term expansion trend
375                 unclump_push (closest, item, 0.3 * (ave - dist_closest)); 
376                 unclump_pull (farest, item, 0.35 * (dist_farest - ave));
377             }
378         }
379     }