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 | |