// what to test #define WITH_ASSERTIONS //#define WITH_CONVEXHULL_TEST //#define WITH_MULTISUBDIVIDE_TEST //#define WITH_FATLINE_TEST //#define WITH_CALCFOCUS_TEST //#define WITH_SAFEPARAMBASE_TEST //#define WITH_SAFEPARAMS_TEST //#define WITH_SAFEPARAM_DETAILED_TEST //#define WITH_SAFEFOCUSPARAM_CALCFOCUS //#define WITH_SAFEFOCUSPARAM_TEST //#define WITH_SAFEFOCUSPARAM_DETAILED_TEST #define WITH_BEZIERCLIP_TEST
/* Implementation of the so-called 'Fat-Line Bezier Clipping Algorithm' by Sederberg et al. * *Actualreferenceis:T.W.SederbergandTNishita:Curve *intersectionusingBezierclipping.InComputerAidedDesign,22 *(9),1990,pp.538--549
*/
/* Misc helper *===========
*/ int fallFac( int n, int k )
{ #ifdef WITH_ASSERTIONS
assert(n>=k); // "For factorials, n must be greater or equal k"
assert(n>=0); // "For factorials, n must be positive"
assert(k>=0); // "For factorials, k must be positive" #endif
int res( 1 );
while( k-- && n ) res *= n--;
return res;
}
int fac( int n )
{ return fallFac(n, n);
}
/* Bezier fat line clipping part *=============================
*/
void Impl_calcFatLine( FatLine& line, const Bezier& c )
{ // Prepare normalized implicit line // ================================
/* calculates two t's for the given bernstein control polygon: the first is *theintersectionoftheminvaluelinewiththeconvexhullfrom *theleft,thesecondistheintersectionofthemaxvaluelinewith *theconvexhullfromtheright.
*/ bool Impl_calcSafeParams( double& t1, double& t2, const Polygon2D& rPoly, double lowerYBound, double upperYBound )
{ // need the convex hull of the control polygon, as this is // guaranteed to completely bound the curve
Polygon2D convHull( convexHull(rPoly) );
// init min and max buffers
t1 = 0.0 ; double currLowerT( 1.0 );
t2 = 1.0; double currHigherT( 0.0 );
if( convHull.size() <= 1 ) returnfalse; // only one point? Then we're done with clipping
/* now, clip against lower and higher bounds */
Point2D p0;
Point2D p1;
bool bIntersection( false );
for( Polygon2D::size_type i=0; i<convHull.size(); ++i )
{ // have to check against convHull.size() segments, as the // convex hull is, by definition, closed. Thus, for the // last point, we take the first point as partner. if( i+1 == convHull.size() )
{ // close the polygon
p0 = convHull[i];
p1 = convHull[0];
} else
{
p0 = convHull[i];
p1 = convHull[i+1];
}
// is the segment in question within or crossing the // horizontal band spanned by lowerYBound and upperYBound? If // not, we've got no intersection. If yes, we maybe don't have // an intersection, but we've got to update the permissible // range, nevertheless. This is because inside lying segments // leads to full range forbidden. if( (tolLessEqual(p0.y, upperYBound) || tolLessEqual(p1.y, upperYBound)) &&
(tolGreaterEqual(p0.y, lowerYBound) || tolGreaterEqual(p1.y, lowerYBound)) )
{ // calc intersection of convex hull segment with // one of the horizontal bounds lines // to optimize a bit, r_x is calculated only in else case constdouble r_y( p1.y - p0.y );
if( tolZero(r_y) )
{ // r_y is virtually zero, thus we've got a horizontal // line. Now check whether we maybe coincide with lower or // upper horizontal bound line. if( tolEqual(p0.y, lowerYBound) ||
tolEqual(p0.y, upperYBound) )
{ // yes, simulate intersection then
currLowerT = std::min(currLowerT, std::min(p0.x, p1.x));
currHigherT = std::max(currHigherT, std::max(p0.x, p1.x));
}
} else
{ // check against lower and higher bounds // ===================================== constdouble r_x( p1.x - p0.x );
// calc intersection with horizontal dMin line constdouble currTLow( (lowerYBound - p0.y) * r_x / r_y + p0.x );
// calc intersection with horizontal dMax line constdouble currTHigh( (upperYBound - p0.y) * r_x / r_y + p0.x );
// set flag that at least one segment is contained or // intersects given horizontal band.
bIntersection = true;
}
}
#ifndef WITH_SAFEPARAMBASE_TEST // limit intersections found to permissible t parameter range
t1 = std::max(0.0, currLowerT);
t2 = std::min(1.0, currHigherT); #endif
return bIntersection;
}
/* calculates two t's for the given bernstein polynomial: the first is *theintersectionoftheminvaluelinewiththeconvexhullfrom *theleft,thesecondistheintersectionofthemaxvaluelinewith *theconvexhullfromtheright. * *Thepolynomialcoefficientsc0toc3giventothismethod *mustcorrespondtotvaluesof0,1/3,2/3and1,respectively.
*/ bool Impl_calcSafeParams_clip( double& t1, double& t2, const FatLine& bounds, double c0, double c1, double c2, double c3 )
{ /* first of all, determine convex hull of c0-c3 */
Polygon2D poly(4);
poly[0] = Point2D(0, c0);
poly[1] = Point2D(1.0/3.0, c1);
poly[2] = Point2D(2.0/3.0, c2);
poly[3] = Point2D(1, c3);
// must use the subdivided version of c2, since the fat line // algorithm works implicitly with the convex hull bounding // box.
Impl_calcFatLine(bounds_c2, c2_part);
// determine clip positions on c2. Can use original c1 (which // is necessary anyway, to get the t's on the original curve), // since the distance calculations work directly in the // Bernstein polynomial parameter domain. if( Impl_calcSafeParams_clip( t1, t2, bounds_c2,
calcLineDistance( bounds_c2.a,
bounds_c2.b,
bounds_c2.c,
c1_orig.p0.x,
c1_orig.p0.y ),
calcLineDistance( bounds_c2.a,
bounds_c2.b,
bounds_c2.c,
c1_orig.p1.x,
c1_orig.p1.y ),
calcLineDistance( bounds_c2.a,
bounds_c2.b,
bounds_c2.c,
c1_orig.p2.x,
c1_orig.p2.y ),
calcLineDistance( bounds_c2.a,
bounds_c2.b,
bounds_c2.c,
c1_orig.p3.x,
c1_orig.p3.y ) ) )
{ //printCurvesWithSafeRange(c1_orig, c2_orig, t1, t2, c2_part, bounds_c2);
// they do intersect returntrue;
}
}
// they don't intersect: nothing to do returnfalse;
}
/* Tangent intersection part *=========================
*/
void Impl_calcFocus( Bezier& res, const Bezier& c )
{ // arbitrary small value, for now // TODO: find meaningful value constdouble minPivotValue( 1.0e-20 );
// so, this is what we calculate here (determine c0 and c1):
fMatrix[0] = c.p1.x - c.p0.x;
fMatrix[1] = c.p2.x - c.p3.x;
fMatrix[2] = (c.p3.y - c.p0.y)/3.0;
fMatrix[3] = c.p0.y - c.p1.y;
fMatrix[4] = c.p3.y - c.p2.y;
fMatrix[5] = (c.p3.x - c.p0.x)/3.0;
// TODO: determine meaningful value for if( !solve(fMatrix, 2, 3, fRes, minPivotValue) )
{ // TODO: generate meaningful values here // singular or nearly singular system -- use arbitrary // values for res
fRes[0] = 0.0;
fRes[1] = 1.0;
std::cerr << "Matrix singular!" << std::endl;
}
// now, the reordered and per-coefficient collected focus curve is // the following third degree bezier curve F(t):
bool Impl_calcSafeParams_focus( double& t1, double& t2, const Bezier& curve, const Bezier& focus )
{ // now, we want to determine which normals of the original curve // P(t) intersect with the focus curve F(t). The condition for // this statement is P'(t)(P(t) - F) = 0, i.e. hodograph P'(t) and // line through P(t) and F are perpendicular. // If you expand this equation, you end up with something like
// Okay, but F is now not a single point, but itself a curve // F(u). Thus, for every value of u, we get a different 2n-1 // bezier curve from the above equation. Therefore, we have a // tensor product bezier patch, with the following defining // equation:
// as above, only that now F is one of the focus' control points.
// Note the difference in the binomial coefficients to the // reference paper, these formulas most probably contained a typo.
// To determine, where D(t,u) is _not_ zero (these are the parts // of the curve that don't share normals with the focus and can // thus be safely clipped away), we project D(u,t) onto the // (d(t,u), t) plane, determine the convex hull there and proceed // as for the curve intersection part (projection is orthogonal to // u axis, thus simply throw away u coordinate).
// \fallfac are so-called falling factorials (see Concrete // Mathematics, p. 47 for a definition).
// now, for tensor product bezier curves, the convex hull property // holds, too. Thus, we simply project the control points (t_{ij}, // u_{ij}, d_{ij}) onto the (t,d) plane and calculate the // intersections of the convex hull with the t axis, as for the // bezier clipping case.
// calc polygon of control points (t_{ij}, d_{ij}):
constint n( 3 ); // cubic bezier curves, as a matter of fact constint i_card( 2*n ); constint j_card( n + 1 ); constint k_max( n-1 );
Polygon2D controlPolygon( i_card*j_card ); // vector of (t_{ij}, d_{ij}) in row-major order
int i, j, k, l; // variable notation from formulas above and Sederberg article
Point2D::value_type d; for( i=0; i<i_card; ++i )
{ for( j=0; j<j_card; ++j )
{ // calc single d_{ij} sum: for( d=0.0, k=std::max(0,i-n); k<=k_max && k<=i; ++k )
{
l = i - k; // invariant: k + l = i
assert(k>=0 && k<=n-1); // k \in {0,...,n-1}
assert(l>=0 && l<=n); // l \in {0,...,n}
// TODO: find, document and assert proper limits for n and int's max_val. // This becomes important should anybody wants to use // this code for higher-than-cubic beziers
d += static_cast<double>(fallFac(n,l)*fallFac(n-1,k)*fac(i)) / static_cast<double>(fac(l)*fac(k) * fallFac(2*n-1,i)) * n *
( (curve[k+1].x - curve[k].x)*(curve[l].x - focus[j].x) + // dot product here
(curve[k+1].y - curve[k].y)*(curve[l].y - focus[j].y) );
}
// Note that the t_{ij} values are evenly spaced on the // [0,1] interval, thus t_{ij}=i/(2n-1)
controlPolygon[ i*j_card + j ] = Point2D( i/(2.0*n-1.0), d );
}
}
#ifndef WITH_SAFEFOCUSPARAM_DETAILED_TEST
// calc safe parameter range, to determine [0,t1] and [t2,1] where // no zero crossing is guaranteed. return Impl_calcSafeParams( t1, t2, controlPolygon, 0.0, 0.0 );
// TODO: tidy up recursion handling. maybe put everything in a // struct and swap that here at method entry
// TODO: Implement limit on recursion depth. Should that limit be // reached, chances are that we're on a higher-order tangency. For // this case, AW proposed to take the middle of the current // interval, and to correct both curve's tangents at that new // endpoint to be equal. That virtually generates a first-order // tangency, and justifies to return a single intersection // point. Otherwise, inside/outside test might fail here.
// Note: we first perform the clipping and only test for precision // sufficiency afterwards, since we want to exploit the fact that // Impl_calcClipRange returns false if the curves don't // intersect. We would have to check that separately for the end // condition, otherwise.
// determine safe range on c1_orig if( safeRangeFunctor( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part ) )
{ // now, t1 and t2 are calculated on the original curve // (but against a fat line calculated from the subdivided // c2, namely c2_part). If the [t1,t2] range is outside // our current [last_t1,last_t2] range, we're done in this // branch - the curves no longer intersect. if( tolLessEqual(t1_c1, last_t2_c1) && tolGreaterEqual(t2_c1, last_t1_c1) )
{ // As noted above, t1 and t2 are calculated on the // original curve, but against a fat line // calculated from the subdivided c2, namely // c2_part. Our domain to work on is // [last_t1,last_t2], on the other hand, so values // of [t1,t2] outside that range are irrelevant // here. Clip range appropriately.
t1_c1 = std::max(t1_c1, last_t1_c1);
t2_c1 = std::min(t2_c1, last_t2_c1);
// TODO: respect delta // for now, end condition is just a fixed threshold on the t's
// subdivide at the middle of the interval (as // we're not subdividing on the original // curve, this simply amounts to subdivision // at 0.5)
Impl_deCasteljauAt( part1, part2, c1_part, 0.5 );
// subdivide at the middle of the interval (as // we're not subdividing on the original // curve, this simply amounts to subdivision // at 0.5)
Impl_deCasteljauAt( part1, part2, c2_part, 0.5 );
// subdivide at t1_c1
Impl_deCasteljauAt( c1_part1, c1_part2, c1_orig, t1_c1 );
// subdivide at t2_c1. As we're working on // c1_part2 now, we have to adapt t2_c1 since // we're no longer in the original parameter // interval. This is based on the following // assumption: t2_new = (t2-t1)/(1-t1), which // relates the t2 value into the new parameter // range [0,1] of c1_part2.
Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2_c1-t1_c1)/(1.0-t1_c1) );
// descend with swapped curves and c1_part1 as the // remaining (middle) segment
Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1,
c2_orig, c2_part, last_t1_c2, last_t2_c2,
c1_orig, c1_part1, t1_c1, t2_c1 );
}
}
}
}
}
struct BezierTangencyFunctor
{ booloperator()( double& t1_c1, double& t2_c1, const Bezier& c1_orig, const Bezier& c1_part, const Bezier& c2_orig, const Bezier& c2_part ) const
{ // calc focus curve of c2
Bezier focus;
Impl_calcFocus(focus, c2_part); // need to use subdivided c2 // here, as the whole curve is // used for focus calculation
// determine safe range on c1_orig bool bRet( Impl_calcSafeParams_focus( t1_c1, t2_c1,
c1_orig, // use orig curve here, need t's on original curve
focus ) );
@paramdelta Maximalalloweddistancetotrueintersection(measuredinthe originalcurve'scoordinatesystem)
*/ void clipBezier( std::back_insert_iterator< std::vector< std::pair<double, double> > >& result, double delta, const Bezier& c1, const Bezier& c2 )
{ #if0 // first of all, determine list of collinear normals. Collinear // normals typically separate two intersections, thus, subdivide // at all collinear normal's t values beforehand. This will cater // for tangent intersections, where two or more intersections are // infinitesimally close together.
// TODO: evaluate effects of higher-than-second-order // tangencies. Sederberg et al. state that collinear normal // algorithm then degrades quickly.
// As Sederberg's collinear normal theorem is only sufficient, not // necessary for two intersections left and right, we've to test // all segments generated by the collinear normal algorithm // against each other. In other words, if the two curves are both // divided in a left and a right part, the collinear normal // theorem does _not_ state that the left part of curve 1 does not // e.g. intersect with the right part of curve 2.
// divide c1 and c2 at collinear normal intersection points
std::vector< Bezier > c1_segments( results.size()+1 );
std::vector< Bezier > c2_segments( results.size()+1 );
Bezier c1_remainder( c1 );
Bezier c2_remainder( c2 );
for(int i = 0; i < results.size(); ++i)
{
Bezier c1_part2;
Impl_deCasteljauAt( c1_segments[i], c1_part2, c1_remainder, results[i].first );
c1_remainder = c1_part2;
// subdivide at t1
Impl_deCasteljauAt( c1_part1, c1_part2, c, t1 );
// subdivide at t2_c1. As we're working on // c1_part2 now, we have to adapt t2_c1 since // we're no longer in the original parameter // interval. This is based on the following // assumption: t2_new = (t2-t1)/(1-t1), which // relates the t2 value into the new parameter // range [0,1] of c1_part2.
Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) );
// subdivide at t2
Impl_deCasteljauAt( c1_part3, c1_part2, c, t2 );
// subdivide at t1_c1
Impl_deCasteljauAt( c1_part1, c1_part2, c2, 0.30204 );
// subdivide at t2_c1. As we're working on // c1_part2 now, we have to adapt t2_c1 since // we're no longer in the original parameter // interval. This is based on the following // assumption: t2_new = (t2-t1)/(1-t1), which // relates the t2 value into the new parameter // range [0,1] of c1_part2.
Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (0.57151-0.30204)/(1.0-0.30204) );
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.39Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.