/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org 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 version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_chart2.hxx" #include "Splines.hxx" #include #include #include #include // header for DBG_ASSERT #include //............................................................................. namespace chart { //............................................................................. using namespace ::com::sun::star; namespace { template< typename T > struct lcl_EqualsFirstDoubleOfPair : ::std::binary_function< ::std::pair< double, T >, ::std::pair< double, T >, bool > { inline bool operator() ( const ::std::pair< double, T > & rOne, const ::std::pair< double, T > & rOther ) { return ( ::rtl::math::approxEqual( rOne.first, rOther.first ) ); } }; //----------------------------------------------------------------------------- typedef ::std::pair< double, double > tPointType; typedef ::std::vector< tPointType > tPointVecType; typedef tPointVecType::size_type lcl_tSizeType; class lcl_SplineCalculation { public: /** @descr creates an object that calculates cublic splines on construction @param rSortedPoints the points for which splines shall be calculated, they need to be sorted in x values @param fY1FirstDerivation the resulting spline should have the first derivation equal to this value at the x-value of the first point of rSortedPoints. If fY1FirstDerivation is set to infinity, a natural spline is calculated. @param fYnFirstDerivation the resulting spline should have the first derivation equal to this value at the x-value of the last point of rSortedPoints */ lcl_SplineCalculation( const tPointVecType & rSortedPoints, double fY1FirstDerivation, double fYnFirstDerivation ); /** @descr this function corresponds to the function splint in [1]. [1] Numerical Recipies in C, 2nd edition William H. Press, et al., Section 3.3, page 116 */ double GetInterpolatedValue( double x ); private: /// a copy of the points given in the CTOR tPointVecType m_aPoints; /// the result of the Calculate() method ::std::vector< double > m_aSecDerivY; double m_fYp1; double m_fYpN; // these values are cached for performance reasons tPointVecType::size_type m_nKLow; tPointVecType::size_type m_nKHigh; double m_fLastInterpolatedValue; /** @descr this function corresponds to the function spline in [1]. [1] Numerical Recipies in C, 2nd edition William H. Press, et al., Section 3.3, page 115 */ void Calculate(); }; //----------------------------------------------------------------------------- lcl_SplineCalculation::lcl_SplineCalculation( const tPointVecType & rSortedPoints, double fY1FirstDerivation, double fYnFirstDerivation ) : m_aPoints( rSortedPoints ), m_fYp1( fY1FirstDerivation ), m_fYpN( fYnFirstDerivation ), m_nKLow( 0 ), m_nKHigh( rSortedPoints.size() - 1 ) { ::rtl::math::setInf( &m_fLastInterpolatedValue, sal_False ); // #108301# remove points that have equal x-values m_aPoints.erase( ::std::unique( m_aPoints.begin(), m_aPoints.end(), lcl_EqualsFirstDoubleOfPair< double >() ), m_aPoints.end() ); Calculate(); } void lcl_SplineCalculation::Calculate() { if( m_aPoints.size() <= 1 ) return; // n is the last valid index to m_aPoints const tPointVecType::size_type n = m_aPoints.size() - 1; ::std::vector< double > u( n ); m_aSecDerivY.resize( n + 1, 0.0 ); if( ::rtl::math::isInf( m_fYp1 ) ) { // natural spline m_aSecDerivY[ 0 ] = 0.0; u[ 0 ] = 0.0; } else { m_aSecDerivY[ 0 ] = -0.5; double xDiff = ( m_aPoints[ 1 ].first - m_aPoints[ 0 ].first ); u[ 0 ] = ( 3.0 / xDiff ) * ((( m_aPoints[ 1 ].second - m_aPoints[ 0 ].second ) / xDiff ) - m_fYp1 ); } for( tPointVecType::size_type i = 1; i < n; ++i ) { ::std::pair< double, double > p_i = m_aPoints[ i ], p_im1 = m_aPoints[ i - 1 ], p_ip1 = m_aPoints[ i + 1 ]; double sig = ( p_i.first - p_im1.first ) / ( p_ip1.first - p_im1.first ); double p = sig * m_aSecDerivY[ i - 1 ] + 2.0; m_aSecDerivY[ i ] = ( sig - 1.0 ) / p; u[ i ] = ( ( p_ip1.second - p_i.second ) / ( p_ip1.first - p_i.first ) ) - ( ( p_i.second - p_im1.second ) / ( p_i.first - p_im1.first ) ); u[ i ] = ( 6.0 * u[ i ] / ( p_ip1.first - p_im1.first ) - sig * u[ i - 1 ] ) / p; } // initialize to values for natural splines (used for m_fYpN equal to // infinity) double qn = 0.0; double un = 0.0; if( ! ::rtl::math::isInf( m_fYpN ) ) { qn = 0.5; double xDiff = ( m_aPoints[ n ].first - m_aPoints[ n - 1 ].first ); un = ( 3.0 / xDiff ) * ( m_fYpN - ( m_aPoints[ n ].second - m_aPoints[ n - 1 ].second ) / xDiff ); } m_aSecDerivY[ n ] = ( un - qn * u[ n - 1 ] ) * ( qn * m_aSecDerivY[ n - 1 ] + 1.0 ); // note: the algorithm in [1] iterates from n-1 to 0, but as size_type // may be (usuall is) an unsigned type, we can not write k >= 0, as this // is always true. for( tPointVecType::size_type k = n; k > 0; --k ) { ( m_aSecDerivY[ k - 1 ] *= m_aSecDerivY[ k ] ) += u[ k - 1 ]; } } double lcl_SplineCalculation::GetInterpolatedValue( double x ) { DBG_ASSERT( ( m_aPoints[ 0 ].first <= x ) && ( x <= m_aPoints[ m_aPoints.size() - 1 ].first ), "Trying to extrapolate" ); const tPointVecType::size_type n = m_aPoints.size() - 1; if( x < m_fLastInterpolatedValue ) { m_nKLow = 0; m_nKHigh = n; // calculate m_nKLow and m_nKHigh // first initialization is done in CTOR while( m_nKHigh - m_nKLow > 1 ) { tPointVecType::size_type k = ( m_nKHigh + m_nKLow ) / 2; if( m_aPoints[ k ].first > x ) m_nKHigh = k; else m_nKLow = k; } } else { while( ( m_aPoints[ m_nKHigh ].first < x ) && ( m_nKHigh <= n ) ) { ++m_nKHigh; ++m_nKLow; } DBG_ASSERT( m_nKHigh <= n, "Out of Bounds" ); } m_fLastInterpolatedValue = x; double h = m_aPoints[ m_nKHigh ].first - m_aPoints[ m_nKLow ].first; DBG_ASSERT( h != 0, "Bad input to GetInterpolatedValue()" ); double a = ( m_aPoints[ m_nKHigh ].first - x ) / h; double b = ( x - m_aPoints[ m_nKLow ].first ) / h; return ( a * m_aPoints[ m_nKLow ].second + b * m_aPoints[ m_nKHigh ].second + (( a*a*a - a ) * m_aSecDerivY[ m_nKLow ] + ( b*b*b - b ) * m_aSecDerivY[ m_nKHigh ] ) * ( h*h ) / 6.0 ); } //----------------------------------------------------------------------------- //create knot vector for B-spline double* createTVector( sal_Int32 n, sal_Int32 k ) { double* t = new double [n + k + 1]; for (sal_Int32 i=0; i<=n+k; i++ ) { if(i < k) t[i] = 0; else if(i <= n) t[i] = i-k+1; else t[i] = n-k+2; } return t; } //calculate left knot vector double TLeft (double x, sal_Int32 i, sal_Int32 k, const double *t ) { double deltaT = t[i + k - 1] - t[i]; return (deltaT == 0.0) ? 0.0 : (x - t[i]) / deltaT; } //calculate right knot vector double TRight(double x, sal_Int32 i, sal_Int32 k, const double *t ) { double deltaT = t[i + k] - t[i + 1]; return (deltaT == 0.0) ? 0.0 : (t[i + k] - x) / deltaT; } //calculate weight vector void BVector(double x, sal_Int32 n, sal_Int32 k, double *b, const double *t) { sal_Int32 i = 0; for( i=0; i<=n+k; i++ ) b[i]=0; sal_Int32 i0 = (sal_Int32)floor(x) + k - 1; b [i0] = 1; for( sal_Int32 j=2; j<=k; j++ ) for( i=0; i<=i0; i++ ) b[i] = TLeft(x, i, j, t) * b[i] + TRight(x, i, j, t) * b [i + 1]; } } // anonymous namespace //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void SplineCalculater::CalculateCubicSplines( const drawing::PolyPolygonShape3D& rInput , drawing::PolyPolygonShape3D& rResult , sal_Int32 nGranularity ) { DBG_ASSERT( nGranularity > 0, "Granularity is invalid" ); rResult.SequenceX.realloc(0); rResult.SequenceY.realloc(0); rResult.SequenceZ.realloc(0); sal_Int32 nOuterCount = rInput.SequenceX.getLength(); if( !nOuterCount ) return; rResult.SequenceX.realloc(nOuterCount); rResult.SequenceY.realloc(nOuterCount); rResult.SequenceZ.realloc(nOuterCount); for( sal_Int32 nOuter = 0; nOuter < nOuterCount; ++nOuter ) { if( rInput.SequenceX[nOuter].getLength() <= 1 ) continue; //we need at least two points sal_Int32 nMaxIndexPoints = rInput.SequenceX[nOuter].getLength()-1; // is >=1 const double* pOldX = rInput.SequenceX[nOuter].getConstArray(); const double* pOldY = rInput.SequenceY[nOuter].getConstArray(); const double* pOldZ = rInput.SequenceZ[nOuter].getConstArray(); // #i13699# The curve gets a parameter and then for each coordinate a // separate spline will be calculated using the parameter as first argument // and the point coordinate as second argument. Therefore the points need // not to be sorted in its x-coordinates. The parameter is sorted by // construction. ::std::vector < double > aParameter(nMaxIndexPoints+1); aParameter[0]=0.0; for( sal_Int32 nIndex=1; nIndex<=nMaxIndexPoints; nIndex++ ) { // The euclidian distance leads to curve loops for functions having single extreme points //aParameter[nIndex]=aParameter[nIndex-1]+ //sqrt( (pOldX[nIndex]-pOldX[nIndex-1])*(pOldX[nIndex]-pOldX[nIndex-1])+ //(pOldY[nIndex]-pOldY[nIndex-1])*(pOldY[nIndex]-pOldY[nIndex-1])+ //(pOldZ[nIndex]-pOldZ[nIndex-1])*(pOldZ[nIndex]-pOldZ[nIndex-1])); // use increment of 1 instead aParameter[nIndex]=aParameter[nIndex-1]+1; } // Split the calculation to X, Y and Z coordinate tPointVecType aInputX; aInputX.resize(nMaxIndexPoints+1); tPointVecType aInputY; aInputY.resize(nMaxIndexPoints+1); tPointVecType aInputZ; aInputZ.resize(nMaxIndexPoints+1); for (sal_Int32 nN=0;nN<=nMaxIndexPoints; nN++ ) { aInputX[ nN ].first=aParameter[nN]; aInputX[ nN ].second=pOldX[ nN ]; aInputY[ nN ].first=aParameter[nN]; aInputY[ nN ].second=pOldY[ nN ]; aInputZ[ nN ].first=aParameter[nN]; aInputZ[ nN ].second=pOldZ[ nN ]; } // generate a spline for each coordinate. It holds the complete // information to calculate each point of the curve double fXDerivation; double fYDerivation; double fZDerivation; if( pOldX[ 0 ] == pOldX[nMaxIndexPoints] && pOldY[ 0 ] == pOldY[nMaxIndexPoints] && pOldZ[ 0 ] == pOldZ[nMaxIndexPoints] ) { // #i101050# avoid a corner in closed lines, which are smoothed by spline // This derivation are special for parameter of kind 0,1,2,3... If you // change generating parameters (see above), then adapt derivations too.) fXDerivation = 0.5 * (pOldX[1]-pOldX[nMaxIndexPoints-1]); fYDerivation = 0.5 * (pOldY[1]-pOldY[nMaxIndexPoints-1]); fZDerivation = 0.5 * (pOldZ[1]-pOldZ[nMaxIndexPoints-1]); } else // generate the kind "natural spline" { double fInfty; ::rtl::math::setInf( &fInfty, sal_False ); fXDerivation = fInfty; fYDerivation = fInfty; fZDerivation = fInfty; } lcl_SplineCalculation aSplineX( aInputX, fXDerivation, fXDerivation ); lcl_SplineCalculation aSplineY( aInputY, fYDerivation, fYDerivation ); lcl_SplineCalculation aSplineZ( aInputZ, fZDerivation, fZDerivation ); // fill result polygon with calculated values rResult.SequenceX[nOuter].realloc( nMaxIndexPoints*nGranularity + 1); rResult.SequenceY[nOuter].realloc( nMaxIndexPoints*nGranularity + 1); rResult.SequenceZ[nOuter].realloc( nMaxIndexPoints*nGranularity + 1); double* pNewX = rResult.SequenceX[nOuter].getArray(); double* pNewY = rResult.SequenceY[nOuter].getArray(); double* pNewZ = rResult.SequenceZ[nOuter].getArray(); sal_Int32 nNewPointIndex = 0; // Index in result points // needed for inner loop double fInc; // step for intermediate points sal_Int32 nj; // for loop double fParam; // a intermediate parameter value for( sal_Int32 ni = 0; ni < nMaxIndexPoints; ni++ ) { // given point is surely a curve point pNewX[nNewPointIndex] = pOldX[ni]; pNewY[nNewPointIndex] = pOldY[ni]; pNewZ[nNewPointIndex] = pOldZ[ni]; nNewPointIndex++; // calculate intermediate points fInc = ( aParameter[ ni+1 ] - aParameter[ni] ) / static_cast< double >( nGranularity ); for(nj = 1; nj < nGranularity; nj++) { fParam = aParameter[ni] + ( fInc * static_cast< double >( nj ) ); pNewX[nNewPointIndex]=aSplineX.GetInterpolatedValue( fParam ); pNewY[nNewPointIndex]=aSplineY.GetInterpolatedValue( fParam ); pNewZ[nNewPointIndex]=aSplineZ.GetInterpolatedValue( fParam ); nNewPointIndex++; } } // add last point pNewX[nNewPointIndex] = pOldX[nMaxIndexPoints]; pNewY[nNewPointIndex] = pOldY[nMaxIndexPoints]; pNewZ[nNewPointIndex] = pOldZ[nMaxIndexPoints]; } } void SplineCalculater::CalculateBSplines( const ::com::sun::star::drawing::PolyPolygonShape3D& rInput , ::com::sun::star::drawing::PolyPolygonShape3D& rResult , sal_Int32 nGranularity , sal_Int32 nDegree ) { // #issue 72216# // k is the order of the BSpline, nDegree is the degree of its polynoms sal_Int32 k = nDegree + 1; rResult.SequenceX.realloc(0); rResult.SequenceY.realloc(0); rResult.SequenceZ.realloc(0); sal_Int32 nOuterCount = rInput.SequenceX.getLength(); if( !nOuterCount ) return; // no input rResult.SequenceX.realloc(nOuterCount); rResult.SequenceY.realloc(nOuterCount); rResult.SequenceZ.realloc(nOuterCount); for( sal_Int32 nOuter = 0; nOuter < nOuterCount; ++nOuter ) { if( rInput.SequenceX[nOuter].getLength() <= 1 ) continue; // need at least 2 control points sal_Int32 n = rInput.SequenceX[nOuter].getLength()-1; // maximum index of control points double fCurveparam =0.0; // parameter for the curve // 0<= fCurveparam < fMaxCurveparam double fMaxCurveparam = 2.0+ n - k; if (fMaxCurveparam <= 0.0) return; // not enough control points for desired spline order if (nGranularity < 1) return; //need at least 1 line for each part beween the control points const double* pOldX = rInput.SequenceX[nOuter].getConstArray(); const double* pOldY = rInput.SequenceY[nOuter].getConstArray(); const double* pOldZ = rInput.SequenceZ[nOuter].getConstArray(); // keep this amount of steps to go well with old version sal_Int32 nNewSectorCount = nGranularity * n; double fCurveStep = fMaxCurveparam/static_cast< double >(nNewSectorCount); double *b = new double [n + k + 1]; // values of blending functions const double* t = createTVector(n, k); // knot vector rResult.SequenceX[nOuter].realloc(nNewSectorCount+1); rResult.SequenceY[nOuter].realloc(nNewSectorCount+1); rResult.SequenceZ[nOuter].realloc(nNewSectorCount+1); double* pNewX = rResult.SequenceX[nOuter].getArray(); double* pNewY = rResult.SequenceY[nOuter].getArray(); double* pNewZ = rResult.SequenceZ[nOuter].getArray(); // variables needed inside loop, when calculating one point of output sal_Int32 nPointIndex =0; //index of given contol points double fX=0.0; double fY=0.0; double fZ=0.0; //coordinates of a new BSpline point for(sal_Int32 nNewSector=0; nNewSector