| 1 | /************************************************************************* | 
|---|
| 2 | * Copyright (c) 2011 AT&T Intellectual Property | 
|---|
| 3 | * All rights reserved. This program and the accompanying materials | 
|---|
| 4 | * are made available under the terms of the Eclipse Public License v1.0 | 
|---|
| 5 | * which accompanies this distribution, and is available at | 
|---|
| 6 | * http://www.eclipse.org/legal/epl-v10.html | 
|---|
| 7 | * | 
|---|
| 8 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ | 
|---|
| 9 | *************************************************************************/ | 
|---|
| 10 |  | 
|---|
| 11 | #include <math.h> | 
|---|
| 12 | #include <stdlib.h> | 
|---|
| 13 | #include "types.h" | 
|---|
| 14 | #include "globals.h" | 
|---|
| 15 | #include "general.h" | 
|---|
| 16 | #include "SparseMatrix.h" | 
|---|
| 17 | #include "edge_bundling.h" | 
|---|
| 18 | #include "ink.h" | 
|---|
| 19 |  | 
|---|
| 20 | double ink_count; | 
|---|
| 21 |  | 
|---|
| 22 | static point_t addPoint (point_t a, point_t b) | 
|---|
| 23 | { | 
|---|
| 24 | a.x += b.x; | 
|---|
| 25 | a.y += b.y; | 
|---|
| 26 | return a; | 
|---|
| 27 | } | 
|---|
| 28 |  | 
|---|
| 29 | static point_t subPoint (point_t a, point_t b) | 
|---|
| 30 | { | 
|---|
| 31 | a.x -= b.x; | 
|---|
| 32 | a.y -= b.y; | 
|---|
| 33 | return a; | 
|---|
| 34 | } | 
|---|
| 35 |  | 
|---|
| 36 | static point_t scalePoint (point_t a, double d) | 
|---|
| 37 | { | 
|---|
| 38 | a.x *= d; | 
|---|
| 39 | a.y *= d; | 
|---|
| 40 | return a; | 
|---|
| 41 | } | 
|---|
| 42 |  | 
|---|
| 43 | static double dotPoint(point_t a, point_t b){ | 
|---|
| 44 | return a.x*b.x + a.y*b.y; | 
|---|
| 45 | } | 
|---|
| 46 |  | 
|---|
| 47 |  | 
|---|
| 48 | point_t Origin; | 
|---|
| 49 |  | 
|---|
| 50 | /* sumLengths: | 
|---|
| 51 | */ | 
|---|
| 52 | static double sumLengths_avoid_bad_angle(point_t* points, int npoints, point_t end, point_t meeting, real angle_param) | 
|---|
| 53 | { | 
|---|
| 54 | /* avoid sharp turns, we want cos_theta to be as close to -1 as possible */ | 
|---|
| 55 | int i; | 
|---|
| 56 | double len0, len, sum = 0; | 
|---|
| 57 | double diff_x, diff_y, diff_x0, diff_y0; | 
|---|
| 58 | double cos_theta, cos_max = -10; | 
|---|
| 59 |  | 
|---|
| 60 | diff_x0 = end.x-meeting.x; | 
|---|
| 61 | diff_y0 = end.y-meeting.y; | 
|---|
| 62 | len0 = sum = sqrt(diff_x0*diff_x0+diff_y0*diff_y0); | 
|---|
| 63 |  | 
|---|
| 64 | // distance form each of 'points' till 'meeting' | 
|---|
| 65 | for (i=0; i<npoints; i++) { | 
|---|
| 66 | diff_x = points[i].x-meeting.x; | 
|---|
| 67 | diff_y = points[i].y-meeting.y; | 
|---|
| 68 | len = sqrt(diff_x*diff_x+diff_y*diff_y); | 
|---|
| 69 | sum += len; | 
|---|
| 70 | cos_theta = (diff_x0*diff_x + diff_y0*diff_y)/MAX((len*len0),0.00001); | 
|---|
| 71 | cos_max = MAX(cos_max, cos_theta); | 
|---|
| 72 | } | 
|---|
| 73 |  | 
|---|
| 74 | // distance of single line from 'meeting' to 'end' | 
|---|
| 75 | return sum*(cos_max + angle_param);/* straight line gives angle_param - 1, turning angle of 180 degree gives angle_param + 1 */ | 
|---|
| 76 | } | 
|---|
| 77 | static double sumLengths(point_t* points, int npoints, point_t end, point_t meeting) | 
|---|
| 78 | { | 
|---|
| 79 | int i; | 
|---|
| 80 | double sum = 0; | 
|---|
| 81 | double diff_x, diff_y; | 
|---|
| 82 |  | 
|---|
| 83 | // distance form each of 'points' till 'meeting' | 
|---|
| 84 | for (i=0; i<npoints; i++) { | 
|---|
| 85 | diff_x = points[i].x-meeting.x; | 
|---|
| 86 | diff_y = points[i].y-meeting.y; | 
|---|
| 87 | sum += sqrt(diff_x*diff_x+diff_y*diff_y); | 
|---|
| 88 | } | 
|---|
| 89 | // distance of single line from 'meeting' to 'end' | 
|---|
| 90 | diff_x = end.x-meeting.x; | 
|---|
| 91 | diff_y = end.y-meeting.y; | 
|---|
| 92 | sum += sqrt(diff_x*diff_x+diff_y*diff_y); | 
|---|
| 93 | return sum; | 
|---|
| 94 | } | 
|---|
| 95 |  | 
|---|
| 96 | /* bestInk: | 
|---|
| 97 | */ | 
|---|
| 98 | static double bestInk(point_t* points, int npoints, point_t begin, point_t end, double prec, point_t *meet, real angle_param) | 
|---|
| 99 | { | 
|---|
| 100 | point_t first, second, third, fourth, diff, meeting; | 
|---|
| 101 | double value1, value2, value3, value4; | 
|---|
| 102 |  | 
|---|
| 103 | first = begin; | 
|---|
| 104 | fourth = end; | 
|---|
| 105 |  | 
|---|
| 106 | do { | 
|---|
| 107 | diff = subPoint(fourth,first); | 
|---|
| 108 | second = addPoint(first,scalePoint(diff,1.0/3.0)); | 
|---|
| 109 | third = addPoint(first,scalePoint(diff,2.0/3.0)); | 
|---|
| 110 |  | 
|---|
| 111 | if (angle_param < 1){ | 
|---|
| 112 | value1 = sumLengths(points, npoints, end, first); | 
|---|
| 113 | value2 = sumLengths(points, npoints, end, second); | 
|---|
| 114 | value3 = sumLengths(points, npoints, end, third); | 
|---|
| 115 | value4 = sumLengths(points, npoints, end, fourth); | 
|---|
| 116 | } else { | 
|---|
| 117 | value1 = sumLengths_avoid_bad_angle(points, npoints, end, first, angle_param); | 
|---|
| 118 | value2 = sumLengths_avoid_bad_angle(points, npoints, end, second, angle_param); | 
|---|
| 119 | value3 = sumLengths_avoid_bad_angle(points, npoints, end, third, angle_param); | 
|---|
| 120 | value4 = sumLengths_avoid_bad_angle(points, npoints, end, fourth, angle_param); | 
|---|
| 121 | } | 
|---|
| 122 |  | 
|---|
| 123 | if (value1<value2) { | 
|---|
| 124 | if (value1<value3) { | 
|---|
| 125 | if (value1<value4) { | 
|---|
| 126 | // first is smallest | 
|---|
| 127 | fourth = second; | 
|---|
| 128 | } | 
|---|
| 129 | else { | 
|---|
| 130 | // fourth is smallest | 
|---|
| 131 | first = third; | 
|---|
| 132 | } | 
|---|
| 133 | } | 
|---|
| 134 | else { | 
|---|
| 135 | if (value3<value4) { | 
|---|
| 136 | // third is smallest | 
|---|
| 137 | first = second; | 
|---|
| 138 | } | 
|---|
| 139 | else { | 
|---|
| 140 | // fourth is smallest | 
|---|
| 141 | first = third; | 
|---|
| 142 | } | 
|---|
| 143 | } | 
|---|
| 144 | } | 
|---|
| 145 | else { | 
|---|
| 146 | if (value2<value3) { | 
|---|
| 147 | if (value2<value4) { | 
|---|
| 148 | // second is smallest | 
|---|
| 149 | fourth = third; | 
|---|
| 150 | } | 
|---|
| 151 | else { | 
|---|
| 152 | // fourth is smallest | 
|---|
| 153 | first = third; | 
|---|
| 154 | } | 
|---|
| 155 | } | 
|---|
| 156 | else { | 
|---|
| 157 | if (value3<value4) { | 
|---|
| 158 | // third is smallest | 
|---|
| 159 | first = second; | 
|---|
| 160 | } | 
|---|
| 161 | else { | 
|---|
| 162 | // fourth is smallest | 
|---|
| 163 | first = third; | 
|---|
| 164 | } | 
|---|
| 165 | } | 
|---|
| 166 | } | 
|---|
| 167 | } while (fabs(value1-value4)/(MIN(value1,value4)+1e-10)>prec && dotPoint(diff, diff) > 1.e-20); | 
|---|
| 168 |  | 
|---|
| 169 | meeting = scalePoint(addPoint(first,fourth),0.5); | 
|---|
| 170 | *meet = meeting; | 
|---|
| 171 |  | 
|---|
| 172 | return sumLengths(points, npoints, end, meeting); | 
|---|
| 173 |  | 
|---|
| 174 | } | 
|---|
| 175 |  | 
|---|
| 176 | static double project_to_line(point_t pt, point_t left, point_t right, real angle){ | 
|---|
| 177 | /* pt | 
|---|
| 178 | ^  ^ | 
|---|
| 179 | .   \ \ | 
|---|
| 180 | .    \   \ | 
|---|
| 181 | d .    a\    \ | 
|---|
| 182 | .      \     \ | 
|---|
| 183 | .       \       \ | 
|---|
| 184 | .   c    \  alpha \             b | 
|---|
| 185 | .<------left:0 ----------------------------> right:1. Find the projection of pt on the left--right line. If the turning angle is small, | 
|---|
| 186 | |                  | | 
|---|
| 187 | |<-------f--------- | 
|---|
| 188 | we should get a negative number. Let a := left->pt, b := left->right, then we are calculating: | 
|---|
| 189 | c = |a| cos(a,b)/|b| b | 
|---|
| 190 | d = a - c | 
|---|
| 191 | f = -ctan(alpha)*|d|/|b| b | 
|---|
| 192 | and the project of alpha degree on the left->right line is | 
|---|
| 193 | c-f = |a| cos(a,b)/|b| b - -ctan(alpha)*|d|/|b| b | 
|---|
| 194 | = (|a| a.b/(|a||b|) + ctan(alpha)|a-c|)/|b| b | 
|---|
| 195 | = (a.b/|b| + ctan(alpha)|a-c|)/|b| b | 
|---|
| 196 | the dimentionless projection is: | 
|---|
| 197 | a.b/|b|^2 + ctan(alpha)|a-c|/|b| | 
|---|
| 198 | = a.b/|b|^2 + ctan(alpha)|d|/|b| | 
|---|
| 199 | */ | 
|---|
| 200 |  | 
|---|
| 201 |  | 
|---|
| 202 | point_t b, a; | 
|---|
| 203 | real bnorm, dnorm; | 
|---|
| 204 | real alpha, ccord; | 
|---|
| 205 |  | 
|---|
| 206 | if (angle <=0 || angle >= M_PI) return 2;/* return outside of the interval which should be handled as a sign of infeasible turning angle */ | 
|---|
| 207 | alpha = angle; | 
|---|
| 208 |  | 
|---|
| 209 | assert(alpha > 0 && alpha < M_PI); | 
|---|
| 210 | b = subPoint(right, left); | 
|---|
| 211 | a = subPoint(pt, left); | 
|---|
| 212 | bnorm = MAX(1.e-10, dotPoint(b, b)); | 
|---|
| 213 | ccord = dotPoint(b, a)/bnorm; | 
|---|
| 214 | dnorm = dotPoint(a,a)/bnorm - ccord*ccord; | 
|---|
| 215 | if (alpha == M_PI/2){ | 
|---|
| 216 | return ccord; | 
|---|
| 217 | } else { | 
|---|
| 218 | //    assert(dnorm >= MIN(-1.e-5, -1.e-5*bnorm)); | 
|---|
| 219 | return ccord + sqrt(MAX(0, dnorm))/tan(alpha); | 
|---|
| 220 | } | 
|---|
| 221 | } | 
|---|
| 222 |  | 
|---|
| 223 |  | 
|---|
| 224 |  | 
|---|
| 225 |  | 
|---|
| 226 |  | 
|---|
| 227 |  | 
|---|
| 228 |  | 
|---|
| 229 |  | 
|---|
| 230 |  | 
|---|
| 231 | /* ink: | 
|---|
| 232 | * Compute minimal ink used the input edges are bundled. | 
|---|
| 233 | * Assumes tails all occur on one side and heads on the other. | 
|---|
| 234 | */ | 
|---|
| 235 | double ink(pedge* edges, int numEdges, int *pick, double *ink0, point_t *meet1, point_t *meet2, real angle_param, real angle) | 
|---|
| 236 | { | 
|---|
| 237 | int i; | 
|---|
| 238 | point_t begin, end, mid, diff; | 
|---|
| 239 | pedge e; | 
|---|
| 240 | real *x; | 
|---|
| 241 | point_t* sources = N_NEW(numEdges, point_t); | 
|---|
| 242 | point_t* targets = N_NEW(numEdges, point_t); | 
|---|
| 243 | double inkUsed; | 
|---|
| 244 | //double eps = 1.0e-2; | 
|---|
| 245 | double eps = 1.0e-2; | 
|---|
| 246 | double cend = 0, cbegin = 0; | 
|---|
| 247 | double wgt = 0; | 
|---|
| 248 |  | 
|---|
| 249 | //  fprintf(stderr,"in ink code ========\n"); | 
|---|
| 250 | ink_count += numEdges; | 
|---|
| 251 |  | 
|---|
| 252 | *ink0 = 0; | 
|---|
| 253 |  | 
|---|
| 254 | /* canonicalize so that edges 1,2,3 and 3,2,1 gives the same optimal ink */ | 
|---|
| 255 | if (pick) vector_sort_int(numEdges, pick, TRUE); | 
|---|
| 256 |  | 
|---|
| 257 | begin = end = Origin; | 
|---|
| 258 | for (i = 0; i < numEdges; i++) { | 
|---|
| 259 | if (pick) { | 
|---|
| 260 | e = edges[pick[i]]; | 
|---|
| 261 | } else { | 
|---|
| 262 | e = edges[i]; | 
|---|
| 263 | } | 
|---|
| 264 | x = e->x; | 
|---|
| 265 | sources[i].x = x[0]; | 
|---|
| 266 | sources[i].y = x[1]; | 
|---|
| 267 | targets[i].x = x[e->dim*e->npoints - e->dim]; | 
|---|
| 268 | targets[i].y = x[e->dim*e->npoints - e->dim + 1]; | 
|---|
| 269 | (*ink0) += sqrt((sources[i].x - targets[i].x)*(sources[i].x - targets[i].x) + (sources[i].y - targets[i].y)*(sources[i].y - targets[i].y)); | 
|---|
| 270 | begin = addPoint (begin, scalePoint(sources[i], e->wgt)); | 
|---|
| 271 | end = addPoint (end, scalePoint(targets[i], e->wgt)); | 
|---|
| 272 | wgt += e->wgt; | 
|---|
| 273 | //fprintf(stderr,"source={%f,%f}, target = {%f,%f}\n",sources[i].x, sources[i].y, | 
|---|
| 274 | //targets[i].x, targets[i].y); | 
|---|
| 275 | } | 
|---|
| 276 |  | 
|---|
| 277 | begin = scalePoint (begin, 1.0/wgt); | 
|---|
| 278 | end = scalePoint (end, 1.0/wgt); | 
|---|
| 279 |  | 
|---|
| 280 |  | 
|---|
| 281 | if (numEdges == 1){ | 
|---|
| 282 | *meet1 = begin; | 
|---|
| 283 | *meet2 = end; | 
|---|
| 284 | //fprintf(stderr,"ink used = %f\n",*ink0); | 
|---|
| 285 | free (sources); | 
|---|
| 286 | free (targets); | 
|---|
| 287 | return *ink0; | 
|---|
| 288 | } | 
|---|
| 289 |  | 
|---|
| 290 | /* shift the begin and end point to avoid sharp turns */ | 
|---|
| 291 | for (i = 0; i < numEdges; i++) { | 
|---|
| 292 | if (pick) { | 
|---|
| 293 | e = edges[pick[i]]; | 
|---|
| 294 | } else { | 
|---|
| 295 | e = edges[i]; | 
|---|
| 296 | } | 
|---|
| 297 | x = e->x; | 
|---|
| 298 | sources[i].x = x[0]; | 
|---|
| 299 | sources[i].y = x[1]; | 
|---|
| 300 | targets[i].x = x[e->dim*e->npoints - e->dim]; | 
|---|
| 301 | targets[i].y = x[e->dim*e->npoints - e->dim + 1]; | 
|---|
| 302 | /* begin(1) ----------- mid(0) */ | 
|---|
| 303 | if (i == 0){ | 
|---|
| 304 | cbegin = project_to_line(sources[i], begin, end, angle); | 
|---|
| 305 | cend =   project_to_line(targets[i], end, begin, angle); | 
|---|
| 306 | } else { | 
|---|
| 307 | cbegin = MAX(cbegin, project_to_line(sources[i], begin, end, angle)); | 
|---|
| 308 | cend = MAX(cend, project_to_line(targets[i], end, begin, angle)); | 
|---|
| 309 | } | 
|---|
| 310 | } | 
|---|
| 311 |  | 
|---|
| 312 | if (angle > 0 && angle < M_PI){ | 
|---|
| 313 | if (cbegin + cend > 1 || cbegin > 1 || cend > 1){ | 
|---|
| 314 | /* no point can be found that satisfies the angular constraints, so we give up and set ink to a large value */ | 
|---|
| 315 | if (Verbose && 0) fprintf(stderr, "no point satisfying any angle constraints can be found. cbeg=%f cend=%f\n",cbegin,cend); | 
|---|
| 316 | inkUsed = 1000*(*ink0); | 
|---|
| 317 | *meet1 = *meet2 = mid; | 
|---|
| 318 | free (sources); | 
|---|
| 319 | free (targets); | 
|---|
| 320 | return inkUsed; | 
|---|
| 321 | } | 
|---|
| 322 | /* make sure the turning angle is no more than alpha degree */ | 
|---|
| 323 | cbegin = MAX(0, cbegin);/* make sure the new adjusted point is with in [begin,end] internal */ | 
|---|
| 324 | diff = subPoint(end, begin); | 
|---|
| 325 | begin = addPoint(begin, scalePoint(diff, cbegin)); | 
|---|
| 326 |  | 
|---|
| 327 | cend = MAX(0, cend);/* make sure the new adjusted point is with in [end,begin] internal */ | 
|---|
| 328 | end = subPoint(end, scalePoint(diff, cend)); | 
|---|
| 329 | } | 
|---|
| 330 | mid = scalePoint (addPoint(begin,end),0.5); | 
|---|
| 331 |  | 
|---|
| 332 | inkUsed = (bestInk (sources, numEdges, begin, mid, eps, meet1, angle_param) | 
|---|
| 333 | + bestInk (targets, numEdges, end, mid, eps, meet2, angle_param)); | 
|---|
| 334 | //fprintf(stderr,"beg={%f,%f}, meet1={%f,%f}, meet2={%f,%f}, mid={%f,%f}, end={%f,%f}\n",begin.x, begin.y, meet1->x, meet1->y, meet2->x, meet2->y, | 
|---|
| 335 | //mid.x, mid.y, end.x, end.y); | 
|---|
| 336 |  | 
|---|
| 337 | //fprintf(stderr,"ink used = %f\n",inkUsed); | 
|---|
| 338 | //  fprintf(stderr,"{cb,ce}={%f, %f} end={%f,%f}, meet={%f,%f}, mid={%f,%f}\n",cbegin, cend, end.x, end.y, meet2->x, meet2->y, mid.x, mid.y); | 
|---|
| 339 | free (sources); | 
|---|
| 340 | free (targets); | 
|---|
| 341 | return inkUsed; | 
|---|
| 342 | } | 
|---|
| 343 |  | 
|---|
| 344 |  | 
|---|
| 345 | double ink1(pedge e){ | 
|---|
| 346 |  | 
|---|
| 347 |  | 
|---|
| 348 | real *x, xx, yy; | 
|---|
| 349 |  | 
|---|
| 350 | real ink0 = 0; | 
|---|
| 351 |  | 
|---|
| 352 | x = e->x; | 
|---|
| 353 | xx = x[0] - x[e->dim*e->npoints - e->dim]; | 
|---|
| 354 | yy = x[1] - x[e->dim*e->npoints - e->dim + 1]; | 
|---|
| 355 | ink0 += sqrt(xx*xx + yy*yy); | 
|---|
| 356 | return ink0; | 
|---|
| 357 | } | 
|---|
| 358 |  | 
|---|