/************************************************************************* * * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: bezierclip.cxx,v $ * * $Revision: 1.4 $ * * last change: $Author: rt $ $Date: 2005-09-07 20:55:49 $ * * The Contents of this file are made available subject to * the terms of GNU Lesser General Public License Version 2.1. * * * GNU Lesser General Public License Version 2.1 * ============================================= * Copyright 2005 by Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, CA 94303, USA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * ************************************************************************/ #include #include #include #include #include #include "bezierclip.hxx" #include "gauss.hxx" // 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 // ================================ // calculate vector orthogonal to p1-p4: line.a = -(c.p0.y - c.p3.y); line.b = (c.p0.x - c.p3.x); // normalize const double len( sqrt( line.a*line.a + line.b*line.b ) ); if( !tolZero(len) ) { line.a /= len; line.b /= len; } line.c = -(line.a*c.p0.x + line.b*c.p0.y); // Determine bounding fat line from it // =================================== // calc control point distances const double dP2( calcLineDistance(line.a, line.b, line.c, c.p1.x, c.p1.y ) ); const double dP3( calcLineDistance(line.a, line.b, line.c, c.p2.x, c.p2.y ) ); // calc approximate bounding lines to curve (tight bounds are // possible here, but more expensive to calculate and thus not // worth the overhead) if( dP2 * dP3 > 0.0 ) { line.dMin = 3.0/4.0 * ::std::min(0.0, ::std::min(dP2, dP3)); line.dMax = 3.0/4.0 * ::std::max(0.0, ::std::max(dP2, dP3)); } else { line.dMin = 4.0/9.0 * ::std::min(0.0, ::std::min(dP2, dP3)); line.dMax = 4.0/9.0 * ::std::max(0.0, ::std::max(dP2, dP3)); } } void Impl_calcBounds( Point2D& leftTop, Point2D& rightBottom, const Bezier& c1 ) { leftTop.x = ::std::min( c1.p0.x, ::std::min( c1.p1.x, ::std::min( c1.p2.x, c1.p3.x ) ) ); leftTop.y = ::std::min( c1.p0.y, ::std::min( c1.p1.y, ::std::min( c1.p2.y, c1.p3.y ) ) ); rightBottom.x = ::std::max( c1.p0.x, ::std::max( c1.p1.x, ::std::max( c1.p2.x, c1.p3.x ) ) ); rightBottom.y = ::std::max( c1.p0.y, ::std::max( c1.p1.y, ::std::max( c1.p2.y, c1.p3.y ) ) ); } bool Impl_doBBoxIntersect( const Bezier& c1, const Bezier& c2 ) { // calc rectangular boxes from c1 and c2 Point2D lt1; Point2D rb1; Point2D lt2; Point2D rb2; Impl_calcBounds( lt1, rb1, c1 ); Impl_calcBounds( lt2, rb2, c2 ); if( ::std::min(rb1.x, rb2.x) < ::std::max(lt1.x, lt2.x) || ::std::min(rb1.y, rb2.y) < ::std::max(lt1.y, lt2.y) ) { return false; } else { return true; } } /* 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 ) return false; // 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 part2 is input curve, part1 is empty (input.p0, that is) part1.p0.x = part1.p1.x = part1.p2.x = part1.p3.x = input.p0.x; part1.p0.y = part1.p1.y = part1.p2.y = part1.p3.y = input.p0.y; part2 = input; } else if( tolEqual(t, 1.0) ) { // t is one -> part1 is input curve, part2 is empty (input.p3, that is) part1 = input; part2.p0.x = part2.p1.x = part2.p2.x = part2.p3.x = input.p3.x; part2.p0.y = part2.p1.y = part2.p2.y = part2.p3.y = input.p3.y; } else { part1.p0.x = input.p0.x; part1.p0.y = input.p0.y; part1.p1.x = (1.0 - t)*part1.p0.x + t*input.p1.x; part1.p1.y = (1.0 - t)*part1.p0.y + t*input.p1.y; const double Hx ( (1.0 - t)*input.p1.x + t*input.p2.x ), Hy ( (1.0 - t)*input.p1.y + t*input.p2.y ); part1.p2.x = (1.0 - t)*part1.p1.x + t*Hx; part1.p2.y = (1.0 - t)*part1.p1.y + t*Hy; part2.p3.x = input.p3.x; part2.p3.y = input.p3.y; part2.p2.x = (1.0 - t)*input.p2.x + t*input.p3.x; part2.p2.y = (1.0 - t)*input.p2.y + t*input.p3.y; part2.p1.x = (1.0 - t)*Hx + t*part2.p2.x; part2.p1.y = (1.0 - t)*Hy + t*part2.p2.y; part2.p0.x = (1.0 - t)*part1.p2.x + t*part2.p1.x; part2.p0.y = (1.0 - t)*part1.p2.y + t*part2.p1.y; part1.p3.x = part2.p0.x; part1.p3.y = part2.p0.y; } } // ----------------------------------------------------------------------------- void printCurvesWithSafeRange( const Bezier& c1, const Bezier& c2, double t1_c1, double t2_c1, const Bezier& c2_part, const FatLine& bounds_c2 ) { static int offset = 0; cout << "# safe param range testing" << endl << "plot [t=0.0:1.0] "; // clip safe ranges off c1 Bezier c1_part1; Bezier c1_part2; Bezier c1_part3; // subdivide at t1_c1 Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1_c1 ); // subdivide at t2_c1 Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, t2_c1 ); // output remaining segment (c1_part1) cout << "bez(" << c1.p0.x+offset << "," << c1.p1.x+offset << "," << c1.p2.x+offset << "," << c1.p3.x+offset << ",t),bez(" << c1.p0.y << "," << c1.p1.y << "," << c1.p2.y << "," << c1.p3.y << ",t), bez(" << c2.p0.x+offset << "," << c2.p1.x+offset << "," << c2.p2.x+offset << "," << c2.p3.x+offset << ",t),bez(" << c2.p0.y << "," << c2.p1.y << "," << c2.p2.y << "," << c2.p3.y << ",t), " #if 1 << "bez(" << c1_part1.p0.x+offset << "," << c1_part1.p1.x+offset << "," << c1_part1.p2.x+offset << "," << c1_part1.p3.x+offset << ",t),bez(" << c1_part1.p0.y << "," << c1_part1.p1.y << "," << c1_part1.p2.y << "," << c1_part1.p3.y << ",t), " #endif #if 1 << "bez(" << c2_part.p0.x+offset << "," << c2_part.p1.x+offset << "," << c2_part.p2.x+offset << "," << c2_part.p3.x+offset << ",t),bez(" << c2_part.p0.y << "," << c2_part.p1.y << "," << c2_part.p2.y << "," << c2_part.p3.y << ",t), " #endif << "linex(" << bounds_c2.a << "," << bounds_c2.b << "," << bounds_c2.c << ",t)+" << offset << ", liney(" << bounds_c2.a << "," << bounds_c2.b << "," << bounds_c2.c << ",t) title \"fat line (center)\", linex(" << bounds_c2.a << "," << bounds_c2.b << "," << bounds_c2.c-bounds_c2.dMin << ",t)+" << offset << ", liney(" << bounds_c2.a << "," << bounds_c2.b << "," << bounds_c2.c-bounds_c2.dMin << ",t) title \"fat line (min) \", linex(" << bounds_c2.a << "," << bounds_c2.b << "," << bounds_c2.c-bounds_c2.dMax << ",t)+" << offset << ", liney(" << bounds_c2.a << "," << bounds_c2.b << "," << bounds_c2.c-bounds_c2.dMax << ",t) title \"fat line (max) \"" << endl; offset += 1; } // ----------------------------------------------------------------------------- void printResultWithFinalCurves( const Bezier& c1, const Bezier& c1_part, const Bezier& c2, const Bezier& c2_part, double t1_c1, double t2_c1 ) { static int offset = 0; cout << "# final result" << endl << "plot [t=0.0:1.0] "; cout << "bez(" << c1.p0.x+offset << "," << c1.p1.x+offset << "," << c1.p2.x+offset << "," << c1.p3.x+offset << ",t),bez(" << c1.p0.y << "," << c1.p1.y << "," << c1.p2.y << "," << c1.p3.y << ",t), bez(" << c1_part.p0.x+offset << "," << c1_part.p1.x+offset << "," << c1_part.p2.x+offset << "," << c1_part.p3.x+offset << ",t),bez(" << c1_part.p0.y << "," << c1_part.p1.y << "," << c1_part.p2.y << "," << c1_part.p3.y << ",t), " << " pointmarkx(bez(" << c1.p0.x+offset << "," << c1.p1.x+offset << "," << c1.p2.x+offset << "," << c1.p3.x+offset << "," << t1_c1 << "),t), " << " pointmarky(bez(" << c1.p0.y << "," << c1.p1.y << "," << c1.p2.y << "," << c1.p3.y << "," << t1_c1 << "),t), " << " pointmarkx(bez(" << c1.p0.x+offset << "," << c1.p1.x+offset << "," << c1.p2.x+offset << "," << c1.p3.x+offset << "," << t2_c1 << "),t), " << " pointmarky(bez(" << c1.p0.y << "," << c1.p1.y << "," << c1.p2.y << "," << c1.p3.y << "," << t2_c1 << "),t), " << "bez(" << c2.p0.x+offset << "," << c2.p1.x+offset << "," << c2.p2.x+offset << "," << c2.p3.x+offset << ",t),bez(" << c2.p0.y << "," << c2.p1.y << "," << c2.p2.y << "," << c2.p3.y << ",t), " << "bez(" << c2_part.p0.x+offset << "," << c2_part.p1.x+offset << "," << c2_part.p2.x+offset << "," << c2_part.p3.x+offset << ",t),bez(" << c2_part.p0.y << "," << c2_part.p1.y << "," << c2_part.p2.y << "," << c2_part.p3.y << ",t)" << endl; offset += 1; } // ----------------------------------------------------------------------------- /** 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 if( Impl_doBBoxIntersect(c1_part, c2_part) ) { // Calculate fat lines around c1 FatLine bounds_c2; // must use the subdivided version of c2, since the fat line // algorithm works implicitely 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 polynom 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 return true; } } // they don't intersect: nothing to do return false; } // ----------------------------------------------------------------------------- /* Tangent intersection part * ========================= */ // ----------------------------------------------------------------------------- void Impl_calcFocus( Bezier& res, const Bezier& c ) { // arbitrary small value, for now // TODO: find meaningful value const double minPivotValue( 1.0e-20 ); Point2D::value_type fMatrix[6]; Point2D::value_type fRes[2]; // calc new curve from hodograph, c and linear blend // Coefficients for derivative of c are (C_i=n(C_{i+1} - C_i)): // // 3(P1 - P0), 3(P2 - P1), 3(P3 - P2) (bezier curve of degree 2) // // The hodograph is then (bezier curve of 2nd degree is P0(1-t)^2 + 2P1(1-t)t + P2t^2): // // 3(P1 - P0)(1-t)^2 + 6(P2 - P1)(1-t)t + 3(P3 - P2)t^2 // // rotate by 90 degrees: x=-y, y=x and you get the normal vector function N(t): // // x(t) = -(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2) // y(t) = 3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2 // // Now, the focus curve is defined to be F(t)=P(t) + c(t)N(t), // where P(t) is the original curve, and c(t)=c0(1-t) + c1 t // // This results in the following expression for F(t): // // x(t) = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 - // (c0(1-t) + c1 t)(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2) // // y(t) = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1.t)t^2 + P3.y t^3 + // (c0(1-t) + c1 t)(3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2) // // As a heuristic, we set F(0)=F(1) (thus, the curve is closed and _tends_ to be small): // // For F(0), the following results: // // x(0) = P0.x - c0 3(P1.y - P0.y) // y(0) = P0.y + c0 3(P1.x - P0.x) // // For F(1), the following results: // // x(1) = P3.x - c1 3(P3.y - P2.y) // y(1) = P3.y + c1 3(P3.x - P2.x) // // Reorder, collect and substitute into F(0)=F(1): // // P0.x - c0 3(P1.y - P0.y) = P3.x - c1 3(P3.y - P2.y) // P0.y + c0 3(P1.x - P0.x) = P3.y + c1 3(P3.x - P2.x) // // which yields // // (P0.y - P1.y)c0 + (P3.y - P2.y)c1 = (P3.x - P0.x)/3 // (P1.x - P0.x)c0 + (P2.x - P3.x)c1 = (P3.y - P0.y)/3 // // 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; cerr << "Matrix singular!" << endl; } // now, the reordered and per-coefficient collected focus curve is // the following third degree bezier curve F(t): // // x(t) = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 - // (c0(1-t) + c1 t)(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2) // = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 - // (3c0P1.y(1-t)^3 - 3c0P0.y(1-t)^3 + 6c0P2.y(1-t)^2t - 6c0P1.y(1-t)^2t + // 3c0P3.y(1-t)t^2 - 3c0P2.y(1-t)t^2 + // 3c1P1.y(1-t)^2t - 3c1P0.y(1-t)^2t + 6c1P2.y(1-t)t^2 - 6c1P1.y(1-t)t^2 + // 3c1P3.yt^3 - 3c1P2.yt^3) // = (P0.x - 3 c0 P1.y + 3 c0 P0.y)(1-t)^3 + // 3(P1.x - c1 P1.y + c1 P0.y - 2 c0 P2.y + 2 c0 P1.y)(1-t)^2t + // 3(P2.x - 2 c1 P2.y + 2 c1 P1.y - c0 P3.y + c0 P2.y)(1-t)t^2 + // (P3.x - 3 c1 P3.y + 3 c1 P2.y)t^3 // = (P0.x - 3 c0(P1.y - P0.y))(1-t)^3 + // 3(P1.x - c1(P1.y - P0.y) - 2c0(P2.y - P1.y))(1-t)^2t + // 3(P2.x - 2 c1(P2.y - P1.y) - c0(P3.y - P2.y))(1-t)t^2 + // (P3.x - 3 c1(P3.y - P2.y))t^3 // // y(t) = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1-t)t^2 + P3.y t^3 + // (c0(1-t) + c1 t)(3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2) // = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1-t)t^2 + P3.y t^3 + // 3c0(P1.x - P0.x)(1-t)^3 + 6c0(P2.x - P1.x)(1-t)^2t + 3c0(P3.x - P2.x)(1-t)t^2 + // 3c1(P1.x - P0.x)(1-t)^2t + 6c1(P2.x - P1.x)(1-t)t^2 + 3c1(P3.x - P2.x)t^3 // = (P0.y + 3 c0 (P1.x - P0.x))(1-t)^3 + // 3(P1.y + 2 c0 (P2.x - P1.x) + c1 (P1.x - P0.x))(1-t)^2t + // 3(P2.y + c0 (P3.x - P2.x) + 2 c1 (P2.x - P1.x))(1-t)t^2 + // (P3.y + 3 c1 (P3.x - P2.x))t^3 // // Therefore, the coefficients F0 to F3 of the focus curve are: // // F0.x = (P0.x - 3 c0(P1.y - P0.y)) F0.y = (P0.y + 3 c0 (P1.x - P0.x)) // F1.x = (P1.x - c1(P1.y - P0.y) - 2c0(P2.y - P1.y)) F1.y = (P1.y + 2 c0 (P2.x - P1.x) + c1 (P1.x - P0.x)) // F2.x = (P2.x - 2 c1(P2.y - P1.y) - c0(P3.y - P2.y)) F2.y = (P2.y + c0 (P3.x - P2.x) + 2 c1 (P2.x - P1.x)) // F3.x = (P3.x - 3 c1(P3.y - P2.y)) F3.y = (P3.y + 3 c1 (P3.x - P2.x)) // res.p0.x = c.p0.x - 3*fRes[0]*(c.p1.y - c.p0.y); res.p1.x = c.p1.x - fRes[1]*(c.p1.y - c.p0.y) - 2*fRes[0]*(c.p2.y - c.p1.y); res.p2.x = c.p2.x - 2*fRes[1]*(c.p2.y - c.p1.y) - fRes[0]*(c.p3.y - c.p2.y); res.p3.x = c.p3.x - 3*fRes[1]*(c.p3.y - c.p2.y); res.p0.y = c.p0.y + 3*fRes[0]*(c.p1.x - c.p0.x); res.p1.y = c.p1.y + 2*fRes[0]*(c.p2.x - c.p1.x) + fRes[1]*(c.p1.x - c.p0.x); res.p2.y = c.p2.y + fRes[0]*(c.p3.x - c.p2.x) + 2*fRes[1]*(c.p2.x - c.p1.x); res.p3.y = c.p3.y + 3*fRes[1]*(c.p3.x - c.p2.x); } // ----------------------------------------------------------------------------- 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 // // (\sum_{i=0}^n (P_i - F)B_i^n(t))^T (\sum_{j=0}^{n-1} n(P_{j+1} - P_j)B_j^{n-1}(t)) // // Multiplying that out (as the scalar product is linear, we can // extract some terms) yields: // // (P_i - F)^T n(P_{j+1} - P_j) B_i^n(t)B_j^{n-1}(t) + ... // // If we combine the B_i^n(t)B_j^{n-1}(t) product, we arrive at a // Bernstein polynomial of degree 2n-1, as // // \binom{n}{i}(1-t)^{n-i}t^i) \binom{n-1}{j}(1-t)^{n-1-j}t^j) = // \binom{n}{i}\binom{n-1}{j}(1-t)^{2n-1-i-j}t^{i+j} // // Thus, with the defining equation for a 2n-1 degree Bernstein // polynomial // // \sum_{i=0}^{2n-1} d_i B_i^{2n-1}(t) // // the d_i are calculated as follows: // // d_i = \sum_{j+k=i, j\in\{0,...,n\}, k\in\{0,...,n-1\}} \frac{\binom{n}{j}\binom{n-1}{k}}{\binom{2n-1}{i}} n (P_{k+1} - P_k)^T(P_j - F) // // // 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: // // d(t,u) = \sum_{i=0}^{2n-1} \sum_{j=0}^m B_i^{2n-1}(t) B_j^{m}(u) d_{ij}, where // d_{ij} = \sum_{k+l=i, l\in\{0,...,n\}, k\in\{0,...,n-1\}} \frac{\binom{n}{l}\binom{n-1}{k}}{\binom{2n-1}{i}} n (P_{k+1} - P_k)^T(P_l - F_j) // // 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}): // const int n( 3 ); // cubic bezier curves, as a matter of fact const int i_card( 2*n ); const int j_card( n + 1 ); const int 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=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(fallFac(n,l)*fallFac(n-1,k)*fac(i)) / static_cast(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 ); #else bool bRet( Impl_calcSafeParams( t1, t2, controlPolygon, 0.0, 0.0 ) ); Polygon2D convHull( convexHull( controlPolygon ) ); cout << "# convex hull testing (focus)" << endl << "plot [t=0:1] "; cout << "'-' using ($1):($2) title \"control polygon\" with lp, " << "'-' using ($1):($2) title \"convex hull\" with lp" << endl; unsigned int count; for( count=0; count void Impl_applySafeRanges_rec( ::std::back_insert_iterator< ::std::vector< ::std::pair > >& 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. for( int i=0; i last_t2_c2 - last_t1_c2 ) { // subdivide c1 // ============ intervalMiddle = last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0; // 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 ); // and descend recursively with swapped curves Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, c2_orig, c2_part, last_t1_c2, last_t2_c2, c1_orig, part1, last_t1_c1, intervalMiddle ); Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, c2_orig, c2_part, last_t1_c2, last_t2_c2, c1_orig, part2, intervalMiddle, last_t2_c1 ); } else { // subdivide c2 // ============ intervalMiddle = last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0; // 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 ); // and descend recursively with swapped curves Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, c2_orig, part1, last_t1_c2, intervalMiddle, c1_orig, c1_part, last_t1_c1, last_t2_c1 ); Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, c2_orig, part2, intervalMiddle, last_t2_c2, c1_orig, c1_part, last_t1_c1, last_t2_c1 ); } } else { // apply calculated clip // ===================== // clip safe ranges off c1_orig Bezier c1_part1; Bezier c1_part2; Bezier c1_part3; // 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 ClipBezierFunctor { bool operator()( double& t1_c1, double& t2_c1, const Bezier& c1_orig, const Bezier& c1_part, const Bezier& c2_orig, const Bezier& c2_part ) const { return Impl_calcClipRange( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part ); } }; // ----------------------------------------------------------------------------- struct BezierTangencyFunctor { bool operator()( 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 ) ); cerr << "range: " << t2_c1 - t1_c1 << ", ret: " << bRet << endl; return bRet; } }; // ----------------------------------------------------------------------------- /** Perform a bezier clip (curve against curve) @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 > >& 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. ::std::vector< ::std::pair > results; ::std::back_insert_iterator< ::std::vector< ::std::pair > > ii(results); Impl_calcCollinearNormals( ii, delta, 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 ); // 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 ); unsigned int i; for( i=0; i 1 ) { Bezier c1( someCurves[0] ); Bezier c2( someCurves[1] ); c1.p0.x += safeParams2_xOffset; c1.p1.x += safeParams2_xOffset; c1.p2.x += safeParams2_xOffset; c1.p3.x += safeParams2_xOffset; c2.p0.x += safeParams2_xOffset; c2.p1.x += safeParams2_xOffset; c2.p2.x += safeParams2_xOffset; c2.p3.x += safeParams2_xOffset; double t1, t2; // output happens here Impl_calcClipRange(t1, t2, c1, c1, c2, c2); } #endif #ifdef WITH_SAFEFOCUSPARAM_TEST // test safe parameter range from focus algorithm const double safeParamsFocus_xOffset( curr_Offset ); curr_Offset += 20; cout << "# safe param range from focus testing" << endl << "plot [t=0.0:1.0] "; for( i=0; i 1 ) { Bezier c1( someCurves[0] ); Bezier c2( someCurves[1] ); c1.p0.x += safeParams3_xOffset; c1.p1.x += safeParams3_xOffset; c1.p2.x += safeParams3_xOffset; c1.p3.x += safeParams3_xOffset; c2.p0.x += safeParams3_xOffset; c2.p1.x += safeParams3_xOffset; c2.p2.x += safeParams3_xOffset; c2.p3.x += safeParams3_xOffset; double t1, t2; Bezier focus; #ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS Impl_calcFocus( focus, c2 ); #else focus = c2; #endif // determine safe range on c1, output happens here Impl_calcSafeParams_focus( t1, t2, c1, focus ); } #endif #ifdef WITH_BEZIERCLIP_TEST ::std::vector< ::std::pair > result; ::std::back_insert_iterator< ::std::vector< ::std::pair > > ii(result); // test full bezier clipping const double bezierClip_xOffset( curr_Offset ); curr_Offset += 20; cout << endl << endl << "# bezier clip testing" << endl << "plot [t=0:1] "; for( i=0; i