1 /* Dia -- an diagram creation/manipulation program
2 * Support for computing bounding boxes
3 * Copyright (C) 2001 Cyrille Chepelov
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 /** \file boundingbox.c This code is nothing but a fancy pile of FPU crap. */
21
22 #include <config.h>
23
24 #define _BSD_SOURCE 1
25 #include <math.h>
26 #include <string.h> /* memcmp() */
27
28 #include <glib.h>
29
30 #include "geometry.h"
31 #include "boundingbox.h"
32
33 /** Translates x- or y- part of bezier points to Bernstein polynom coefficients
34 * @param p x- or y-part of the four points
35 * @param A
36 * @param B
37 * @param C
38 * @param D
39 * See: Foley et al., Computer Graphics, Bezier Curves or
40 * http://en.wikipedia.org/wiki/B%C3%A9zier_curve
41 */
42 void
bernstein_develop(const real p[4],real * A,real * B,real * C,real * D)43 bernstein_develop(const real p[4], real *A, real *B, real *C, real *D)
44 {
45 *A = -p[0]+3*p[1]-3*p[2]+p[3];
46 *B = 3*p[0]-6*p[1]+3*p[2];
47 *C = -3*p[0]+3*p[1];
48 *D = p[0];
49 /* if Q(u)=Sum(i=0..3)piBi(u) (Bi(u) being the Bernstein stuff),
50 then Q(u)=Au^3+Bu^2+Cu+p[0]. */
51 }
52
53 /** Evaluates the Bernstein polynoms for a given position
54 * @param p x- or y-values of four points describing the bezier
55 * @param u position on the curve [0 .. 1]
56 * @returns the evaluate x- or y-part of the point
57 */
58 real
bezier_eval(const real p[4],real u)59 bezier_eval(const real p[4], real u)
60 {
61 real A,B,C,D;
62 bernstein_develop(p,&A,&B,&C,&D);
63 return A*u*u*u+B*u*u+C*u+D;
64 }
65
66 /** Calculates the tangent for a given point on a bezier curve
67 * @param p x- or y-values of four points describing the bezier
68 * @param u position on the curve between[0 .. 1]
69 * @return the x- or y-part of the tangent vector
70 */
71 real
bezier_eval_tangent(const real p[4],real u)72 bezier_eval_tangent(const real p[4], real u)
73 {
74 real A,B,C,D;
75 bernstein_develop(p,&A,&B,&C,&D);
76 return 3*A*u*u+2*B*u+C;
77 }
78
79 /**
80 * Calculates the extrma of the given curve in x- or y-direction.
81 * @param p x- or y-values of four points describing the bezier
82 * @param u The position of the extrema [0 .. 1]
83 * @return The number of extrema found.
84 */
85 static int
bicubicbezier_extrema(const real p[4],real u[2])86 bicubicbezier_extrema(const real p[4],real u[2])
87 {
88 real A,B,C,D,delta;
89
90 bernstein_develop(p,&A,&B,&C,&D);
91 delta = 4*B*B - 12*A*C;
92
93 u[0] = u[1] = 0.0;
94 if (delta<0) return 0;
95
96 /* just a quadratic contribution? */
97 if (fabs(A) < 1e-6) {
98 u[0] = -C/(2*B);
99 return 1;
100 }
101
102 u[0] = (-2*B + sqrt(delta)) / (6*A);
103 if (delta==0) return 1;
104 u[1] = (-2*B - sqrt(delta)) / (6*A);
105 return 2;
106 }
107
108 /** Add to a bounding box the area covered by a standard arrow.
109 * @param rect The bounding box to adjust
110 * @param vertex The end point of the arrow.
111 * @param normed_dir The normalized direction of the arrow (i.e. 1 cm in the
112 * direction the arrow points from.
113 * @param extra_long ???
114 * @param extra_trans ???
115 * @bug I don't know what the last two arguments do.
116 */
117 static void
add_arrow_rectangle(Rectangle * rect,const Point * vertex,const Point * normed_dir,real extra_long,real extra_trans)118 add_arrow_rectangle(Rectangle *rect,
119 const Point *vertex,
120 const Point *normed_dir,
121 real extra_long,real extra_trans)
122 {
123 Point vl,vt,pt;
124 vl = *normed_dir;
125
126 point_get_perp(&vt,&vl);
127 point_copy_add_scaled(&pt,vertex,&vl,extra_long);
128 point_add_scaled(&pt,&vt,extra_trans);
129 rectangle_add_point(rect,&pt);
130 point_add_scaled(&pt,&vt,-2.0 * extra_trans);
131 rectangle_add_point(rect,&pt);
132 point_add_scaled(&pt,&vl,-2.0 * extra_long);
133 rectangle_add_point(rect,&pt);
134 point_add_scaled(&pt,&vt,2.0 * extra_trans);
135 rectangle_add_point(rect,&pt);
136 }
137
138 /** Calculate the boundingbox for a 2D bezier curve segment.
139 * @param p0 start point
140 * @param p1 1st control point
141 * @param p2 2nd control point
142 * @param p3 end point
143 * @param extra information about extra space from linewidth and arrow to add to the bounding box
144 * @param rect The rectangle that the segment fits inside.
145 */
146 void
bicubicbezier2D_bbox(const Point * p0,const Point * p1,const Point * p2,const Point * p3,const PolyBBExtras * extra,Rectangle * rect)147 bicubicbezier2D_bbox(const Point *p0,const Point *p1,
148 const Point *p2,const Point *p3,
149 const PolyBBExtras *extra,
150 Rectangle *rect)
151 {
152 real x[4],y[4];
153 Point vl,vt,p,tt;
154 real *xy;
155 int i,extr;
156 real u[2];
157
158 rect->left = rect->right = p0->x;
159 rect->top = rect->bottom = p0->y;
160
161 rectangle_add_point(rect,p3);
162 /* start point */
163 point_copy_add_scaled(&vl,p0,p1,-1);
164 point_normalize(&vl);
165 add_arrow_rectangle(rect,p0,&vl,extra->start_long,MAX(extra->start_trans,
166 extra->middle_trans));
167
168 /* end point */
169 point_copy_add_scaled(&vl,p3,p2,-1);
170 point_normalize(&vl);
171 add_arrow_rectangle(rect,p3,&vl,extra->end_long,MAX(extra->end_trans,
172 extra->middle_trans));
173
174 /* middle part */
175 x[0] = p0->x; x[1] = p1->x; x[2] = p2->x; x[3] = p3->x;
176 y[0] = p0->y; y[1] = p1->y; y[2] = p2->y; y[3] = p3->y;
177
178 for (xy = x; xy ; xy=(xy==x?y:NULL) ) { /* sorry */
179 extr = bicubicbezier_extrema(xy,u);
180 for (i=0;i<extr;i++) {
181 if ((u[i]<0) || (u[i]>1)) continue;
182 p.x = bezier_eval(x,u[i]);
183 vl.x = bezier_eval_tangent(x,u[i]);
184 p.y = bezier_eval(y,u[i]);
185 vl.y = bezier_eval_tangent(y,u[i]);
186 point_normalize(&vl);
187 point_get_perp(&vt,&vl);
188
189 point_copy_add_scaled(&tt,&p,&vt,extra->middle_trans);
190 rectangle_add_point(rect,&tt);
191 point_copy_add_scaled(&tt,&p,&vt,-extra->middle_trans);
192 rectangle_add_point(rect,&tt);
193 }
194 }
195 }
196
197 /** Calculate the bounding box for a simple line.
198 * @param p1 One end of the line.
199 * @param p2 The other end of the line.
200 * @param extra Extra information
201 * @param rect The box that the line and extra stuff fits inside.
202 */
203 void
line_bbox(const Point * p1,const Point * p2,const LineBBExtras * extra,Rectangle * rect)204 line_bbox(const Point *p1, const Point *p2,
205 const LineBBExtras *extra,
206 Rectangle *rect)
207 {
208 Point vl;
209
210 rect->left = rect->right = p1->x;
211 rect->top = rect->bottom = p1->y;
212
213 rectangle_add_point(rect,p2); /* as a safety, so we don't need to care if it above or below p1 */
214
215 point_copy_add_scaled(&vl,p1,p2,-1);
216 point_normalize(&vl);
217 add_arrow_rectangle(rect,p1,&vl,extra->start_long,extra->start_trans);
218 point_scale(&vl,-1);
219 add_arrow_rectangle(rect,p2,&vl,extra->end_long,extra->end_trans);
220 }
221
222 /** Calculate the bounding box of an ellipse.
223 * @param centre The center point of the ellipse.
224 * @param width The width of the ellipse.
225 * @param height The height of the ellipse.
226 * @param extra Extra information required.
227 * @param rect The bounding box that the ellipse fits inside.
228 * @bug describe what the extra information is.
229 */
230 void
ellipse_bbox(const Point * centre,real width,real height,const ElementBBExtras * extra,Rectangle * rect)231 ellipse_bbox(const Point *centre, real width, real height,
232 const ElementBBExtras *extra,
233 Rectangle *rect)
234 {
235 Rectangle rin;
236 rin.left = centre->x - width/2;
237 rin.right = centre->x + width/2;
238 rin.top = centre->y - height/2;
239 rin.bottom = centre->y + height/2;
240
241 rectangle_bbox(&rin,extra,rect);
242 }
243
244 /** Allocate some scratch space to hold a big enough Bezier.
245 * That space is not guaranteed to be preserved upon the next allocation
246 * (in fact it's guaranteed it's not).
247 * @param numpoints How many points of bezier to allocate space for.
248 * @returns Newly allocated array of points.
249 */
250 static BezPoint *
alloc_polybezier_space(int numpoints)251 alloc_polybezier_space(int numpoints)
252 {
253 static int alloc_np = 0;
254 static BezPoint *alloced = NULL;
255
256 if (alloc_np < numpoints) {
257 g_free(alloced);
258 alloc_np = numpoints;
259 alloced = g_new0(BezPoint,numpoints);
260 }
261 return alloced;
262 }
263
264 /** Free the scratch space allocated above.
265 * @param points Previously allocated list of points.
266 * @note Doesn't actually free it, as alloc_polybezier_space does that.
267 * @bug Should explain the strange freeing model, or fix it.
268 */
269 static void
free_polybezier_space(BezPoint * points)270 free_polybezier_space(BezPoint *points)
271 { /* dummy */ }
272
273 /** Calculate the boundingbox for a polyline.
274 * @param pts Array of points.
275 * @param numpoints Number of elements in `pts'.
276 * @param extra Extra space information
277 * @param closed Whether the polyline is closed or not.
278 * @param rect Return value: The bounding box that includes the points and
279 * extra spacing.
280 * @bug Surely doesn't need to use bezier code, but remember extra stuff.
281 */
282 void
polyline_bbox(const Point * pts,int numpoints,const PolyBBExtras * extra,gboolean closed,Rectangle * rect)283 polyline_bbox(const Point *pts, int numpoints,
284 const PolyBBExtras *extra, gboolean closed,
285 Rectangle *rect)
286 {
287 /* It's much easier to re-use the Bezier code... */
288 int i;
289 BezPoint *bpts = alloc_polybezier_space(numpoints + 1);
290
291 bpts[0].type = BEZ_MOVE_TO;
292 bpts[0].p1 = pts[0];
293
294 for (i=1;i<numpoints;i++) {
295 bpts[i].type = BEZ_LINE_TO;
296 bpts[i].p1 = pts[i];
297 }
298 /* This one will be used only when closed==TRUE... */
299 bpts[numpoints].type = BEZ_LINE_TO;
300 bpts[numpoints].p1 = pts[0];
301
302 polybezier_bbox(bpts,numpoints + (closed?1:0),extra,closed,rect);
303 free_polybezier_space(bpts);
304 }
305
306 /** Calculate a bounding box for a set of bezier points.
307 * @param pts The bezier points
308 * @param numpoints The number of elements in `pts'
309 * @param extra Extra spacing information.
310 * @param closed True if the bezier points form a closed line.
311 * @param rect Return value: The enclosing rectangle will be stored here.
312 * @bug This function is way too long (214 lines) and should be split.
313 */
314 void
polybezier_bbox(const BezPoint * pts,int numpoints,const PolyBBExtras * extra,gboolean closed,Rectangle * rect)315 polybezier_bbox(const BezPoint *pts, int numpoints,
316 const PolyBBExtras *extra, gboolean closed,
317 Rectangle *rect)
318 {
319 Point vx,vn,vsc,vp;
320 int i,prev,next;
321 Rectangle rt;
322 PolyBBExtras bextra,start_bextra,end_bextra;
323 LineBBExtras lextra,start_lextra,end_lextra,full_lextra;
324 gboolean start,end;
325
326 vp.x=0;
327 vp.y=0;
328
329 g_assert(pts[0].type == BEZ_MOVE_TO);
330
331 rect->left = rect->right = pts[0].p1.x;
332 rect->top = rect->bottom = pts[0].p1.y;
333
334 /* First, we build derived BBExtras structures, so we have something to feed
335 the primitives. */
336 if (!closed) {
337 start_lextra.start_long = extra->start_long;
338 start_lextra.start_trans = MAX(extra->start_trans,extra->middle_trans);
339 start_lextra.end_long = 0;
340 start_lextra.end_trans = extra->middle_trans;
341
342 end_lextra.start_long = 0;
343 end_lextra.start_trans = extra->middle_trans;
344 end_lextra.end_long = extra->end_long;
345 end_lextra.end_trans = MAX(extra->end_trans,extra->middle_trans);
346 }
347
348 full_lextra.start_long = extra->start_long;
349 full_lextra.start_trans = MAX(extra->start_trans,extra->middle_trans);
350 full_lextra.end_long = extra->end_long;
351 full_lextra.end_trans = MAX(extra->end_trans,extra->middle_trans);
352
353 if (!closed) {
354 lextra.start_long = 0;
355 lextra.start_trans = extra->middle_trans;
356 lextra.end_long = 0;
357 lextra.end_trans = extra->middle_trans;
358
359 start_bextra.start_long = extra->start_long;
360 start_bextra.start_trans = extra->start_trans;
361 start_bextra.middle_trans = extra->middle_trans;
362 start_bextra.end_long = 0;
363 start_bextra.end_trans = extra->middle_trans;
364
365 end_bextra.start_long = 0;
366 end_bextra.start_trans = extra->middle_trans;
367 end_bextra.middle_trans = extra->middle_trans;
368 end_bextra.end_long = extra->end_long;
369 end_bextra.end_trans = extra->end_trans;
370 }
371
372 bextra.start_long = 0;
373 bextra.start_trans = extra->middle_trans;
374 bextra.middle_trans = extra->middle_trans;
375 bextra.end_long = 0;
376 bextra.end_trans = extra->middle_trans;
377
378
379 for (i=1;i<numpoints;i++) {
380 next = (i+1) % numpoints;
381 prev = (i-1) % numpoints;
382 if (closed && (next == 0)) next=1;
383 if (closed && (prev == 0)) prev=numpoints-1;
384
385 /* We have now:
386 i = index of current vertex.
387 prev,next: index of previous/next vertices (of the control polygon)
388
389 We want:
390 vp, vx, vn: the previous, current and next vertices;
391 start, end: TRUE if we're at an end of poly (then, vp and/or vn are not
392 valid, respectively).
393
394 Some values *will* be recomputed a few times across iterations (but stored in
395 different boxes). Either gprof says it's a real problem, or gcc finally gets
396 a clue.
397 */
398
399 if (pts[i].type == BEZ_MOVE_TO) {
400 continue;
401 }
402
403 switch(pts[i].type) {
404 case BEZ_MOVE_TO:
405 g_assert_not_reached();
406 break;
407 case BEZ_LINE_TO:
408 point_copy(&vx,&pts[i].p1);
409 switch(pts[prev].type) {
410 case BEZ_MOVE_TO:
411 case BEZ_LINE_TO:
412 point_copy(&vsc,&pts[prev].p1);
413 point_copy(&vp,&pts[prev].p1);
414 break;
415 case BEZ_CURVE_TO:
416 point_copy(&vsc,&pts[prev].p3);
417 point_copy(&vp,&pts[prev].p3);
418 break;
419 }
420 break;
421 case BEZ_CURVE_TO:
422 point_copy(&vx,&pts[i].p3);
423 point_copy(&vp,&pts[i].p2);
424 switch(pts[prev].type) {
425 case BEZ_MOVE_TO:
426 case BEZ_LINE_TO:
427 point_copy(&vsc,&pts[prev].p1);
428 break;
429 case BEZ_CURVE_TO:
430 point_copy(&vsc,&pts[prev].p3);
431 break;
432 } /* vsc is the start of the curve. */
433
434 break;
435 }
436 start = (pts[prev].type == BEZ_MOVE_TO);
437 end = (pts[next].type == BEZ_MOVE_TO);
438 point_copy(&vn,&pts[next].p1); /* whichever type pts[next] is. */
439
440 /* Now, we know about a few vertices around the one we're dealing with.
441 Depending on the shape of the (previous,current) segment, and whether
442 it's a middle or end segment, we'll be doing different stuff. */
443 if (closed) {
444 if (pts[i].type == BEZ_LINE_TO) {
445 line_bbox(&vsc,&vx,&full_lextra,&rt);
446 } else {
447 bicubicbezier2D_bbox(&vsc,
448 &pts[i].p1,&pts[i].p2,&pts[i].p3,
449 &bextra,
450 &rt);
451 }
452 } else if (start) {
453 if (pts[i].type == BEZ_LINE_TO) {
454 if (end) {
455 line_bbox(&vsc,&vx,&full_lextra,&rt);
456 } else {
457 line_bbox(&vsc,&vx,&start_lextra,&rt);
458 }
459 } else { /* BEZ_MOVE_TO */
460 if (end) {
461 bicubicbezier2D_bbox(&vsc,
462 &pts[i].p1,&pts[i].p2,&pts[i].p3,
463 extra,
464 &rt);
465 } else {
466 bicubicbezier2D_bbox(&vsc,
467 &pts[i].p1,&pts[i].p2,&pts[i].p3,
468 &start_bextra,
469 &rt);
470 }
471 }
472 } else if (end) { /* end but not start. Not closed anyway. */
473 if (pts[i].type == BEZ_LINE_TO) {
474 line_bbox(&vsc,&vx,&end_lextra,&rt);
475 } else {
476 bicubicbezier2D_bbox(&vsc,
477 &pts[i].p1,&pts[i].p2,&pts[i].p3,
478 &end_bextra,
479 &rt);
480 }
481 } else { /* normal case : middle segment (not closed shape). */
482 if (pts[i].type == BEZ_LINE_TO) {
483 line_bbox(&vsc,&vx,&lextra,&rt);
484 } else {
485 bicubicbezier2D_bbox(&vsc,
486 &pts[i].p1,&pts[i].p2,&pts[i].p3,
487 &bextra,
488 &rt);
489 }
490 }
491 rectangle_union(rect,&rt);
492
493 /* The following code enlarges a little the bounding box (if necessary) to
494 account with the "pointy corners" X (and PS) add when LINEJOIN_MITER mode is
495 in force. */
496
497 if (!end) { /* only the last segment might not produce overshoot. */
498 Point vpx,vxn;
499 real co,alpha;
500
501 point_copy_add_scaled(&vpx,&vx,&vp,-1);
502 point_normalize(&vpx);
503 point_copy_add_scaled(&vxn,&vn,&vx,-1);
504 point_normalize(&vxn);
505
506 co = point_dot(&vpx,&vxn);
507 if (co >= 1.0)
508 alpha = 0.0;
509 else if (co <= -1.0)
510 alpha = M_PI;
511 else
512 alpha = dia_acos(-co);
513 if (co > -0.9816) { /* 0.9816 = cos(11deg) */
514 /* we have a pointy join. */
515 real overshoot;
516 Point vovs,pto;
517
518 if (alpha > 0.0 && alpha < M_PI)
519 overshoot = extra->middle_trans / sin(alpha/2.0);
520 else /* prependicular? */
521 overshoot = extra->middle_trans;
522
523 point_copy_add_scaled(&vovs,&vpx,&vxn,-1);
524 point_normalize(&vovs);
525 point_copy_add_scaled(&pto,&vx,&vovs,overshoot);
526
527 rectangle_add_point(rect,&pto);
528 } else {
529 /* we don't have a pointy join. */
530 Point vpxt,vxnt,tmp;
531
532 point_get_perp(&vpxt,&vpx);
533 point_get_perp(&vxnt,&vxn);
534
535 point_copy_add_scaled(&tmp,&vx,&vpxt,1);
536 rectangle_add_point(rect,&tmp);
537 point_copy_add_scaled(&tmp,&vx,&vpxt,-1);
538 rectangle_add_point(rect,&tmp);
539 point_copy_add_scaled(&tmp,&vx,&vxnt,1);
540 rectangle_add_point(rect,&tmp);
541 point_copy_add_scaled(&tmp,&vx,&vxnt,-1);
542 rectangle_add_point(rect,&tmp);
543 }
544 }
545 }
546 }
547
548 /** Figure out a bounding box for a rectangle (fairly simple:)
549 * @param rin A rectangle to find bbox for.
550 * @param extra Extra information required to find bbox.
551 * @param rout Return value: The enclosing bounding box.
552 * @bug Describe extra info better.
553 */
554 void
rectangle_bbox(const Rectangle * rin,const ElementBBExtras * extra,Rectangle * rout)555 rectangle_bbox(const Rectangle *rin,
556 const ElementBBExtras *extra,
557 Rectangle *rout)
558 {
559 rout->left = rin->left - extra->border_trans;
560 rout->top = rin->top - extra->border_trans;
561 rout->right = rin->right + extra->border_trans;
562 rout->bottom = rin->bottom + extra->border_trans;
563 }
564
565
566 /* TODO: text_bbox ? */
567
568
569