1 /* Dia -- an diagram creation/manipulation program
2 * Support for computing bounding boxes
3 * Copyright (C) 2001 Cyrille Chepelov
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.
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.
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.
20 /* This code is nothing but a fancy pile of FPU crap. */
26 #include "boundingbox.h"
29 bernstein_develop(const real p
[4],real
*A
,real
*B
,real
*C
,real
*D
)
31 *A
= -p
[0]+3*p
[1]-3*p
[2]+p
[3];
32 *B
= 3*p
[0]-6*p
[1]+3*p
[2];
35 /* if Q(u)=Sum(i=0..3)piBi(u) (Bi(u) being the Bernstein stuff),
36 then Q(u)=Au^3+Bu^2+Cu+p[0]. */
40 bezier_eval(const real p
[4],real u
) {
42 bernstein_develop(p
,&A
,&B
,&C
,&D
);
43 return A
*u
*u
*u
+B
*u
*u
+C
*u
+D
;
47 bezier_eval_tangent(const real p
[4],real u
) {
49 bernstein_develop(p
,&A
,&B
,&C
,&D
);
50 return 3*A
*u
*u
+2*B
*u
+C
;
54 bicubicbezier_extrema(const real p
[4],real u
[2])
58 bernstein_develop(p
,&A
,&B
,&C
,&D
);
59 delta
= 4*B
*B
- 12*A
*C
;
62 if (delta
<0) return 0;
64 u
[0] = (-2*B
+ sqrt(delta
)) / (6*A
);
65 if (delta
==0) return 1;
66 u
[1] = (-2*B
- sqrt(delta
)) / (6*A
);
71 add_arrow_rectangle(Rectangle
*rect
,
73 const Point
*normed_dir
,
74 real extra_long
,real extra_trans
)
79 point_get_perp(&vt
,&vl
);
80 point_copy_add_scaled(&pt
,vertex
,&vl
,extra_long
);
81 point_add_scaled(&pt
,&vt
,extra_trans
);
82 rectangle_add_point(rect
,&pt
);
83 point_add_scaled(&pt
,&vt
,-2.0 * extra_trans
);
84 rectangle_add_point(rect
,&pt
);
85 point_add_scaled(&pt
,&vl
,-2.0 * extra_long
);
86 rectangle_add_point(rect
,&pt
);
87 point_add_scaled(&pt
,&vt
,2.0 * extra_trans
);
88 rectangle_add_point(rect
,&pt
);
92 bicubicbezier2D_bbox(const Point
*p0
,const Point
*p1
,
93 const Point
*p2
,const Point
*p3
,
94 const PolyBBExtras
*extra
,
103 rect
->left
= rect
->right
= p0
->x
;
104 rect
->top
= rect
->bottom
= p0
->y
;
106 rectangle_add_point(rect
,p3
);
108 point_copy_add_scaled(&vl
,p0
,p1
,-1);
109 point_normalize(&vl
);
110 add_arrow_rectangle(rect
,p0
,&vl
,extra
->start_long
,MAX(extra
->start_trans
,
111 extra
->middle_trans
));
114 point_copy_add_scaled(&vl
,p3
,p2
,-1);
115 point_normalize(&vl
);
116 add_arrow_rectangle(rect
,p3
,&vl
,extra
->end_long
,MAX(extra
->end_trans
,
117 extra
->middle_trans
));
120 x
[0] = p0
->x
; x
[1] = p1
->x
; x
[2] = p2
->x
; x
[3] = p3
->x
;
121 y
[0] = p0
->y
; y
[1] = p1
->y
; y
[2] = p2
->y
; y
[3] = p3
->y
;
123 for (xy
= x
; xy
; xy
=(xy
==x
?y
:NULL
) ) { /* sorry */
124 extr
= bicubicbezier_extrema(xy
,u
);
125 for (i
=0;i
<extr
;i
++) {
126 if ((u
[i
]<0) || (u
[i
]>1)) continue;
127 p
.x
= bezier_eval(x
,u
[i
]);
128 vl
.x
= bezier_eval_tangent(x
,u
[i
]);
129 p
.y
= bezier_eval(y
,u
[i
]);
130 vl
.y
= bezier_eval_tangent(y
,u
[i
]);
131 point_normalize(&vl
);
132 point_get_perp(&vt
,&vl
);
134 point_copy_add_scaled(&tt
,&p
,&vt
,extra
->middle_trans
);
135 rectangle_add_point(rect
,&tt
);
136 point_copy_add_scaled(&tt
,&p
,&vt
,-extra
->middle_trans
);
137 rectangle_add_point(rect
,&tt
);
144 line_bbox(const Point
*p1
, const Point
*p2
,
145 const LineBBExtras
*extra
,
150 rect
->left
= rect
->right
= p1
->x
;
151 rect
->top
= rect
->bottom
= p1
->y
;
153 rectangle_add_point(rect
,p2
); /* as a safety */
155 point_copy_add_scaled(&vl
,p1
,p2
,-1);
156 point_normalize(&vl
);
157 add_arrow_rectangle(rect
,p1
,&vl
,extra
->start_long
,extra
->start_trans
);
159 add_arrow_rectangle(rect
,p2
,&vl
,extra
->end_long
,extra
->end_trans
);
163 ellipse_bbox(const Point
*centre
, real width
, real height
,
164 const ElementBBExtras
*extra
,
168 rin
.left
= centre
->x
- width
/2;
169 rin
.right
= centre
->x
+ width
/2;
170 rin
.top
= centre
->y
- height
/2;
171 rin
.bottom
= centre
->y
+ height
/2;
173 rectangle_bbox(&rin
,extra
,rect
);
177 alloc_polybezier_space(int numpoints
) {
178 /* Allocate some SCRATCH space to hold a big enough Bezier.
179 That space is not guaranteed to be preserved upon the next allocation
180 (in fact it's guaranteed it's not). */
181 static int alloc_np
= 0;
182 static BezPoint
*alloced
= NULL
;
184 if (alloc_np
< numpoints
) {
186 alloc_np
= numpoints
;
187 alloced
= g_new0(BezPoint
,numpoints
);
192 static void free_polybezier_space(BezPoint
*points
) { /* dummy */ }
195 polyline_bbox(const Point
*pts
, int numpoints
,
196 const PolyBBExtras
*extra
, gboolean closed
,
199 /* It's much easier to re-use the Bezier code... */
201 BezPoint
*bpts
= alloc_polybezier_space(numpoints
+ 1);
203 bpts
[0].type
= BEZ_MOVE_TO
;
206 for (i
=1;i
<numpoints
;i
++) {
207 bpts
[i
].type
= BEZ_LINE_TO
;
210 /* This one will be used only when closed==TRUE... */
211 bpts
[numpoints
].type
= BEZ_LINE_TO
;
212 bpts
[numpoints
].p1
= pts
[0];
214 polybezier_bbox(bpts
,numpoints
+ (closed
?1:0),extra
,closed
,rect
);
215 free_polybezier_space(bpts
);
219 polybezier_bbox(const BezPoint
*pts
, int numpoints
,
220 const PolyBBExtras
*extra
, gboolean closed
,
226 PolyBBExtras bextra
,start_bextra
,end_bextra
;
227 LineBBExtras lextra
,start_lextra
,end_lextra
,full_lextra
;
230 g_assert(pts
[0].type
== BEZ_MOVE_TO
);
232 rect
->left
= rect
->right
= pts
[0].p1
.x
;
233 rect
->top
= rect
->bottom
= pts
[0].p1
.y
;
235 /* First, we build derived BBExtras structures, so we have something to feed
238 start_lextra
.start_long
= extra
->start_long
;
239 start_lextra
.start_trans
= MAX(extra
->start_trans
,extra
->middle_trans
);
240 start_lextra
.end_long
= 0;
241 start_lextra
.end_trans
= extra
->middle_trans
;
243 end_lextra
.start_long
= 0;
244 end_lextra
.start_trans
= extra
->middle_trans
;
245 end_lextra
.end_long
= extra
->end_long
;
246 end_lextra
.end_trans
= MAX(extra
->end_trans
,extra
->middle_trans
);
249 full_lextra
.start_long
= extra
->start_long
;
250 full_lextra
.start_trans
= MAX(extra
->start_trans
,extra
->middle_trans
);
251 full_lextra
.end_long
= extra
->end_long
;
252 full_lextra
.end_trans
= MAX(extra
->end_trans
,extra
->middle_trans
);
255 lextra
.start_long
= 0;
256 lextra
.start_trans
= extra
->middle_trans
;
258 lextra
.end_trans
= extra
->middle_trans
;
260 start_bextra
.start_long
= extra
->start_long
;
261 start_bextra
.start_trans
= extra
->start_trans
;
262 start_bextra
.middle_trans
= extra
->middle_trans
;
263 start_bextra
.end_long
= 0;
264 start_bextra
.end_trans
= extra
->middle_trans
;
266 end_bextra
.start_long
= 0;
267 end_bextra
.start_trans
= extra
->middle_trans
;
268 end_bextra
.middle_trans
= extra
->middle_trans
;
269 end_bextra
.end_long
= extra
->end_long
;
270 end_bextra
.end_trans
= extra
->end_trans
;
273 bextra
.start_long
= 0;
274 bextra
.start_trans
= extra
->middle_trans
;
275 bextra
.middle_trans
= extra
->middle_trans
;
277 bextra
.end_trans
= extra
->middle_trans
;
280 for (i
=1;i
<numpoints
;i
++) {
281 next
= (i
+1) % numpoints
;
282 prev
= (i
-1) % numpoints
;
283 if (closed
&& (next
== 0)) next
=1;
284 if (closed
&& (prev
== 0)) prev
=numpoints
-1;
287 i = index of current vertex.
288 prev,next: index of previous/next vertices (of the control polygon)
291 vp, vx, vn: the previous, current and next vertices;
292 start, end: TRUE if we're at an end of poly (then, vp and/or vn are not
293 valid, respectively).
295 Some values *will* be recomputed a few times across iterations (but stored in
296 different boxes). Either gprof says it's a real problem, or gcc finally gets
300 if (pts
[i
].type
== BEZ_MOVE_TO
) {
304 switch(pts
[i
].type
) {
306 g_assert_not_reached();
309 point_copy(&vx
,&pts
[i
].p1
);
310 switch(pts
[prev
].type
) {
313 point_copy(&vsc
,&pts
[prev
].p1
);
314 point_copy(&vp
,&pts
[prev
].p1
);
317 point_copy(&vsc
,&pts
[prev
].p3
);
318 point_copy(&vp
,&pts
[prev
].p3
);
323 point_copy(&vx
,&pts
[i
].p3
);
324 point_copy(&vp
,&pts
[i
].p2
);
325 switch(pts
[prev
].type
) {
328 point_copy(&vsc
,&pts
[prev
].p1
);
331 point_copy(&vsc
,&pts
[prev
].p3
);
333 } /* vsc is the start of the curve. */
337 start
= (pts
[prev
].type
== BEZ_MOVE_TO
);
338 end
= (pts
[next
].type
== BEZ_MOVE_TO
);
339 point_copy(&vn
,&pts
[next
].p1
); /* whichever type pts[next] is. */
341 /* Now, we know about a few vertices around the one we're dealing with.
342 Depending on the shape of the (previous,current) segment, and whether
343 it's a middle or end segment, we'll be doing different stuff. */
345 if (pts
[i
].type
== BEZ_LINE_TO
) {
346 line_bbox(&vsc
,&vx
,&full_lextra
,&rt
);
348 bicubicbezier2D_bbox(&vsc
,
349 &pts
[i
].p1
,&pts
[i
].p2
,&pts
[i
].p3
,
354 if (pts
[i
].type
== BEZ_LINE_TO
) {
356 line_bbox(&vsc
,&vx
,&full_lextra
,&rt
);
358 line_bbox(&vsc
,&vx
,&start_lextra
,&rt
);
360 } else { /* BEZ_MOVE_TO */
362 bicubicbezier2D_bbox(&vsc
,
363 &pts
[i
].p1
,&pts
[i
].p2
,&pts
[i
].p3
,
367 bicubicbezier2D_bbox(&vsc
,
368 &pts
[i
].p1
,&pts
[i
].p2
,&pts
[i
].p3
,
373 } else if (end
) { /* end but not start. Not closed anyway. */
374 if (pts
[i
].type
== BEZ_LINE_TO
) {
375 line_bbox(&vsc
,&vx
,&end_lextra
,&rt
);
377 bicubicbezier2D_bbox(&vsc
,
378 &pts
[i
].p1
,&pts
[i
].p2
,&pts
[i
].p3
,
382 } else { /* normal case : middle segment (not closed shape). */
383 if (pts
[i
].type
== BEZ_LINE_TO
) {
384 line_bbox(&vsc
,&vx
,&lextra
,&rt
);
386 bicubicbezier2D_bbox(&vsc
,
387 &pts
[i
].p1
,&pts
[i
].p2
,&pts
[i
].p3
,
392 rectangle_union(rect
,&rt
);
394 /* The following code enlarges a little the bounding box (if necessary) to
395 account with the "pointy corners" X (and PS) add when LINEJOIN_MITER mode is
398 if ((!start
) && (!end
)) { /* We have a non-extremity vertex. */
402 point_copy_add_scaled(&vpx
,&vx
,&vp
,-1);
403 point_normalize(&vpx
);
404 point_copy_add_scaled(&vxn
,&vn
,&vx
,-1);
405 point_normalize(&vxn
);
407 co
= point_dot(&vpx
,&vxn
);
409 if ((co
> -0.9816) && (finite(alpha
))) { /* 0.9816 = cos(11deg) */
410 /* we have a pointy join. */
411 real overshoot
= extra
->middle_trans
/ sin(alpha
/2.0);
414 point_copy_add_scaled(&vovs
,&vpx
,&vxn
,-1);
415 point_normalize(&vovs
);
416 point_copy_add_scaled(&pto
,&vx
,&vovs
,overshoot
);
418 rectangle_add_point(rect
,&pto
);
420 /* we don't have a pointy join. */
423 point_get_perp(&vpxt
,&vpx
);
424 point_get_perp(&vxnt
,&vxn
);
426 point_copy_add_scaled(&tmp
,&vx
,&vpxt
,1);
427 rectangle_add_point(rect
,&tmp
);
428 point_copy_add_scaled(&tmp
,&vx
,&vpxt
,-1);
429 rectangle_add_point(rect
,&tmp
);
430 point_copy_add_scaled(&tmp
,&vx
,&vxnt
,1);
431 rectangle_add_point(rect
,&tmp
);
432 point_copy_add_scaled(&tmp
,&vx
,&vxnt
,-1);
433 rectangle_add_point(rect
,&tmp
);
439 void rectangle_bbox(const Rectangle
*rin
,
440 const ElementBBExtras
*extra
,
443 rout
->left
= rin
->left
- extra
->border_trans
;
444 rout
->top
= rin
->top
- extra
->border_trans
;
445 rout
->right
= rin
->right
+ extra
->border_trans
;
446 rout
->bottom
= rin
->bottom
+ extra
->border_trans
;
450 /* TODO: text_bbox ? */