/* This file is part of the KDE project Copyright (C) 2001, 2002, 2003 The Karbon Developers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "vpainter.h" #include "vpath.h" #include "vsegment.h" #include VSegment::VSegment( unsigned short deg ) { m_degree = deg; m_nodes = new VNodeData[ degree() ]; for( unsigned short i = 0; i < degree(); ++i ) selectPoint( i ); m_state = normal; m_prev = 0L; m_next = 0L; } VSegment::VSegment( const VSegment& segment ) { m_degree = segment.degree(); m_nodes = new VNodeData[ degree() ]; m_state = segment.m_state; // Copying the pointers m_prev/m_next has some advantages (see VSegment::length()). // Inserting a segment into a path overwrites these anyway. m_prev = segment.m_prev; m_next = segment.m_next; // Copy points. for( unsigned short i = 0; i < degree(); i++ ) { setPoint( i, segment.point( i ) ); selectPoint( i, segment.pointIsSelected( i ) ); } } VSegment::~VSegment() { delete[]( m_nodes ); } void VSegment::setDegree( unsigned short deg ) { // Do nothing if old and new degrees are identical. if( degree() == deg ) return; // TODO : this code is fishy, please make it sane // Remember old nodes. VNodeData* oldNodes = m_nodes; KoPoint oldKnot = knot(); // Allocate new node data. m_nodes = new VNodeData[ deg ]; if( deg == 1 ) m_nodes[ 0 ].m_vector = oldKnot; else { // Copy old node data (from the knot "backwards". unsigned short offset = kMax( 0, deg - m_degree ); for( unsigned short i = offset; i < deg; ++i ) { m_nodes[ i ].m_vector = oldNodes[ i - offset ].m_vector; } // Fill with "zeros" if necessary. for( unsigned short i = 0; i < offset; ++i ) { m_nodes[ i ].m_vector = KoPoint( 0.0, 0.0 ); } } // Set new degree. m_degree = deg; // Delete old nodes. delete[]( oldNodes ); } void VSegment::draw( VPainter* painter ) const { // Don't draw a deleted segment. if( state() == deleted ) return; if( prev() ) { if( degree() == 3 ) { painter->curveTo( point( 0 ), point( 1 ), point( 2 ) ); } else { painter->lineTo( knot() ); } } else { painter->moveTo( knot() ); } } bool VSegment::isFlat( double flatness ) const { // Lines and "begin" segments are flat. if( !prev() || degree() == 1 ) { return true; } // Iterate over control points. for( unsigned short i = 0; i < degree() - 1; ++i ) { if( height( prev()->knot(), point( i ), knot() ) / chordLength() >= flatness ) { return false; } } return true; } KoPoint VSegment::pointAt( double t ) const { KoPoint p; pointDerivativesAt( t, &p ); return p; } void VSegment::pointDerivativesAt( double t, KoPoint* p, KoPoint* d1, KoPoint* d2 ) const { if( !prev() ) return; // Optimise the line case. if( degree() == 1 ) { const KoPoint diff = knot() - prev()->knot(); if( p ) *p = prev()->knot() + diff * t; if( d1 ) *d1 = diff; if( d2 ) *d2 = KoPoint( 0.0, 0.0 ); return; } // Beziers. // Copy points. KoPoint* q = new KoPoint[ degree() + 1 ]; q[ 0 ] = prev()->knot(); for( unsigned short i = 0; i < degree(); ++i ) { q[ i + 1 ] = point( i ); } // The De Casteljau algorithm. for( unsigned short j = 1; j <= degree(); j++ ) { for( unsigned short i = 0; i <= degree() - j; i++ ) { q[ i ] = ( 1.0 - t ) * q[ i ] + t * q[ i + 1 ]; } // Save second derivative now that we have it. if( j == degree() - 2 ) { if( d2 ) *d2 = degree() * ( degree() - 1 ) * ( q[ 2 ] - 2 * q[ 1 ] + q[ 0 ] ); } // Save first derivative now that we have it. else if( j == degree() - 1 ) { if( d1 ) *d1 = degree() * ( q[ 1 ] - q[ 0 ] ); } } // Save point. if( p ) *p = q[ 0 ]; delete[]( q ); return; } KoPoint VSegment::tangentAt( double t ) const { KoPoint tangent; pointTangentNormalAt( t, 0L, &tangent ); return tangent; } void VSegment::pointTangentNormalAt( double t, KoPoint* p, KoPoint* tn, KoPoint* n ) const { // Calculate derivative if necessary. KoPoint d; pointDerivativesAt( t, p, tn || n ? &d : 0L ); // Normalize derivative. if( tn || n ) { const double norm = sqrt( d.x() * d.x() + d.y() * d.y() ); d = norm ? d * ( 1.0 / norm ) : KoPoint( 0.0, 0.0 ); } // Assign tangent vector. if( tn ) *tn = d; // Calculate normal vector. if( n ) { // Calculate vector product of "binormal" x tangent // (0,0,1) x (dx,dy,0), which is simply (dy,-dx,0). n->setX( d.y() ); n->setY( -d.x() ); } } double VSegment::length( double t ) const { if( !prev() || t == 0.0 ) { return 0.0; } // Optimise the line case. if( degree() == 1 ) { return t * chordLength(); } /* This algortihm is by Jens Gravesen . * We calculate the chord length "chord"=|P0P3| and the length of the control point * polygon "poly"=|P0P1|+|P1P2|+|P2P3|. The approximation for the bezier length is * 0.5 * poly + 0.5 * chord. "poly - chord" is a measure for the error. * We subdivide each segment until the error is smaller than a given tolerance * and add up the subresults. */ // "Copy segment" splitted at t into a path. VSubpath path( 0L ); path.moveTo( prev()->knot() ); // Optimize a bit: most of the time we'll need the // length of the whole segment. if( t == 1.0 ) path.append( this->clone() ); else { VSegment* copy = this->clone(); path.append( copy->splitAt( t ) ); delete copy; } double chord; double poly; double length = 0.0; while( path.current() ) { chord = path.current()->chordLength(); poly = path.current()->polyLength(); if( poly && ( poly - chord ) / poly > VGlobal::lengthTolerance ) { // Split at midpoint. path.insert( path.current()->splitAt( 0.5 ) ); } else { length += 0.5 * poly + 0.5 * chord; path.next(); } } return length; } double VSegment::chordLength() const { if( !prev() ) return 0.0; KoPoint d = knot() - prev()->knot(); return sqrt( d * d ); } double VSegment::polyLength() const { if( !prev() ) return 0.0; // Start with distance |first point - previous knot|. KoPoint d = point( 0 ) - prev()->knot(); double length = sqrt( d * d ); // Iterate over remaining points. for( unsigned short i = 1; i < degree(); ++i ) { d = point( i ) - point( i - 1 ); length += sqrt( d * d ); } return length; } double VSegment::lengthParam( double len ) const { if( !prev() || len == 0.0 ) // We divide by len below. { return 0.0; } // Optimise the line case. if( degree() == 1 ) { return len / chordLength(); } // Perform a successive interval bisection. double param1 = 0.0; double paramMid = 0.5; double param2 = 1.0; double lengthMid = length( paramMid ); while( QABS( lengthMid - len ) / len > VGlobal::paramLengthTolerance ) { if( lengthMid < len ) param1 = paramMid; else param2 = paramMid; paramMid = 0.5 * ( param2 + param1 ); lengthMid = length( paramMid ); } return paramMid; } double VSegment::nearestPointParam( const KoPoint& p ) const { if( !prev() ) { return 1.0; } /* This function solves the "nearest point on curve" problem. That means, it * calculates the point q (to be precise: it's parameter t) on this segment, which * is located nearest to the input point P. * The basic idea is best described (because it is freely available) in "Phoenix: * An Interactive Curve Design System Based on the Automatic Fitting of * Hand-Sketched Curves", Philip J. Schneider (Master thesis, University of * Washington). * * For the nearest point q = C(t) on this segment, the first derivative is * orthogonal to the distance vector "C(t) - P". In other words we are looking for * solutions of f(t) = ( C(t) - P ) * C'(t) = 0. * ( C(t) - P ) is a nth degree curve, C'(t) a n-1th degree curve => f(t) is a * (2n - 1)th degree curve and thus has up to 2n - 1 distinct solutions. * We solve the problem f(t) = 0 by using something called "Approximate Inversion Method". * Let's write f(t) explicitly (with c_i = p_i - P and d_j = p_{j+1} - p_j): * * n n-1 * f(t) = SUM c_i * B^n_i(t) * SUM d_j * B^{n-1}_j(t) * i=0 j=0 * * n n-1 * = SUM SUM w_{ij} * B^{2n-1}_{i+j}(t) * i=0 j=0 * * with w_{ij} = c_i * d_j * z_{ij} and * * BinomialCoeff( n, i ) * BinomialCoeff( n - i ,j ) * z_{ij} = ----------------------------------------------- * BinomialCoeff( 2n - 1, i + j ) * * This Bernstein-Bezier polynom representation can now be solved for it's roots. */ // Calculate the c_i = point( i ) - P. KoPoint* c = new KoPoint[ degree() + 1 ]; c[ 0 ] = prev()->knot() - p; for( unsigned short i = 1; i <= degree(); ++i ) { c[ i ] = point( i - 1 ) - p; } // Calculate the d_j = point( j + 1 ) - point( j ). KoPoint* d = new KoPoint[ degree() ]; d[ 0 ] = point( 0 ) - prev()->knot(); for( unsigned short j = 1; j <= degree() - 1; ++j ) { d[ j ] = 3.0 * ( point( j ) - point( j - 1 ) ); } // Calculate the z_{ij}. double* z = new double[ degree() * ( degree() + 1 ) ]; for( unsigned short j = 0; j <= degree() - 1; ++j ) { for( unsigned short i = 0; i <= degree(); ++i ) { z[ j * ( degree() + 1 ) + i ] = static_cast( VGlobal::binomialCoeff( degree(), i ) * VGlobal::binomialCoeff( degree() - i, j ) ) / static_cast( VGlobal::binomialCoeff( 2 * degree() - 1, i + j ) ); } } // Calculate the dot products of c_i and d_i. double* products = new double[ degree() * ( degree() + 1 ) ]; for( unsigned short j = 0; j <= degree() - 1; ++j ) { for( unsigned short i = 0; i <= degree(); ++i ) { products[ j * ( degree() + 1 ) + i ] = d[ j ] * c[ i ]; } } // We don't need the c_i and d_i anymore. delete[]( d ); delete[]( c ); // Calculate the control points of the new 2n-1th degree curve. VSubpath newCurve( 0L ); newCurve.append( new VSegment( 2 * degree() - 1 ) ); // Set up control points in the ( u, f(u) )-plane. for( unsigned short u = 0; u <= 2 * degree() - 1; ++u ) { newCurve.current()->setP( u, KoPoint( static_cast( u ) / static_cast( 2 * degree() - 1 ), 0.0 ) ); } // Set f(u)-values. for( unsigned short k = 0; k <= 2 * degree() - 1; ++k ) { unsigned short min = kMin( k, degree() ); for( unsigned short i = kMax( 0, k - ( degree() - 1 ) ); i <= min; ++i ) { unsigned short j = k - i; // p_k += products[j][i] * z[j][i]. newCurve.getLast()->setP( k, KoPoint( newCurve.getLast()->p( k ).x(), newCurve.getLast()->p( k ).y() + products[ j * ( degree() + 1 ) + i ] * z[ j * ( degree() + 1 ) + i ] ) ); } } // We don't need the c_i/d_i dot products and the z_{ij} anymore. delete[]( products ); delete[]( z ); kdDebug(38000) << "results" << endl; for( int i = 0; i <= 2 * degree() - 1; ++i ) { kdDebug(38000) << newCurve.getLast()->p( i ).x() << " " << newCurve.getLast()->p( i ).y() << endl; } kdDebug(38000) << endl; // Find roots. QValueList params; newCurve.getLast()->rootParams( params ); // Now compare the distances of the candidate points. double resultParam; double distanceSquared; double oldDistanceSquared; KoPoint dist; // First candidate is the previous knot. dist = prev()->knot() - p; distanceSquared = dist * dist; resultParam = 0.0; // Iterate over the found candidate params. for( QValueListConstIterator itr = params.begin(); itr != params.end(); ++itr ) { pointDerivativesAt( *itr, &dist ); dist -= p; oldDistanceSquared = distanceSquared; distanceSquared = dist * dist; if( distanceSquared < oldDistanceSquared ) resultParam = *itr; } // Last candidate is the knot. dist = knot() - p; oldDistanceSquared = distanceSquared; distanceSquared = dist * dist; if( distanceSquared < oldDistanceSquared ) resultParam = 1.0; return resultParam; } void VSegment::rootParams( QValueList& params ) const { if( !prev() ) { return; } // Calculate how often the control polygon crosses the x-axis // This is the upper limit for the number of roots. switch( controlPolygonZeros() ) { // No solutions. case 0: return; // Exactly one solution. case 1: if( isFlat( VGlobal::flatnessTolerance / chordLength() ) ) { // Calculate intersection of chord with x-axis. KoPoint chord = knot() - prev()->knot(); kdDebug(38000) << prev()->knot().x() << " " << prev()->knot().y() << knot().x() << " " << knot().y() << " ---> " << ( chord.x() * prev()->knot().y() - chord.y() * prev()->knot().x() ) / - chord.y() << endl; params.append( ( chord.x() * prev()->knot().y() - chord.y() * prev()->knot().x() ) / - chord.y() ); return; } break; } // Many solutions. Do recursive midpoint subdivision. VSubpath path( *this ); path.insert( path.current()->splitAt( 0.5 ) ); path.current()->rootParams( params ); path.next()->rootParams( params ); } int VSegment::controlPolygonZeros() const { if( !prev() ) { return 0; } int signChanges = 0; int sign = VGlobal::sign( prev()->knot().y() ); int oldSign; for( unsigned short i = 0; i < degree(); ++i ) { oldSign = sign; sign = VGlobal::sign( point( i ).y() ); if( sign != oldSign ) { ++signChanges; } } return signChanges; } bool VSegment::isSmooth( const VSegment& next ) const { // Return false if this segment is a "begin". if( !prev() ) return false; // Calculate tangents. KoPoint t1; KoPoint t2; pointTangentNormalAt( 1.0, 0L, &t1 ); next.pointTangentNormalAt( 0.0, 0L, &t2 ); // Dot product. if( t1 * t2 >= VGlobal::parallelTolerance ) return true; return false; } KoRect VSegment::boundingBox() const { // Initialize with knot. KoRect rect( knot(), knot() ); // Add p0, if it exists. if( prev() ) { if( prev()->knot().x() < rect.left() ) rect.setLeft( prev()->knot().x() ); if( prev()->knot().x() > rect.right() ) rect.setRight( prev()->knot().x() ); if( prev()->knot().y() < rect.top() ) rect.setTop( prev()->knot().y() ); if( prev()->knot().y() > rect.bottom() ) rect.setBottom( prev()->knot().y() ); } if( degree() == 3 ) { /* The basic idea for calculating the axis aligned bounding box (AABB) for bezier segments was found in comp.graphics.algorithms: Both the x coordinate and the y coordinate are polynomial. Newton told us that at a maximum or minimum the derivative will be zero. Take all those points, and take the ends; their AABB will be that of the curve. We have a helpful trick for the derivatives: use the curve defined by differences of successive control points. This is a quadratic Bezier curve: 2 r(t) = Sum Bi,2(t) *Pi = B0,2(t) * P0 + B1,2(t) * P1 + B2,2(t) * P2 i=0 r(t) = (1-t)^2 * P0 + 2t(1-t) * P1 + t^2 * P2 r(t) = (P2 - 2*P1 + P0) * t^2 + (2*P1 - 2*P0) * t + P0 Setting r(t) to zero and using the x and y coordinates of differences of successive control points lets us find the paramters t, where the original bezier curve has a minimum or a maximum. */ double t[4]; // calcualting the differnces between successive control points KoPoint x0 = p(1)-p(0); KoPoint x1 = p(2)-p(1); KoPoint x2 = p(3)-p(2); // calculating the coefficents KoPoint a = x2 - 2.0*x1 + x0; KoPoint b = 2.0*x1 - 2.0*x0; KoPoint c = x0; // calculating parameter t at minimum/maximum in x-direction if( a.x() == 0.0 ) { t[0] = - c.x() / b.x(); t[1] = -1.0; } else { double rx = b.x()*b.x() - 4.0*a.x()*c.x(); if( rx < 0.0 ) rx = 0.0; t[0] = ( -b.x() + sqrt( rx ) ) / (2.0*a.x()); t[1] = ( -b.x() - sqrt( rx ) ) / (2.0*a.x()); } // calculating parameter t at minimum/maximum in y-direction if( a.y() == 0.0 ) { t[2] = - c.y() / b.y(); t[3] = -1.0; } else { double ry = b.y()*b.y() - 4.0*a.y()*c.y(); if( ry < 0.0 ) ry = 0.0; t[2] = ( -b.y() + sqrt( ry ) ) / (2.0*a.y()); t[3] = ( -b.y() - sqrt( ry ) ) / (2.0*a.y()); } // calculate points at found minimum/maximum and update bounding box for( int i = 0; i < 4; ++i ) { if( t[i] >= 0.0 && t[i] <= 1.0 ) { KoPoint p = pointAt( t[i] ); if( p.x() < rect.left() ) rect.setLeft( p.x() ); if( p.x() > rect.right() ) rect.setRight( p.x() ); if( p.y() < rect.top() ) rect.setTop( p.y() ); if( p.y() > rect.bottom() ) rect.setBottom( p.y() ); } } return rect; } for( unsigned short i = 0; i < degree() - 1; ++i ) { if( point( i ).x() < rect.left() ) rect.setLeft( point( i ).x() ); if( point( i ).x() > rect.right() ) rect.setRight( point( i ).x() ); if( point( i ).y() < rect.top() ) rect.setTop( point( i ).y() ); if( point( i ).y() > rect.bottom() ) rect.setBottom( point( i ).y() ); } return rect; } VSegment* VSegment::splitAt( double t ) { if( !prev() ) { return 0L; } // Create new segment. VSegment* segment = new VSegment( degree() ); // Set segment state. segment->m_state = m_state; // Lines are easy: no need to modify the current segment. if( degree() == 1 ) { segment->setKnot( prev()->knot() + ( knot() - prev()->knot() ) * t ); return segment; } // Beziers. // Copy points. KoPoint* q = new KoPoint[ degree() + 1 ]; q[ 0 ] = prev()->knot(); for( unsigned short i = 0; i < degree(); ++i ) { q[ i + 1 ] = point( i ); } // The De Casteljau algorithm. for( unsigned short j = 1; j <= degree(); ++j ) { for( unsigned short i = 0; i <= degree() - j; ++i ) { q[ i ] = ( 1.0 - t ) * q[ i ] + t * q[ i + 1 ]; } // Modify the new segment. segment->setPoint( j - 1, q[ 0 ] ); } // Modify the current segment (no need to modify the knot though). for( unsigned short i = 1; i < degree(); ++i ) { setPoint( i - 1, q[ i ] ); } delete[]( q ); return segment; } double VSegment::height( const KoPoint& a, const KoPoint& p, const KoPoint& b ) { // Calculate determinant of AP and AB to obtain projection of vector AP to // the orthogonal vector of AB. const double det = p.x() * a.y() + b.x() * p.y() - p.x() * b.y() - a.x() * p.y() + a.x() * b.y() - b.x() * a.y(); // Calculate norm = length(AB). const KoPoint ab = b - a; const double norm = sqrt( ab * ab ); // If norm is very small, simply use distance AP. if( norm < VGlobal::verySmallNumber ) return sqrt( ( p.x() - a.x() ) * ( p.x() - a.x() ) + ( p.y() - a.y() ) * ( p.y() - a.y() ) ); // Normalize. return QABS( det ) / norm; } bool VSegment::linesIntersect( const KoPoint& a0, const KoPoint& a1, const KoPoint& b0, const KoPoint& b1 ) { const KoPoint delta_a = a1 - a0; const double det_a = a1.x() * a0.y() - a1.y() * a0.x(); const double r_b0 = delta_a.y() * b0.x() - delta_a.x() * b0.y() + det_a; const double r_b1 = delta_a.y() * b1.x() - delta_a.x() * b1.y() + det_a; if( r_b0 != 0.0 && r_b1 != 0.0 && r_b0 * r_b1 > 0.0 ) return false; const KoPoint delta_b = b1 - b0; const double det_b = b1.x() * b0.y() - b1.y() * b0.x(); const double r_a0 = delta_b.y() * a0.x() - delta_b.x() * a0.y() + det_b; const double r_a1 = delta_b.y() * a1.x() - delta_b.x() * a1.y() + det_b; if( r_a0 != 0.0 && r_a1 != 0.0 && r_a0 * r_a1 > 0.0 ) return false; return true; } bool VSegment::intersects( const VSegment& segment ) const { if( !prev() || !segment.prev() ) { return false; } //TODO: this just dumbs down beziers to lines! return linesIntersect( segment.prev()->knot(), segment.knot(), prev()->knot(), knot() ); } // TODO: Move this function into "userland" uint VSegment::nodeNear( const KoPoint& p, double isNearRange ) const { int index = 0; for( unsigned short i = 0; i < degree(); ++i ) { if( point( 0 ).isNear( p, isNearRange ) ) { index = i + 1; break; } } return index; } VSegment* VSegment::revert() const { if( !prev() ) return 0L; // Create new segment. VSegment* segment = new VSegment( degree() ); segment->m_state = m_state; // Swap points. for( unsigned short i = 0; i < degree() - 1; ++i ) { segment->setPoint( i, point( degree() - 2 - i ) ); } segment->setKnot( prev()->knot() ); // TODO swap node attributes (selected) return segment; } VSegment* VSegment::prev() const { VSegment* segment = m_prev; while( segment && segment->state() == deleted ) { segment = segment->m_prev; } return segment; } VSegment* VSegment::next() const { VSegment* segment = m_next; while( segment && segment->state() == deleted ) { segment = segment->m_next; } return segment; } // TODO: remove this backward compatibility function after koffice 1.3.x void VSegment::load( const QDomElement& element ) { if( element.tagName() == "CURVE" ) { setDegree( 3 ); setPoint( 0, KoPoint( element.attribute( "x1" ).toDouble(), element.attribute( "y1" ).toDouble() ) ); setPoint( 1, KoPoint( element.attribute( "x2" ).toDouble(), element.attribute( "y2" ).toDouble() ) ); setKnot( KoPoint( element.attribute( "x3" ).toDouble(), element.attribute( "y3" ).toDouble() ) ); } else if( element.tagName() == "LINE" ) { setDegree( 1 ); setKnot( KoPoint( element.attribute( "x" ).toDouble(), element.attribute( "y" ).toDouble() ) ); } else if( element.tagName() == "MOVE" ) { setDegree( 1 ); setKnot( KoPoint( element.attribute( "x" ).toDouble(), element.attribute( "y" ).toDouble() ) ); } } VSegment* VSegment::clone() const { return new VSegment( *this ); }