1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: Splines.cxx,v $
10 * $Revision: 1.11.44.1 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
32 // MARKER(update_precomp.py): autogen include statement, do not remove
33 #include "precompiled_chart2.hxx"
35 #include "Splines.hxx"
36 #include <rtl/math.hxx>
42 // header for DBG_ASSERT
43 #include <tools/debug.hxx>
45 //.............................................................................
48 //.............................................................................
49 using namespace ::com::sun::star
;
54 template< typename T
>
55 struct lcl_EqualsFirstDoubleOfPair
: ::std::binary_function
< ::std::pair
< double, T
>, ::std::pair
< double, T
>, bool >
57 inline bool operator() ( const ::std::pair
< double, T
> & rOne
, const ::std::pair
< double, T
> & rOther
)
59 return ( ::rtl::math::approxEqual( rOne
.first
, rOther
.first
) );
63 //-----------------------------------------------------------------------------
65 typedef ::std::pair
< double, double > tPointType
;
66 typedef ::std::vector
< tPointType
> tPointVecType
;
67 typedef tPointVecType::size_type lcl_tSizeType
;
69 class lcl_SplineCalculation
72 /** @descr creates an object that calculates cublic splines on construction
74 @param rSortedPoints the points for which splines shall be calculated, they need to be sorted in x values
75 @param fY1FirstDerivation the resulting spline should have the first
76 derivation equal to this value at the x-value of the first point
77 of rSortedPoints. If fY1FirstDerivation is set to infinity, a natural
79 @param fYnFirstDerivation the resulting spline should have the first
80 derivation equal to this value at the x-value of the last point
83 lcl_SplineCalculation( const tPointVecType
& rSortedPoints
,
84 double fY1FirstDerivation
,
85 double fYnFirstDerivation
);
87 /** @descr this function corresponds to the function splint in [1].
89 [1] Numerical Recipies in C, 2nd edition
90 William H. Press, et al.,
93 double GetInterpolatedValue( double x
);
96 /// a copy of the points given in the CTOR
97 tPointVecType m_aPoints
;
99 /// the result of the Calculate() method
100 ::std::vector
< double > m_aSecDerivY
;
105 // these values are cached for performance reasons
106 tPointVecType::size_type m_nKLow
;
107 tPointVecType::size_type m_nKHigh
;
108 double m_fLastInterpolatedValue
;
110 /** @descr this function corresponds to the function spline in [1].
112 [1] Numerical Recipies in C, 2nd edition
113 William H. Press, et al.,
114 Section 3.3, page 115
119 //-----------------------------------------------------------------------------
121 lcl_SplineCalculation::lcl_SplineCalculation(
122 const tPointVecType
& rSortedPoints
,
123 double fY1FirstDerivation
,
124 double fYnFirstDerivation
)
125 : m_aPoints( rSortedPoints
),
126 m_fYp1( fY1FirstDerivation
),
127 m_fYpN( fYnFirstDerivation
),
129 m_nKHigh( rSortedPoints
.size() - 1 )
131 ::rtl::math::setInf( &m_fLastInterpolatedValue
, sal_False
);
133 // #108301# remove points that have equal x-values
134 m_aPoints
.erase( ::std::unique( m_aPoints
.begin(), m_aPoints
.end(),
135 lcl_EqualsFirstDoubleOfPair
< double >() ),
140 void lcl_SplineCalculation::Calculate()
142 if( m_aPoints
.size() <= 1 )
145 // n is the last valid index to m_aPoints
146 const tPointVecType::size_type n
= m_aPoints
.size() - 1;
147 ::std::vector
< double > u( n
);
148 m_aSecDerivY
.resize( n
+ 1, 0.0 );
150 if( ::rtl::math::isInf( m_fYp1
) )
153 m_aSecDerivY
[ 0 ] = 0.0;
158 m_aSecDerivY
[ 0 ] = -0.5;
159 double xDiff
= ( m_aPoints
[ 1 ].first
- m_aPoints
[ 0 ].first
);
160 u
[ 0 ] = ( 3.0 / xDiff
) *
161 ((( m_aPoints
[ 1 ].second
- m_aPoints
[ 0 ].second
) / xDiff
) - m_fYp1
);
164 for( tPointVecType::size_type i
= 1; i
< n
; ++i
)
166 ::std::pair
< double, double >
167 p_i
= m_aPoints
[ i
],
168 p_im1
= m_aPoints
[ i
- 1 ],
169 p_ip1
= m_aPoints
[ i
+ 1 ];
171 double sig
= ( p_i
.first
- p_im1
.first
) /
172 ( p_ip1
.first
- p_im1
.first
);
173 double p
= sig
* m_aSecDerivY
[ i
- 1 ] + 2.0;
175 m_aSecDerivY
[ i
] = ( sig
- 1.0 ) / p
;
177 ( ( p_ip1
.second
- p_i
.second
) /
178 ( p_ip1
.first
- p_i
.first
) ) -
179 ( ( p_i
.second
- p_im1
.second
) /
180 ( p_i
.first
- p_im1
.first
) );
182 ( 6.0 * u
[ i
] / ( p_ip1
.first
- p_im1
.first
)
183 - sig
* u
[ i
- 1 ] ) / p
;
186 // initialize to values for natural splines (used for m_fYpN equal to
191 if( ! ::rtl::math::isInf( m_fYpN
) )
194 double xDiff
= ( m_aPoints
[ n
].first
- m_aPoints
[ n
- 1 ].first
);
195 un
= ( 3.0 / xDiff
) *
196 ( m_fYpN
- ( m_aPoints
[ n
].second
- m_aPoints
[ n
- 1 ].second
) / xDiff
);
199 m_aSecDerivY
[ n
] = ( un
- qn
* u
[ n
- 1 ] ) * ( qn
* m_aSecDerivY
[ n
- 1 ] + 1.0 );
201 // note: the algorithm in [1] iterates from n-1 to 0, but as size_type
202 // may be (usuall is) an unsigned type, we can not write k >= 0, as this
204 for( tPointVecType::size_type k
= n
; k
> 0; --k
)
206 ( m_aSecDerivY
[ k
- 1 ] *= m_aSecDerivY
[ k
] ) += u
[ k
- 1 ];
210 double lcl_SplineCalculation::GetInterpolatedValue( double x
)
212 DBG_ASSERT( ( m_aPoints
[ 0 ].first
<= x
) &&
213 ( x
<= m_aPoints
[ m_aPoints
.size() - 1 ].first
),
214 "Trying to extrapolate" );
216 const tPointVecType::size_type n
= m_aPoints
.size() - 1;
217 if( x
< m_fLastInterpolatedValue
)
222 // calculate m_nKLow and m_nKHigh
223 // first initialization is done in CTOR
224 while( m_nKHigh
- m_nKLow
> 1 )
226 tPointVecType::size_type k
= ( m_nKHigh
+ m_nKLow
) / 2;
227 if( m_aPoints
[ k
].first
> x
)
235 while( ( m_aPoints
[ m_nKHigh
].first
< x
) &&
241 DBG_ASSERT( m_nKHigh
<= n
, "Out of Bounds" );
243 m_fLastInterpolatedValue
= x
;
245 double h
= m_aPoints
[ m_nKHigh
].first
- m_aPoints
[ m_nKLow
].first
;
246 DBG_ASSERT( h
!= 0, "Bad input to GetInterpolatedValue()" );
248 double a
= ( m_aPoints
[ m_nKHigh
].first
- x
) / h
;
249 double b
= ( x
- m_aPoints
[ m_nKLow
].first
) / h
;
251 return ( a
* m_aPoints
[ m_nKLow
].second
+
252 b
* m_aPoints
[ m_nKHigh
].second
+
253 (( a
*a
*a
- a
) * m_aSecDerivY
[ m_nKLow
] +
254 ( b
*b
*b
- b
) * m_aSecDerivY
[ m_nKHigh
] ) *
258 //-----------------------------------------------------------------------------
260 //create knot vector for B-spline
261 double* createTVector( sal_Int32 n
, sal_Int32 k
)
263 double* t
= new double [n
+ k
+ 1];
264 for (sal_Int32 i
=0; i
<=n
+k
; i
++ )
276 //calculate left knot vector
277 double TLeft (double x
, sal_Int32 i
, sal_Int32 k
, const double *t
)
279 double deltaT
= t
[i
+ k
- 1] - t
[i
];
280 return (deltaT
== 0.0)
282 : (x
- t
[i
]) / deltaT
;
285 //calculate right knot vector
286 double TRight(double x
, sal_Int32 i
, sal_Int32 k
, const double *t
)
288 double deltaT
= t
[i
+ k
] - t
[i
+ 1];
289 return (deltaT
== 0.0)
291 : (t
[i
+ k
] - x
) / deltaT
;
294 //calculate weight vector
295 void BVector(double x
, sal_Int32 n
, sal_Int32 k
, double *b
, const double *t
)
298 for( i
=0; i
<=n
+k
; i
++ )
301 sal_Int32 i0
= (sal_Int32
)floor(x
) + k
- 1;
304 for( sal_Int32 j
=2; j
<=k
; j
++ )
305 for( i
=0; i
<=i0
; i
++ )
306 b
[i
] = TLeft(x
, i
, j
, t
) * b
[i
] + TRight(x
, i
, j
, t
) * b
[i
+ 1];
309 } // anonymous namespace
311 //-----------------------------------------------------------------------------
312 //-----------------------------------------------------------------------------
313 //-----------------------------------------------------------------------------
315 void SplineCalculater::CalculateCubicSplines(
316 const drawing::PolyPolygonShape3D
& rInput
317 , drawing::PolyPolygonShape3D
& rResult
318 , sal_Int32 nGranularity
)
320 DBG_ASSERT( nGranularity
> 0, "Granularity is invalid" );
322 rResult
.SequenceX
.realloc(0);
323 rResult
.SequenceY
.realloc(0);
324 rResult
.SequenceZ
.realloc(0);
326 sal_Int32 nOuterCount
= rInput
.SequenceX
.getLength();
330 rResult
.SequenceX
.realloc(nOuterCount
);
331 rResult
.SequenceY
.realloc(nOuterCount
);
332 rResult
.SequenceZ
.realloc(nOuterCount
);
334 for( sal_Int32 nOuter
= 0; nOuter
< nOuterCount
; ++nOuter
)
336 if( rInput
.SequenceX
[nOuter
].getLength() <= 1 )
337 continue; //we need at least two points
339 sal_Int32 nMaxIndexPoints
= rInput
.SequenceX
[nOuter
].getLength()-1; // is >=1
340 const double* pOldX
= rInput
.SequenceX
[nOuter
].getConstArray();
341 const double* pOldY
= rInput
.SequenceY
[nOuter
].getConstArray();
342 const double* pOldZ
= rInput
.SequenceZ
[nOuter
].getConstArray();
344 // #i13699# The curve gets a parameter and then for each coordinate a
345 // separate spline will be calculated using the parameter as first argument
346 // and the point coordinate as second argument. Therefore the points need
347 // not to be sorted in its x-coordinates. The parameter is sorted by
350 ::std::vector
< double > aParameter(nMaxIndexPoints
+1);
352 for( sal_Int32 nIndex
=1; nIndex
<=nMaxIndexPoints
; nIndex
++ )
354 // The euclidian distance leads to curve loops for functions having single extreme points
355 //aParameter[nIndex]=aParameter[nIndex-1]+
356 //sqrt( (pOldX[nIndex]-pOldX[nIndex-1])*(pOldX[nIndex]-pOldX[nIndex-1])+
357 //(pOldY[nIndex]-pOldY[nIndex-1])*(pOldY[nIndex]-pOldY[nIndex-1])+
358 //(pOldZ[nIndex]-pOldZ[nIndex-1])*(pOldZ[nIndex]-pOldZ[nIndex-1]));
360 // use increment of 1 instead
361 aParameter
[nIndex
]=aParameter
[nIndex
-1]+1;
363 // Split the calculation to X, Y and Z coordinate
364 tPointVecType aInputX
;
365 aInputX
.resize(nMaxIndexPoints
+1);
366 tPointVecType aInputY
;
367 aInputY
.resize(nMaxIndexPoints
+1);
368 tPointVecType aInputZ
;
369 aInputZ
.resize(nMaxIndexPoints
+1);
370 for (sal_Int32 nN
=0;nN
<=nMaxIndexPoints
; nN
++ )
372 aInputX
[ nN
].first
=aParameter
[nN
];
373 aInputX
[ nN
].second
=pOldX
[ nN
];
374 aInputY
[ nN
].first
=aParameter
[nN
];
375 aInputY
[ nN
].second
=pOldY
[ nN
];
376 aInputZ
[ nN
].first
=aParameter
[nN
];
377 aInputZ
[ nN
].second
=pOldZ
[ nN
];
380 // generate a spline for each coordinate. It holds the complete
381 // information to calculate each point of the curve
385 if( pOldX
[ 0 ] == pOldX
[nMaxIndexPoints
] &&
386 pOldY
[ 0 ] == pOldY
[nMaxIndexPoints
] &&
387 pOldZ
[ 0 ] == pOldZ
[nMaxIndexPoints
] )
389 // #i101050# avoid a corner in closed lines, which are smoothed by spline
390 // This derivation are special for parameter of kind 0,1,2,3... If you
391 // change generating parameters (see above), then adapt derivations too.)
392 fXDerivation
= 0.5 * (pOldX
[1]-pOldX
[nMaxIndexPoints
-1]);
393 fYDerivation
= 0.5 * (pOldY
[1]-pOldY
[nMaxIndexPoints
-1]);
394 fZDerivation
= 0.5 * (pOldZ
[1]-pOldZ
[nMaxIndexPoints
-1]);
396 else // generate the kind "natural spline"
399 ::rtl::math::setInf( &fInfty
, sal_False
);
400 fXDerivation
= fInfty
;
401 fYDerivation
= fInfty
;
402 fZDerivation
= fInfty
;
404 lcl_SplineCalculation
aSplineX( aInputX
, fXDerivation
, fXDerivation
);
405 lcl_SplineCalculation
aSplineY( aInputY
, fYDerivation
, fYDerivation
);
406 lcl_SplineCalculation
aSplineZ( aInputZ
, fZDerivation
, fZDerivation
);
408 // fill result polygon with calculated values
409 rResult
.SequenceX
[nOuter
].realloc( nMaxIndexPoints
*nGranularity
+ 1);
410 rResult
.SequenceY
[nOuter
].realloc( nMaxIndexPoints
*nGranularity
+ 1);
411 rResult
.SequenceZ
[nOuter
].realloc( nMaxIndexPoints
*nGranularity
+ 1);
413 double* pNewX
= rResult
.SequenceX
[nOuter
].getArray();
414 double* pNewY
= rResult
.SequenceY
[nOuter
].getArray();
415 double* pNewZ
= rResult
.SequenceZ
[nOuter
].getArray();
417 sal_Int32 nNewPointIndex
= 0; // Index in result points
418 // needed for inner loop
419 double fInc
; // step for intermediate points
420 sal_Int32 nj
; // for loop
421 double fParam
; // a intermediate parameter value
423 for( sal_Int32 ni
= 0; ni
< nMaxIndexPoints
; ni
++ )
425 // given point is surely a curve point
426 pNewX
[nNewPointIndex
] = pOldX
[ni
];
427 pNewY
[nNewPointIndex
] = pOldY
[ni
];
428 pNewZ
[nNewPointIndex
] = pOldZ
[ni
];
431 // calculate intermediate points
432 fInc
= ( aParameter
[ ni
+1 ] - aParameter
[ni
] ) / static_cast< double >( nGranularity
);
433 for(nj
= 1; nj
< nGranularity
; nj
++)
435 fParam
= aParameter
[ni
] + ( fInc
* static_cast< double >( nj
) );
437 pNewX
[nNewPointIndex
]=aSplineX
.GetInterpolatedValue( fParam
);
438 pNewY
[nNewPointIndex
]=aSplineY
.GetInterpolatedValue( fParam
);
439 pNewZ
[nNewPointIndex
]=aSplineZ
.GetInterpolatedValue( fParam
);
444 pNewX
[nNewPointIndex
] = pOldX
[nMaxIndexPoints
];
445 pNewY
[nNewPointIndex
] = pOldY
[nMaxIndexPoints
];
446 pNewZ
[nNewPointIndex
] = pOldZ
[nMaxIndexPoints
];
450 void SplineCalculater::CalculateBSplines(
451 const ::com::sun::star::drawing::PolyPolygonShape3D
& rInput
452 , ::com::sun::star::drawing::PolyPolygonShape3D
& rResult
453 , sal_Int32 nGranularity
454 , sal_Int32 nDegree
)
457 // k is the order of the BSpline, nDegree is the degree of its polynoms
458 sal_Int32 k
= nDegree
+ 1;
460 rResult
.SequenceX
.realloc(0);
461 rResult
.SequenceY
.realloc(0);
462 rResult
.SequenceZ
.realloc(0);
464 sal_Int32 nOuterCount
= rInput
.SequenceX
.getLength();
468 rResult
.SequenceX
.realloc(nOuterCount
);
469 rResult
.SequenceY
.realloc(nOuterCount
);
470 rResult
.SequenceZ
.realloc(nOuterCount
);
472 for( sal_Int32 nOuter
= 0; nOuter
< nOuterCount
; ++nOuter
)
474 if( rInput
.SequenceX
[nOuter
].getLength() <= 1 )
475 continue; // need at least 2 control points
477 sal_Int32 n
= rInput
.SequenceX
[nOuter
].getLength()-1; // maximum index of control points
479 double fCurveparam
=0.0; // parameter for the curve
480 // 0<= fCurveparam < fMaxCurveparam
481 double fMaxCurveparam
= 2.0+ n
- k
;
482 if (fMaxCurveparam
<= 0.0)
483 return; // not enough control points for desired spline order
485 if (nGranularity
< 1)
486 return; //need at least 1 line for each part beween the control points
488 const double* pOldX
= rInput
.SequenceX
[nOuter
].getConstArray();
489 const double* pOldY
= rInput
.SequenceY
[nOuter
].getConstArray();
490 const double* pOldZ
= rInput
.SequenceZ
[nOuter
].getConstArray();
492 // keep this amount of steps to go well with old version
493 sal_Int32 nNewSectorCount
= nGranularity
* n
;
494 double fCurveStep
= fMaxCurveparam
/static_cast< double >(nNewSectorCount
);
496 double *b
= new double [n
+ k
+ 1]; // values of blending functions
498 const double* t
= createTVector(n
, k
); // knot vector
500 rResult
.SequenceX
[nOuter
].realloc(nNewSectorCount
+1);
501 rResult
.SequenceY
[nOuter
].realloc(nNewSectorCount
+1);
502 rResult
.SequenceZ
[nOuter
].realloc(nNewSectorCount
+1);
503 double* pNewX
= rResult
.SequenceX
[nOuter
].getArray();
504 double* pNewY
= rResult
.SequenceY
[nOuter
].getArray();
505 double* pNewZ
= rResult
.SequenceZ
[nOuter
].getArray();
507 // variables needed inside loop, when calculating one point of output
508 sal_Int32 nPointIndex
=0; //index of given contol points
511 double fZ
=0.0; //coordinates of a new BSpline point
513 for(sal_Int32 nNewSector
=0; nNewSector
<nNewSectorCount
; nNewSector
++)
514 { // in first looping fCurveparam has value 0.0
516 // Calculate the values of the blending functions for actual curve parameter
517 BVector(fCurveparam
, n
, k
, b
, t
);
519 // output point(fCurveparam) = sum over {input point * value of blending function}
523 for (nPointIndex
=0;nPointIndex
<=n
;nPointIndex
++)
525 fX
+=pOldX
[nPointIndex
]*b
[nPointIndex
];
526 fY
+=pOldY
[nPointIndex
]*b
[nPointIndex
];
527 fZ
+=pOldZ
[nPointIndex
]*b
[nPointIndex
];
529 pNewX
[nNewSector
] = fX
;
530 pNewY
[nNewSector
] = fY
;
531 pNewZ
[nNewSector
] = fZ
;
533 fCurveparam
+= fCurveStep
; //for next looping
535 // add last control point to BSpline curve
536 pNewX
[nNewSectorCount
] = pOldX
[n
];
537 pNewY
[nNewSectorCount
] = pOldY
[n
];
538 pNewZ
[nNewSectorCount
] = pOldZ
[n
];
545 //.............................................................................
547 //.............................................................................