/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// 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. * * Actual reference is: T. W. Sederberg and T Nishita: Curve * intersection using Bezier clipping. In Computer Aided Design, 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 * the intersection of the min value line with the convex hull from * the left, the second is the intersection of the max value line with * the convex hull from the right.
*/ 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 * the intersection of the min value line with the convex hull from * the left, the second is the intersection of the max value line with * the convex hull from the right. * * The polynomial coefficients c0 to c3 given to this method * must correspond to t values of 0, 1/3, 2/3 and 1, 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);
/** determine parameter ranges [0,t1) and (t2,1] on c1, where c1 is guaranteed to lie outside c2. Returns false, if the two curves don't even intersect.
@param t1 Range [0,t1) on c1 is guaranteed to lie outside c2
@param t2 Range (t2,1] on c1 is guaranteed to lie outside c2
@param c1_orig Original curve c1
@param c1_part Subdivided current part of c1
@param c2_orig Original curve c2
@param c2_part Subdivided current part of c2
*/ bool Impl_calcClipRange( double& t1, double& t2, const Bezier& c1_orig, const Bezier& c1_part, const Bezier& c2_orig, const Bezier& c2_part )
{ // TODO: Maybe also check fat line orthogonal to P0P3, having P0 // and P3 as the extremal points
// 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 );
/** Calc all values t_i on c1, for which safeRanges functor does not give a safe range on c1 and c2.
This method is the workhorse of the bezier clipping. Because c1 and c2 must be alternatingly tested against each other (first determine safe parameter interval on c1 with regard to c2, then the other way around), we call this method recursively with c1 and c2 swapped.
@param result Output iterator where the final t values are added to. If curves don't intersect, nothing is added.
@param delta Maximal allowed distance to true critical point (measured in the original curve's coordinate system)
@param safeRangeFunctor Functor object, that must provide the following operator(): bool safeRangeFunctor( double& t1, double& t2, const Bezier& c1_orig, const Bezier& c1_part, const Bezier& c2_orig, const Bezier& c2_part ); This functor must calculate the safe ranges [0,t1] and [t2,1] on c1_orig, where c1_orig is 'safe' from c2_part. If the whole c1_orig is safe, false must be returned, true otherwise.
*/ template <class Functor> void Impl_applySafeRanges_rec( std::back_insert_iterator< std::vector< std::pair<double, double> > >& result, double delta, const Functor& safeRangeFunctor, int recursionLevel, const Bezier& c1_orig, const Bezier& c1_part, double last_t1_c1, double last_t2_c1, const Bezier& c2_orig, const Bezier& c2_part, double last_t1_c2, double last_t2_c2 )
{ // check end condition // ===================
// 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 ) );
@param result Output iterator where the final t values are added to. This iterator will remain empty, if there are no intersections.
@param delta Maximal allowed distance to true intersection (measured in the original curve's coordinate system)
*/ void clipBezier( std::back_insert_iterator< std::vector< std::pair<double, double> > >& result, double delta, const Bezier& c1, const Bezier& c2 )
{ #if 0 // 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 );
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.