/** Calculate a bounding box for a set of bezier points. * @param pts The bezier points * @param numpoints The number of elements in `pts' * @param extra Extra spacing information. * @param closed True if the bezier points form a closed line. * @param rect Return value: The enclosing rectangle will be stored here. * @bug This function is way too long (214 lines) and should be split. */ void polybezier_bbox(const BezPoint *pts, int numpoints, const PolyBBExtras *extra, gboolean closed, Rectangle *rect) { Point vx,vn,vsc,vp; int i,prev,next; Rectangle rt; PolyBBExtras bextra,start_bextra,end_bextra; LineBBExtras lextra,start_lextra,end_lextra,full_lextra; gboolean start,end; vp.x=0; vp.y=0; g_assert(pts[0].type == BEZ_MOVE_TO); rect->left = rect->right = pts[0].p1.x; rect->top = rect->bottom = pts[0].p1.y; /* First, we build derived BBExtras structures, so we have something to feed the primitives. */ if (!closed) { start_lextra.start_long = extra->start_long; start_lextra.start_trans = MAX(extra->start_trans,extra->middle_trans); start_lextra.end_long = 0; start_lextra.end_trans = extra->middle_trans; end_lextra.start_long = 0; end_lextra.start_trans = extra->middle_trans; end_lextra.end_long = extra->end_long; end_lextra.end_trans = MAX(extra->end_trans,extra->middle_trans); } full_lextra.start_long = extra->start_long; full_lextra.start_trans = MAX(extra->start_trans,extra->middle_trans); full_lextra.end_long = extra->end_long; full_lextra.end_trans = MAX(extra->end_trans,extra->middle_trans); if (!closed) { lextra.start_long = 0; lextra.start_trans = extra->middle_trans; lextra.end_long = 0; lextra.end_trans = extra->middle_trans; start_bextra.start_long = extra->start_long; start_bextra.start_trans = extra->start_trans; start_bextra.middle_trans = extra->middle_trans; start_bextra.end_long = 0; start_bextra.end_trans = extra->middle_trans; end_bextra.start_long = 0; end_bextra.start_trans = extra->middle_trans; end_bextra.middle_trans = extra->middle_trans; end_bextra.end_long = extra->end_long; end_bextra.end_trans = extra->end_trans; } bextra.start_long = 0; bextra.start_trans = extra->middle_trans; bextra.middle_trans = extra->middle_trans; bextra.end_long = 0; bextra.end_trans = extra->middle_trans; for (i=1;i<numpoints;i++) { next = (i+1) % numpoints; prev = (i-1) % numpoints; if (closed && (next == 0)) next=1; if (closed && (prev == 0)) prev=numpoints-1; /* We have now: i = index of current vertex. prev,next: index of previous/next vertices (of the control polygon) We want: vp, vx, vn: the previous, current and next vertices; start, end: TRUE if we're at an end of poly (then, vp and/or vn are not valid, respectively). Some values *will* be recomputed a few times across iterations (but stored in different boxes). Either gprof says it's a real problem, or gcc finally gets a clue. */ if (pts[i].type == BEZ_MOVE_TO) { continue; } switch(pts[i].type) { case BEZ_MOVE_TO: g_assert_not_reached(); break; case BEZ_LINE_TO: point_copy(&vx,&pts[i].p1); switch(pts[prev].type) { case BEZ_MOVE_TO: case BEZ_LINE_TO: point_copy(&vsc,&pts[prev].p1); point_copy(&vp,&pts[prev].p1); break; case BEZ_CURVE_TO: point_copy(&vsc,&pts[prev].p3); point_copy(&vp,&pts[prev].p3); break; } break; case BEZ_CURVE_TO: point_copy(&vx,&pts[i].p3); point_copy(&vp,&pts[i].p2); switch(pts[prev].type) { case BEZ_MOVE_TO: case BEZ_LINE_TO: point_copy(&vsc,&pts[prev].p1); break; case BEZ_CURVE_TO: point_copy(&vsc,&pts[prev].p3); break; } /* vsc is the start of the curve. */ break; } start = (pts[prev].type == BEZ_MOVE_TO); end = (pts[next].type == BEZ_MOVE_TO); point_copy(&vn,&pts[next].p1); /* whichever type pts[next] is. */ /* Now, we know about a few vertices around the one we're dealing with. Depending on the shape of the (previous,current) segment, and whether it's a middle or end segment, we'll be doing different stuff. */ if (closed) { if (pts[i].type == BEZ_LINE_TO) { line_bbox(&vsc,&vx,&full_lextra,&rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &bextra, &rt); } } else if (start) { if (pts[i].type == BEZ_LINE_TO) { if (end) { line_bbox(&vsc,&vx,&full_lextra,&rt); } else { line_bbox(&vsc,&vx,&start_lextra,&rt); } } else { /* BEZ_MOVE_TO */ if (end) { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, extra, &rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &start_bextra, &rt); } } } else if (end) { /* end but not start. Not closed anyway. */ if (pts[i].type == BEZ_LINE_TO) { line_bbox(&vsc,&vx,&end_lextra,&rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &end_bextra, &rt); } } else { /* normal case : middle segment (not closed shape). */ if (pts[i].type == BEZ_LINE_TO) { line_bbox(&vsc,&vx,&lextra,&rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &bextra, &rt); } } rectangle_union(rect,&rt); /* The following code enlarges a little the bounding box (if necessary) to account with the "pointy corners" X (and PS) add when LINEJOIN_MITER mode is in force. */ if ((!start) && (!end)) { /* We have a non-extremity vertex. */ Point vpx,vxn; real co,alpha; point_copy_add_scaled(&vpx,&vx,&vp,-1); point_normalize(&vpx); point_copy_add_scaled(&vxn,&vn,&vx,-1); point_normalize(&vxn); co = point_dot(&vpx,&vxn); alpha = acos(-co); if (co > -0.9816) { /* 0.9816 = cos(11deg) */ /* we have a pointy join. */ real overshoot; Point vovs,pto; if (finite(alpha)) overshoot = extra->middle_trans / sin(alpha/2.0); else /* prependicular? */ overshoot = extra->middle_trans; point_copy_add_scaled(&vovs,&vpx,&vxn,-1); point_normalize(&vovs); point_copy_add_scaled(&pto,&vx,&vovs,overshoot); rectangle_add_point(rect,&pto); } else { /* we don't have a pointy join. */ Point vpxt,vxnt,tmp; point_get_perp(&vpxt,&vpx); point_get_perp(&vxnt,&vxn); point_copy_add_scaled(&tmp,&vx,&vpxt,1); rectangle_add_point(rect,&tmp); point_copy_add_scaled(&tmp,&vx,&vpxt,-1); rectangle_add_point(rect,&tmp); point_copy_add_scaled(&tmp,&vx,&vxnt,1); rectangle_add_point(rect,&tmp); point_copy_add_scaled(&tmp,&vx,&vxnt,-1); rectangle_add_point(rect,&tmp); } } } }
void polybezier_bbox(const BezPoint *pts, int numpoints, const PolyBBExtras *extra, bool closed, Rectangle *rect) { Point vx,vp,vn,vsc; int i,prev,next; Rectangle rt; PolyBBExtras bextra,start_bextra,end_bextra; LineBBExtras lextra,start_lextra,end_lextra,full_lextra; bool start,end; rect->setLeft(pts[0].p1.x()); rect->setRight(pts[0].p1.x()); rect->setTop(pts[0].p1.y()); rect->setBottom(pts[0].p1.y()); if (!closed) { start_lextra.startLong = extra->startLong; start_lextra.startTrans = qMax(extra->startTrans,extra->middleTrans); start_lextra.endLong = 0; start_lextra.endTrans = extra->middleTrans; end_lextra.startLong = 0; end_lextra.startTrans = extra->middleTrans; end_lextra.endLong = extra->endLong; end_lextra.endTrans = qMax(extra->endTrans,extra->middleTrans); } full_lextra.startLong = extra->startLong; full_lextra.startTrans = qMax(extra->startTrans,extra->middleTrans); full_lextra.endLong = extra->endLong; full_lextra.endTrans = qMax(extra->endTrans,extra->middleTrans); if (!closed) { lextra.startLong = 0; lextra.startTrans = extra->middleTrans; lextra.endLong = 0; lextra.endTrans = extra->middleTrans; start_bextra.startLong = extra->startLong; start_bextra.startTrans = extra->startTrans; start_bextra.middleTrans = extra->middleTrans; start_bextra.endLong = 0; start_bextra.endTrans = extra->middleTrans; end_bextra.startLong = 0; end_bextra.startTrans = extra->middleTrans; end_bextra.middleTrans = extra->middleTrans; end_bextra.endLong = extra->endLong; end_bextra.endTrans = extra->endTrans; } bextra.startLong = 0; bextra.startTrans = extra->middleTrans; bextra.middleTrans = extra->middleTrans; bextra.endLong = 0; bextra.endTrans = extra->middleTrans; for (i=1;i<numpoints;i++) { next = (i+1) % numpoints; prev = (i-1) % numpoints; if (closed && (next == 0)) next=1; if (closed && (prev == 0)) prev=numpoints-1; if (pts[i].type == BezPoint::BEZ_MOVE_TO) { continue; } switch(pts[i].type) { case BezPoint::BEZ_MOVE_TO: break; case BezPoint::BEZ_LINE_TO: vx = pts[i].p1; switch(pts[prev].type) { case BezPoint::BEZ_MOVE_TO: case BezPoint::BEZ_LINE_TO: vsc = pts[prev].p1; vp = pts[prev].p1; break; case BezPoint::BEZ_CURVE_TO: vsc = pts[prev].p3; vp = pts[prev].p3; break; } break; case BezPoint::BEZ_CURVE_TO: vx = pts[i].p3; vp = pts[i].p2; switch(pts[prev].type) { case BezPoint::BEZ_MOVE_TO: case BezPoint::BEZ_LINE_TO: vsc = pts[prev].p1; break; case BezPoint::BEZ_CURVE_TO: vsc = pts[prev].p3; break; } break; } start = (pts[prev].type == BezPoint::BEZ_MOVE_TO); end = (pts[next].type == BezPoint::BEZ_MOVE_TO); vn = pts[next].p1; if (closed) { if (pts[i].type == BezPoint::BEZ_LINE_TO) { line_bbox(&vsc,&vx,&full_lextra,&rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &bextra, &rt); } } else if (start) { if (pts[i].type == BezPoint::BEZ_LINE_TO) { if (end) { line_bbox(&vsc,&vx,&full_lextra,&rt); } else { line_bbox(&vsc,&vx,&start_lextra,&rt); } } else { if (end) { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, extra, &rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &start_bextra, &rt); } } } else if (end) { if (pts[i].type == BezPoint::BEZ_LINE_TO) { line_bbox(&vsc,&vx,&end_lextra,&rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &end_bextra, &rt); } } else { if (pts[i].type == BezPoint::BEZ_LINE_TO) { line_bbox(&vsc,&vx,&lextra,&rt); } else { bicubicbezier2D_bbox(&vsc, &pts[i].p1,&pts[i].p2,&pts[i].p3, &bextra, &rt); } } *rect = rect->united(rt); if ((!start) && (!end)) { Point vpx,vxn; double co,alpha; point_copy_add_scaled(&vpx,&vx,&vp,-1); point_normalize(&vpx); point_copy_add_scaled(&vxn,&vn,&vx,-1); point_normalize(&vxn); co = point_dot(&vpx,&vxn); alpha = acos(-co); if ((co > -0.9816) && (finite(alpha))) { double overshoot = extra->middleTrans / sin(alpha/2.0); Point vovs,pto; point_copy_add_scaled(&vovs,&vpx,&vxn,-1); point_normalize(&vovs); point_copy_add_scaled(&pto,&vx,&vovs,overshoot); rectangle_add_point(rect,&pto); } else { Point vpxt,vxnt,tmp; point_get_perp(&vpxt,&vpx); point_get_perp(&vxnt,&vxn); point_copy_add_scaled(&tmp,&vx,&vpxt,1); rectangle_add_point(rect,&tmp); point_copy_add_scaled(&tmp,&vx,&vpxt,-1); rectangle_add_point(rect,&tmp); point_copy_add_scaled(&tmp,&vx,&vxnt,1); rectangle_add_point(rect,&tmp); point_copy_add_scaled(&tmp,&vx,&vxnt,-1); rectangle_add_point(rect,&tmp); } } } }
/*! * \brief Calculate crossing points of two bezier segments * * Beware two bezier segments can intersect more than once, but this * function only returns the first or no intersection. It is the * responsibility of the caller to further split segments until there * is no intersection left. */ static gboolean bezier_bezier_intersection (GArray *crossing, const BezierSegment *a, const BezierSegment *b, int depth, real asplit, real bsplit) { Rectangle abox, bbox; PolyBBExtras extra = { 0, }; gboolean small_a, small_b; /* Avoid intersection overflow: if start and end are on the other segment * assume full overlap and no crossing. */ if ( (_segment_has_point (a, &b->p0) && _segment_has_point (a, &b->p3)) || (_segment_has_point (b, &a->p0) && _segment_has_point (b, &a->p3))) return FALSE; /* XXX: more variants pending, partial overlap */ /* With very similar segments we would create a lot of points with not * a very deep recursion (test with ying-yang symbol). * Just comparing the segments on depth=1 is not good enough, so for * now we are limiting the number of intersections */ if (crossing->len > 127) { /* XXX: arbitrary limit */ g_warning ("Crossing limit (%d) reached", crossing->len); return FALSE; } bicubicbezier2D_bbox (&a->p0, &a->p1, &a->p2, &a->p3, &extra, &abox); bicubicbezier2D_bbox (&b->p0, &b->p1, &b->p2, &b->p3, &extra, &bbox); if (!rectangle_intersects (&abox, &bbox)) return FALSE; small_a = (abox.right - abox.left) < EPSILON && (abox.bottom - abox.top) < EPSILON; small_b = (bbox.right - bbox.left) < EPSILON && (bbox.bottom - bbox.top) < EPSILON; /* if the boxes are small enough we can calculate the point */ if (small_a && small_b) { /* intersecting and both small, should not matter which one is used */ Point pt = { (abox.right + abox.left + bbox.right + bbox.left) / 4, (abox.bottom + abox.top + bbox.bottom + bbox.top) / 4 }; Intersection is; int i; for (i = 0; i < crossing->len; ++i) { /* if it's already included we are done */ if (distance_point_point (&g_array_index (crossing, Intersection, i).pt, &pt) < 1.4142*EPSILON) return TRUE; /* although we did not add it */ } is.split_one = asplit; is.split_two = bsplit; is.pt = pt; g_print ("d=%d; as=%g; bs=%g; ", depth, asplit, bsplit); g_array_append_val (crossing, is); return TRUE; } else { /* further splitting of a and b; it could be smart to only search in the * intersection of a-box and b-box ... */ BezierSegment a1, a2; BezierSegment b1, b2; real ofs = 1.0/(1<<(depth+1)); gboolean ret = FALSE; bezier_split (a, &a1, &a2); bezier_split (b, &b1, &b2); ret |= bezier_bezier_intersection (crossing, &a1, &b1, depth+1, asplit-ofs, bsplit-ofs); ret |= bezier_bezier_intersection (crossing, &a2, &b1, depth+1, asplit+ofs, bsplit-ofs); ret |= bezier_bezier_intersection (crossing, &a1, &b2, depth+1, asplit-ofs, bsplit+ofs); ret |= bezier_bezier_intersection (crossing, &a2, &b2, depth+1, asplit+ofs, bsplit+ofs); /* XXX: check !ret case, not sure if it should happen */ return ret; } }