1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "Splines.hxx"
21 #include <osl/diagnose.h>
22 #include <com/sun/star/drawing/Position3D.hpp>
32 using namespace ::com::sun::star
;
37 typedef std::pair
< double, double > tPointType
;
38 typedef std::vector
< tPointType
> tPointVecType
;
39 typedef tPointVecType::size_type lcl_tSizeType
;
41 class lcl_SplineCalculation
44 /** @descr creates an object that calculates cubic splines on construction
46 @param rSortedPoints the points for which splines shall be calculated, they need to be sorted in x values
47 @param fY1FirstDerivation the resulting spline should have the first
48 derivation equal to this value at the x-value of the first point
49 of rSortedPoints. If fY1FirstDerivation is set to infinity, a natural
51 @param fYnFirstDerivation the resulting spline should have the first
52 derivation equal to this value at the x-value of the last point
55 lcl_SplineCalculation( tPointVecType
&& rSortedPoints
,
56 double fY1FirstDerivation
,
57 double fYnFirstDerivation
);
59 /** @descr creates an object that calculates cubic splines on construction
60 for the special case of periodic cubic spline
62 @param rSortedPoints the points for which splines shall be calculated,
63 they need to be sorted in x values. First and last y value must be equal
65 explicit lcl_SplineCalculation( tPointVecType
&& rSortedPoints
);
67 /** @descr this function corresponds to the function splint in [1].
69 [1] Numerical Recipes in C, 2nd edition
70 William H. Press, et al.,
73 double GetInterpolatedValue( double x
);
76 /// a copy of the points given in the CTOR
77 tPointVecType m_aPoints
;
79 /// the result of the Calculate() method
80 std::vector
< double > m_aSecDerivY
;
85 // these values are cached for performance reasons
86 lcl_tSizeType m_nKLow
;
87 lcl_tSizeType m_nKHigh
;
88 double m_fLastInterpolatedValue
;
90 /** @descr this function corresponds to the function spline in [1].
92 [1] Numerical Recipes in C, 2nd edition
93 William H. Press, et al.,
98 /** @descr this function corresponds to the algorithm 4.76 in [2] and
101 [2] Engeln-Müllges, Gisela: Numerik-Algorithmen: Verfahren, Beispiele, Anwendungen
102 Springer, Berlin; Auflage: 9., überarb. und erw. A. (8. Dezember 2004)
103 Section 4.10.2, page 175
105 [3] Hanrath, Wilhelm: Mathematik III / Numerik, Vorlesungsskript zur
106 Veranstaltung im WS 2007/2008
107 Fachhochschule Aachen, 2009-09-19
108 Numerik_01.pdf, downloaded 2011-04-19 via
109 http://www.fh-aachen.de/index.php?id=11424&no_cache=1&file=5016&uid=44191
110 Section 5.3, page 129
112 void CalculatePeriodic();
115 lcl_SplineCalculation::lcl_SplineCalculation(
116 tPointVecType
&& rSortedPoints
,
117 double fY1FirstDerivation
,
118 double fYnFirstDerivation
)
119 : m_aPoints( std::move(rSortedPoints
) ),
120 m_fYp1( fY1FirstDerivation
),
121 m_fYpN( fYnFirstDerivation
),
123 m_nKHigh( m_aPoints
.size() - 1 ),
124 m_fLastInterpolatedValue(std::numeric_limits
<double>::infinity())
129 lcl_SplineCalculation::lcl_SplineCalculation(
130 tPointVecType
&& rSortedPoints
)
131 : m_aPoints( std::move(rSortedPoints
) ),
132 m_fYp1( 0.0 ), /*dummy*/
133 m_fYpN( 0.0 ), /*dummy*/
135 m_nKHigh( m_aPoints
.size() - 1 ),
136 m_fLastInterpolatedValue(std::numeric_limits
<double>::infinity())
141 void lcl_SplineCalculation::Calculate()
143 if( m_aPoints
.size() <= 1 )
146 // n is the last valid index to m_aPoints
147 const lcl_tSizeType n
= m_aPoints
.size() - 1;
148 std::vector
< double > u( n
);
149 m_aSecDerivY
.resize( n
+ 1, 0.0 );
151 if( std::isinf( m_fYp1
) )
154 m_aSecDerivY
[ 0 ] = 0.0;
159 m_aSecDerivY
[ 0 ] = -0.5;
160 double xDiff
= m_aPoints
[ 1 ].first
- m_aPoints
[ 0 ].first
;
161 u
[ 0 ] = ( 3.0 / xDiff
) *
162 ((( m_aPoints
[ 1 ].second
- m_aPoints
[ 0 ].second
) / xDiff
) - m_fYp1
);
165 for( lcl_tSizeType i
= 1; i
< n
; ++i
)
168 p_i
= m_aPoints
[ i
],
169 p_im1
= m_aPoints
[ i
- 1 ],
170 p_ip1
= m_aPoints
[ i
+ 1 ];
172 double sig
= ( p_i
.first
- p_im1
.first
) /
173 ( p_ip1
.first
- p_im1
.first
);
174 double p
= sig
* m_aSecDerivY
[ i
- 1 ] + 2.0;
176 m_aSecDerivY
[ i
] = ( sig
- 1.0 ) / p
;
178 ( ( p_ip1
.second
- p_i
.second
) /
179 ( p_ip1
.first
- p_i
.first
) ) -
180 ( ( p_i
.second
- p_im1
.second
) /
181 ( p_i
.first
- p_im1
.first
) );
183 ( 6.0 * u
[ i
] / ( p_ip1
.first
- p_im1
.first
)
184 - sig
* u
[ i
- 1 ] ) / p
;
187 // initialize to values for natural splines (used for m_fYpN equal to
192 if( ! std::isinf( m_fYpN
) )
195 double xDiff
= m_aPoints
[ n
].first
- m_aPoints
[ n
- 1 ].first
;
196 un
= ( 3.0 / xDiff
) *
197 ( m_fYpN
- ( m_aPoints
[ n
].second
- m_aPoints
[ n
- 1 ].second
) / xDiff
);
200 m_aSecDerivY
[ n
] = ( un
- qn
* u
[ n
- 1 ] ) / ( qn
* m_aSecDerivY
[ n
- 1 ] + 1.0 );
202 // note: the algorithm in [1] iterates from n-1 to 0, but as size_type
203 // may be (usually is) an unsigned type, we can not write k >= 0, as this
205 for( lcl_tSizeType k
= n
; k
> 0; --k
)
207 m_aSecDerivY
[ k
- 1 ] = (m_aSecDerivY
[ k
- 1 ] * m_aSecDerivY
[ k
] ) + u
[ k
- 1 ];
211 void lcl_SplineCalculation::CalculatePeriodic()
213 if( m_aPoints
.size() <= 1 )
216 // n is the last valid index to m_aPoints
217 const lcl_tSizeType n
= m_aPoints
.size() - 1;
219 // u is used for vector f in A*c=f in [3], vector a in Ax=a in [2],
220 // vector z in Rtranspose z = a and Dr=z in [2]
221 std::vector
< double > u( n
+ 1, 0.0 );
223 // used for vector c in A*c=f and vector x in Ax=a in [2]
224 m_aSecDerivY
.resize( n
+ 1, 0.0 );
226 // diagonal of matrix A, used index 1 to n
227 std::vector
< double > Adiag( n
+ 1, 0.0 );
229 // secondary diagonal of matrix A with index 1 to n-1 and upper right element in A[n]
230 std::vector
< double > Aupper( n
+ 1, 0.0 );
232 // diagonal of matrix D in A=(R transpose)*D*R in [2], used index 1 to n
233 std::vector
< double > Ddiag( n
+1, 0.0 );
235 // right column of matrix R, used index 1 to n-2
236 std::vector
< double > Rright( n
-1, 0.0 );
238 // secondary diagonal of matrix R, used index 1 to n-1
239 std::vector
< double > Rupper( n
, 0.0 );
244 { // special handling of three polynomials, that are four points
245 double xDiff0
= m_aPoints
[ 1 ].first
- m_aPoints
[ 0 ].first
;
246 double xDiff1
= m_aPoints
[ 2 ].first
- m_aPoints
[ 1 ].first
;
247 double xDiff2
= m_aPoints
[ 3 ].first
- m_aPoints
[ 2 ].first
;
248 double xDiff2p1
= xDiff2
+ xDiff1
;
249 double xDiff0p2
= xDiff0
+ xDiff2
;
250 double xDiff1p0
= xDiff1
+ xDiff0
;
251 double fFactor
= 1.5 / (xDiff0
*xDiff1
+ xDiff1
*xDiff2
+ xDiff2
*xDiff0
);
252 double yDiff0
= (m_aPoints
[ 1 ].second
- m_aPoints
[ 0 ].second
) / xDiff0
;
253 double yDiff1
= (m_aPoints
[ 2 ].second
- m_aPoints
[ 1 ].second
) / xDiff1
;
254 double yDiff2
= (m_aPoints
[ 0 ].second
- m_aPoints
[ 2 ].second
) / xDiff2
;
255 m_aSecDerivY
[ 1 ] = fFactor
* (yDiff1
*xDiff2p1
- yDiff0
*xDiff0p2
);
256 m_aSecDerivY
[ 2 ] = fFactor
* (yDiff2
*xDiff0p2
- yDiff1
*xDiff1p0
);
257 m_aSecDerivY
[ 3 ] = fFactor
* (yDiff0
*xDiff1p0
- yDiff2
*xDiff2p1
);
258 m_aSecDerivY
[ 0 ] = m_aSecDerivY
[ 3 ];
262 // special handling of two polynomials, that are three points
263 double xDiff0
= m_aPoints
[ 1 ].first
- m_aPoints
[ 0 ].first
;
264 double xDiff1
= m_aPoints
[ 2 ].first
- m_aPoints
[ 1 ].first
;
265 double fHelp
= 3.0 * (m_aPoints
[ 0 ].second
- m_aPoints
[ 1 ].second
) / (xDiff0
*xDiff1
);
266 m_aSecDerivY
[ 1 ] = fHelp
;
267 m_aSecDerivY
[ 2 ] = -fHelp
;
268 m_aSecDerivY
[ 0 ] = m_aSecDerivY
[ 2 ] ;
272 // should be handled with natural spline, periodic not possible.
277 double xDiff_i
=1.0; // values are dummy;
278 double xDiff_im1
=1.0;
279 double yDiff_i
= 1.0;
280 double yDiff_im1
= 1.0;
281 // fill matrix A and fill right side vector u
282 for( lcl_tSizeType i
=1; i
<n
; ++i
)
284 xDiff_im1
= m_aPoints
[ i
].first
- m_aPoints
[ i
-1 ].first
;
285 xDiff_i
= m_aPoints
[ i
+1 ].first
- m_aPoints
[ i
].first
;
286 yDiff_im1
= (m_aPoints
[ i
].second
- m_aPoints
[ i
-1 ].second
) / xDiff_im1
;
287 yDiff_i
= (m_aPoints
[ i
+1 ].second
- m_aPoints
[ i
].second
) / xDiff_i
;
288 Adiag
[ i
] = 2 * (xDiff_im1
+ xDiff_i
);
289 Aupper
[ i
] = xDiff_i
;
290 u
[ i
] = 3 * (yDiff_i
- yDiff_im1
);
292 xDiff_im1
= m_aPoints
[ n
].first
- m_aPoints
[ n
-1 ].first
;
293 xDiff_i
= m_aPoints
[ 1 ].first
- m_aPoints
[ 0 ].first
;
294 yDiff_im1
= (m_aPoints
[ n
].second
- m_aPoints
[ n
-1 ].second
) / xDiff_im1
;
295 yDiff_i
= (m_aPoints
[ 1 ].second
- m_aPoints
[ 0 ].second
) / xDiff_i
;
296 Adiag
[ n
] = 2 * (xDiff_im1
+ xDiff_i
);
297 Aupper
[ n
] = xDiff_i
;
298 u
[ n
] = 3 * (yDiff_i
- yDiff_im1
);
300 // decomposite A=(R transpose)*D*R
302 Rupper
[1] = Aupper
[1] / Ddiag
[1];
303 Rright
[1] = Aupper
[n
] / Ddiag
[1];
304 for( lcl_tSizeType i
=2; i
<=n
-2; ++i
)
306 Ddiag
[i
] = Adiag
[i
] - Aupper
[ i
-1 ] * Rupper
[ i
-1 ];
307 Rupper
[ i
] = Aupper
[ i
] / Ddiag
[ i
];
308 Rright
[ i
] = - Rright
[ i
-1 ] * Aupper
[ i
-1 ] / Ddiag
[ i
];
310 Ddiag
[ n
-1 ] = Adiag
[ n
-1 ] - Aupper
[ n
-2 ] * Rupper
[ n
-2 ];
311 Rupper
[ n
-1 ] = ( Aupper
[ n
-1 ] - Aupper
[ n
-2 ] * Rright
[ n
-2] ) / Ddiag
[ n
-1 ];
313 for ( lcl_tSizeType i
=1; i
<=n
-2; ++i
)
315 fSum
+= Ddiag
[ i
] * Rright
[ i
] * Rright
[ i
];
317 Ddiag
[ n
] = Adiag
[ n
] - fSum
- Ddiag
[ n
-1 ] * Rupper
[ n
-1 ] * Rupper
[ n
-1 ]; // bug in [2]!
319 // solve forward (R transpose)*z=u, overwrite u with z
320 for ( lcl_tSizeType i
=2; i
<=n
-1; ++i
)
322 u
[ i
] -= u
[ i
-1 ]* Rupper
[ i
-1 ];
325 for ( lcl_tSizeType i
=1; i
<=n
-2; ++i
)
327 fSum
+= Rright
[ i
] * u
[ i
];
329 u
[ n
] = u
[ n
] - fSum
- Rupper
[ n
- 1] * u
[ n
-1 ];
331 // solve forward D*r=z, z is in u, overwrite u with r
332 for ( lcl_tSizeType i
=1; i
<=n
; ++i
)
334 u
[ i
] = u
[i
] / Ddiag
[ i
];
337 // solve backward R*x= r, r is in u
338 m_aSecDerivY
[ n
] = u
[ n
];
339 m_aSecDerivY
[ n
-1 ] = u
[ n
-1 ] - Rupper
[ n
-1 ] * m_aSecDerivY
[ n
];
340 for ( lcl_tSizeType i
=n
-2; i
>=1; --i
)
342 m_aSecDerivY
[ i
] = u
[ i
] - Rupper
[ i
] * m_aSecDerivY
[ i
+1 ] - Rright
[ i
] * m_aSecDerivY
[ n
];
345 m_aSecDerivY
[ 0 ] = m_aSecDerivY
[ n
];
348 // adapt m_aSecDerivY for usage in GetInterpolatedValue()
349 for( lcl_tSizeType i
= 0; i
<= n
; ++i
)
351 m_aSecDerivY
[ i
] *= 2.0;
356 double lcl_SplineCalculation::GetInterpolatedValue( double x
)
358 OSL_PRECOND( ( m_aPoints
[ 0 ].first
<= x
) &&
359 ( x
<= m_aPoints
[ m_aPoints
.size() - 1 ].first
),
360 "Trying to extrapolate" );
362 const lcl_tSizeType n
= m_aPoints
.size() - 1;
363 if( x
< m_fLastInterpolatedValue
)
368 // calculate m_nKLow and m_nKHigh
369 // first initialization is done in CTOR
370 while( m_nKHigh
- m_nKLow
> 1 )
372 lcl_tSizeType k
= ( m_nKHigh
+ m_nKLow
) / 2;
373 if( m_aPoints
[ k
].first
> x
)
381 while( ( m_nKHigh
<= n
) &&
382 ( m_aPoints
[ m_nKHigh
].first
< x
) )
387 OSL_ENSURE( m_nKHigh
<= n
, "Out of Bounds" );
389 m_fLastInterpolatedValue
= x
;
391 double h
= m_aPoints
[ m_nKHigh
].first
- m_aPoints
[ m_nKLow
].first
;
392 OSL_ENSURE( h
!= 0, "Bad input to GetInterpolatedValue()" );
394 double a
= ( m_aPoints
[ m_nKHigh
].first
- x
) / h
;
395 double b
= ( x
- m_aPoints
[ m_nKLow
].first
) / h
;
397 return ( a
* m_aPoints
[ m_nKLow
].second
+
398 b
* m_aPoints
[ m_nKHigh
].second
+
399 (( a
*a
*a
- a
) * m_aSecDerivY
[ m_nKLow
] +
400 ( b
*b
*b
- b
) * m_aSecDerivY
[ m_nKHigh
] ) *
404 // helper methods for B-spline
406 // Create parameter t_0 to t_n using the centripetal method with a power of 0.5
407 bool createParameterT(const tPointVecType
& rUniquePoints
, double* t
)
408 { // precondition: no adjacent identical points
409 // postcondition: 0 = t_0 < t_1 < ... < t_n = 1
410 bool bIsSuccessful
= true;
411 const lcl_tSizeType n
= rUniquePoints
.size() - 1;
413 double fDenominator
= 0.0; // initialized for summing up
414 for (lcl_tSizeType i
=1; i
<=n
; ++i
)
415 { // 4th root(dx^2+dy^2)
416 double dx
= rUniquePoints
[i
].first
- rUniquePoints
[i
-1].first
;
417 double dy
= rUniquePoints
[i
].second
- rUniquePoints
[i
-1].second
;
418 if (dx
== 0 && dy
== 0)
420 bIsSuccessful
= false;
425 fDenominator
+= sqrt(std::hypot(dx
, dy
));
428 if (fDenominator
== 0.0)
430 bIsSuccessful
= false;
434 for (lcl_tSizeType j
=1; j
<=n
; ++j
)
436 double fNumerator
= 0.0;
437 for (lcl_tSizeType i
=1; i
<=j
; ++i
)
439 double dx
= rUniquePoints
[i
].first
- rUniquePoints
[i
-1].first
;
440 double dy
= rUniquePoints
[i
].second
- rUniquePoints
[i
-1].second
;
441 fNumerator
+= sqrt(std::hypot(dx
, dy
));
443 t
[j
] = fNumerator
/ fDenominator
;
446 // postcondition check
448 double fPrevious
= 0.0;
449 for (lcl_tSizeType i
=1; i
<= n
&& bIsSuccessful
; ++i
)
451 if (fPrevious
>= t
[i
])
453 bIsSuccessful
= false;
461 return bIsSuccessful
;
464 void createKnotVector(const lcl_tSizeType n
, const sal_uInt32 p
, const double* t
, double* u
)
465 { // precondition: 0 = t_0 < t_1 < ... < t_n = 1
466 for (lcl_tSizeType j
= 0; j
<= p
; ++j
)
470 for (lcl_tSizeType j
= 1; j
<= n
-p
; ++j
)
473 for (lcl_tSizeType i
= j
; i
<= j
+p
-1; ++i
)
480 for (lcl_tSizeType j
= n
+1; j
<= n
+1+p
; ++j
)
486 void applyNtoParameterT(const lcl_tSizeType i
,const double tk
,const sal_uInt32 p
,const double* u
, double* rowN
)
488 // get N_p(t_k) recursively, only N_(i-p) till N_(i) are relevant, all other N_# are zero
490 // initialize with indicator function degree 0
491 rowN
[p
] = 1.0; // all others are zero
493 // calculate up to degree p
494 for (sal_uInt32 s
= 1; s
<= p
; ++s
)
497 double fLeftFactor
= 0.0;
498 double fRightFactor
= ( u
[i
+1] - tk
) / ( u
[i
+1]- u
[i
-s
+1] );
499 // i-s "true index" - (i-p)"shift" = p-s
500 rowN
[p
-s
] = fRightFactor
* rowN
[p
-s
+1];
503 for (sal_uInt32 j
= s
-1; j
>=1 ; --j
)
505 fLeftFactor
= ( tk
- u
[i
-j
] ) / ( u
[i
-j
+s
] - u
[i
-j
] ) ;
506 fRightFactor
= ( u
[i
-j
+s
+1] - tk
) / ( u
[i
-j
+s
+1] - u
[i
-j
+1] );
507 // i-j "true index" - (i-p)"shift" = p-j
508 rowN
[p
-j
] = fLeftFactor
* rowN
[p
-j
] + fRightFactor
* rowN
[p
-j
+1];
512 fLeftFactor
= ( tk
- u
[i
] ) / ( u
[i
+s
] - u
[i
] );
513 // i "true index" - (i-p)"shift" = p
514 rowN
[p
] = fLeftFactor
* rowN
[p
];
518 } // anonymous namespace
520 // Calculates uniform parametric splines with subinterval length 1,
521 // according ODF1.2 part 1, chapter 'chart interpolation'.
522 void SplineCalculater::CalculateCubicSplines(
523 const std::vector
<std::vector
<css::drawing::Position3D
>>& rInput
524 , std::vector
<std::vector
<css::drawing::Position3D
>>& rResult
525 , sal_uInt32 nGranularity
)
527 OSL_PRECOND( nGranularity
> 0, "Granularity is invalid" );
529 sal_uInt32 nOuterCount
= rInput
.size();
531 rResult
.resize(nOuterCount
);
533 auto pSequence
= rResult
.data();
538 for( sal_uInt32 nOuter
= 0; nOuter
< nOuterCount
; ++nOuter
)
540 if( rInput
[nOuter
].size() <= 1 )
541 continue; //we need at least two points
543 sal_uInt32 nMaxIndexPoints
= rInput
[nOuter
].size()-1; // is >=1
544 const css::drawing::Position3D
* pOld
= rInput
[nOuter
].data();
546 std::vector
< double > aParameter(nMaxIndexPoints
+1);
548 for( sal_uInt32 nIndex
=1; nIndex
<=nMaxIndexPoints
; nIndex
++ )
550 aParameter
[nIndex
]=aParameter
[nIndex
-1]+1;
553 // Split the calculation to X, Y and Z coordinate
554 tPointVecType aInputX
;
555 aInputX
.resize(nMaxIndexPoints
+1);
556 tPointVecType aInputY
;
557 aInputY
.resize(nMaxIndexPoints
+1);
558 tPointVecType aInputZ
;
559 aInputZ
.resize(nMaxIndexPoints
+1);
560 for (sal_uInt32 nN
=0;nN
<=nMaxIndexPoints
; nN
++ )
562 aInputX
[ nN
].first
=aParameter
[nN
];
563 aInputX
[ nN
].second
=pOld
[ nN
].PositionX
;
564 aInputY
[ nN
].first
=aParameter
[nN
];
565 aInputY
[ nN
].second
=pOld
[ nN
].PositionY
;
566 aInputZ
[ nN
].first
=aParameter
[nN
];
567 aInputZ
[ nN
].second
=pOld
[ nN
].PositionZ
;
570 // generate a spline for each coordinate. It holds the complete
571 // information to calculate each point of the curve
572 std::optional
<lcl_SplineCalculation
> aSplineX
;
573 std::optional
<lcl_SplineCalculation
> aSplineY
;
574 // lcl_SplineCalculation* aSplineZ; the z-coordinates of all points in
575 // a data series are equal. No spline calculation needed, but copy
576 // coordinate to output
578 if( pOld
[ 0 ].PositionX
== pOld
[nMaxIndexPoints
].PositionX
&&
579 pOld
[ 0 ].PositionY
== pOld
[nMaxIndexPoints
].PositionY
&&
580 pOld
[ 0 ].PositionZ
== pOld
[nMaxIndexPoints
].PositionZ
&&
581 nMaxIndexPoints
>=2 )
583 aSplineX
.emplace(std::move(aInputX
));
584 aSplineY
.emplace(std::move(aInputY
));
586 else // generate the kind "natural spline"
588 double fXDerivation
= std::numeric_limits
<double>::infinity();
589 double fYDerivation
= std::numeric_limits
<double>::infinity();
590 aSplineX
.emplace(std::move(aInputX
), fXDerivation
, fXDerivation
);
591 aSplineY
.emplace(std::move(aInputY
), fYDerivation
, fYDerivation
);
594 // fill result polygon with calculated values
595 pSequence
[nOuter
].resize( nMaxIndexPoints
*nGranularity
+ 1);
597 css::drawing::Position3D
* pNew
= pSequence
[nOuter
].data();
599 sal_uInt32 nNewPointIndex
= 0; // Index in result points
601 for( sal_uInt32 ni
= 0; ni
< nMaxIndexPoints
; ni
++ )
603 // given point is surely a curve point
604 pNew
[nNewPointIndex
].PositionX
= pOld
[ni
].PositionX
;
605 pNew
[nNewPointIndex
].PositionY
= pOld
[ni
].PositionY
;
606 pNew
[nNewPointIndex
].PositionZ
= pOld
[ni
].PositionZ
;
609 // calculate intermediate points
610 double fInc
= ( aParameter
[ ni
+1 ] - aParameter
[ni
] ) / static_cast< double >( nGranularity
);
611 for(sal_uInt32 nj
= 1; nj
< nGranularity
; nj
++)
613 double fParam
= aParameter
[ni
] + ( fInc
* static_cast< double >( nj
) );
615 pNew
[nNewPointIndex
].PositionX
= aSplineX
->GetInterpolatedValue( fParam
);
616 pNew
[nNewPointIndex
].PositionY
= aSplineY
->GetInterpolatedValue( fParam
);
617 // pNewZ[nNewPointIndex]=aSplineZ->GetInterpolatedValue( fParam );
618 pNew
[nNewPointIndex
].PositionZ
= pOld
[ni
].PositionZ
;
623 pNew
[nNewPointIndex
] = pOld
[nMaxIndexPoints
];
627 void SplineCalculater::CalculateBSplines(
628 const std::vector
<std::vector
<css::drawing::Position3D
>>& rInput
629 , std::vector
<std::vector
<css::drawing::Position3D
>>& rResult
630 , sal_uInt32 nResolution
631 , sal_uInt32 nDegree
)
633 // nResolution is ODF1.2 file format attribute chart:spline-resolution and
634 // ODF1.2 spec variable k. Causion, k is used as index in the spec in addition.
635 // nDegree is ODF1.2 file format attribute chart:spline-order and
636 // ODF1.2 spec variable p
637 OSL_ASSERT( nResolution
> 1 );
638 OSL_ASSERT( nDegree
>= 1 );
640 // limit the b-spline degree at 15 to prevent insanely large sets of points
641 sal_uInt32 p
= std::min
<sal_uInt32
>(nDegree
, 15);
643 sal_Int32 nOuterCount
= rInput
.size();
645 rResult
.resize(nOuterCount
);
647 auto pSequence
= rResult
.data();
652 for( sal_Int32 nOuter
= 0; nOuter
< nOuterCount
; ++nOuter
)
654 if( rInput
[nOuter
].size() <= 1 )
655 continue; // need at least 2 points, next piece of the series
657 // Copy input to vector of points and remove adjacent double points. The
658 // Z-coordinate is equal for all points in a series and holds the depth
659 // in 3D mode, simple copying is enough.
660 lcl_tSizeType nMaxIndexPoints
= rInput
[nOuter
].size()-1; // is >=1
661 const css::drawing::Position3D
* pOld
= rInput
[nOuter
].data();
662 double fZCoordinate
= pOld
[0].PositionZ
;
663 tPointVecType aPointsIn
;
664 aPointsIn
.resize(nMaxIndexPoints
+1);
665 for (lcl_tSizeType i
= 0; i
<= nMaxIndexPoints
; ++i
)
667 aPointsIn
[ i
].first
= pOld
[i
].PositionX
;
668 aPointsIn
[ i
].second
= pOld
[i
].PositionY
;
670 aPointsIn
.erase( std::unique( aPointsIn
.begin(), aPointsIn
.end()),
673 // n is the last valid index to the reduced aPointsIn
674 // There are n+1 valid data points.
675 const lcl_tSizeType n
= aPointsIn
.size() - 1;
677 continue; // need at least 2 points, degree p needs at least n+1 points
678 // next piece of series
680 std::vector
<double> t(n
+ 1);
681 if (!createParameterT(aPointsIn
, t
.data()))
683 continue; // next piece of series
686 lcl_tSizeType m
= n
+ p
+ 1;
687 std::vector
<double> u(m
+ 1);
688 createKnotVector(n
, p
, t
.data(), u
.data());
690 // The matrix N contains the B-spline basis functions applied to parameters.
691 // In each row only p+1 adjacent elements are non-zero. The starting
692 // column in a higher row is equal or greater than in the lower row.
693 // To store this matrix the non-zero elements are shifted to column 0
694 // and the amount of shifting is remembered in an array.
695 std::vector
<std::vector
<double>> aMatN(n
+ 1, std::vector
<double>(p
+ 1));
696 std::vector
<lcl_tSizeType
> aShift(n
+ 1);
697 aMatN
[0][0] = 1.0; //all others are zero
701 for (lcl_tSizeType k
= 1; k
<=n
-1; ++k
)
702 { // all basis functions are applied to t_k,
703 // results are elements in row k in matrix N
705 // find the one interval with u_i <= t_k < u_(i+1)
706 // remember u_0 = ... = u_p = 0.0 and u_(m-p) = ... u_m = 1.0 and 0<t_k<1
708 while (u
[i
] > t
[k
] || t
[k
] >= u
[i
+1])
713 // index in reduced matrix aMatN = (index in full matrix N) - (i-p)
716 applyNtoParameterT(i
, t
[k
], p
, u
.data(), aMatN
[k
].data());
719 // Get matrix C of control points from the matrix equation aMatN * C = aPointsIn
720 // aPointsIn is overwritten with C.
721 // Gaussian elimination is possible without pivoting, see reference
722 lcl_tSizeType r
= 0; // true row index
723 lcl_tSizeType c
= 0; // true column index
724 double fDivisor
= 1.0; // used for diagonal element
725 double fEliminate
= 1.0; // used for the element, that will become zero
726 bool bIsSuccessful
= true;
727 for (c
= 0 ; c
<= n
&& bIsSuccessful
; ++c
)
729 // search for first non-zero downwards
731 while ( r
< n
&& aMatN
[r
][c
-aShift
[r
]] == 0 )
735 if (aMatN
[r
][c
-aShift
[r
]] == 0.0)
737 // Matrix N is singular, although this is mathematically impossible
738 bIsSuccessful
= false;
742 // exchange total row r with total row c if necessary
745 std::swap( aMatN
[r
], aMatN
[c
] );
746 std::swap( aPointsIn
[r
], aPointsIn
[c
] );
747 std::swap( aShift
[r
], aShift
[c
] );
750 // divide row c, so that element(c,c) becomes 1
751 fDivisor
= aMatN
[c
][c
-aShift
[c
]]; // not zero, see above
752 for (sal_uInt32 i
= 0; i
<= p
; ++i
)
754 aMatN
[c
][i
] /= fDivisor
;
756 aPointsIn
[c
].first
/= fDivisor
;
757 aPointsIn
[c
].second
/= fDivisor
;
759 // eliminate forward, examine row c+1 to n-1 (worst case)
760 // stop if first non-zero element in row has an higher column as c
761 // look at nShift for that, elements in nShift are equal or increasing
762 for ( r
= c
+1; r
< n
&& aShift
[r
]<=c
; ++r
)
764 fEliminate
= aMatN
[r
][0];
765 if (fEliminate
!= 0.0) // else accidentally zero, nothing to do
767 for (sal_uInt32 i
= 1; i
<= p
; ++i
)
769 aMatN
[r
][i
-1] = aMatN
[r
][i
] - fEliminate
* aMatN
[c
][i
];
772 aPointsIn
[r
].first
-= fEliminate
* aPointsIn
[c
].first
;
773 aPointsIn
[r
].second
-= fEliminate
* aPointsIn
[c
].second
;
778 }// upper triangle form is reached
781 // eliminate backwards, begin with last column
782 for (lcl_tSizeType cc
= n
; cc
>= 1; --cc
)
784 // In row cc the diagonal element(cc,cc) == 1 and all elements left from
785 // diagonal are zero and do not influence other rows.
786 // Full matrix N has semibandwidth < p, therefore element(r,c) is
787 // zero, if abs(r-cc)>=p. abs(r-cc)=cc-r, because r<cc.
789 while ( r
!=0 && cc
-r
< p
)
791 fEliminate
= aMatN
[r
][ cc
- aShift
[r
] ];
792 if ( fEliminate
!= 0.0) // else element is accidentally zero, no action needed
794 // row r -= fEliminate * row cc only relevant for right side
795 aMatN
[r
][cc
- aShift
[r
]] = 0.0;
796 aPointsIn
[r
].first
-= fEliminate
* aPointsIn
[cc
].first
;
797 aPointsIn
[r
].second
-= fEliminate
* aPointsIn
[cc
].second
;
802 // aPointsIn contains the control points now.
804 // calculate the intermediate points according given resolution
805 // using deBoor-Cox algorithm
806 lcl_tSizeType nNewSize
= nResolution
* n
+ 1;
807 pSequence
[nOuter
].resize(nNewSize
);
808 css::drawing::Position3D
* pNew
= pSequence
[nOuter
].data();
809 pNew
[0].PositionX
= aPointsIn
[0].first
;
810 pNew
[0].PositionY
= aPointsIn
[0].second
;
811 pNew
[0].PositionZ
= fZCoordinate
; // Precondition: z-coordinates of all points of a series are equal
812 pNew
[nNewSize
-1 ].PositionX
= aPointsIn
[n
].first
;
813 pNew
[nNewSize
-1 ].PositionY
= aPointsIn
[n
].second
;
814 pNew
[nNewSize
-1 ].PositionZ
= fZCoordinate
;
815 std::vector
<double> aP(m
+ 1);
816 lcl_tSizeType nLow
= 0;
817 for ( lcl_tSizeType nTIndex
= 0; nTIndex
<= n
-1; ++nTIndex
)
819 for (sal_uInt32 nResolutionStep
= 1;
820 nResolutionStep
<= nResolution
&& ( nTIndex
!= n
-1 || nResolutionStep
!= nResolution
);
823 lcl_tSizeType nNewIndex
= nTIndex
* nResolution
+ nResolutionStep
;
824 double ux
= t
[nTIndex
] + nResolutionStep
* ( t
[nTIndex
+1] - t
[nTIndex
]) /nResolution
;
826 // get index nLow, so that u[nLow]<= ux < u[nLow +1]
827 // continue from previous nLow
828 while ( u
[nLow
] <= ux
)
835 for (lcl_tSizeType i
= nLow
-p
; i
<= nLow
; ++i
)
837 aP
[i
] = aPointsIn
[i
].first
;
839 for (sal_uInt32 lcl_Degree
= 1; lcl_Degree
<= p
; ++lcl_Degree
)
841 for (lcl_tSizeType i
= nLow
; i
>= nLow
+ lcl_Degree
- p
; --i
)
843 double fFactor
= ( ux
- u
[i
] ) / ( u
[i
+p
+1-lcl_Degree
] - u
[i
]);
844 aP
[i
] = (1 - fFactor
)* aP
[i
-1] + fFactor
* aP
[i
];
847 pNew
[nNewIndex
].PositionX
= aP
[nLow
];
850 for (lcl_tSizeType i
= nLow
- p
; i
<= nLow
; ++i
)
852 aP
[i
] = aPointsIn
[i
].second
;
854 for (sal_uInt32 lcl_Degree
= 1; lcl_Degree
<= p
; ++lcl_Degree
)
856 for (lcl_tSizeType i
= nLow
; i
>= nLow
+lcl_Degree
- p
; --i
)
858 double fFactor
= ( ux
- u
[i
] ) / ( u
[i
+p
+1-lcl_Degree
] - u
[i
]);
859 aP
[i
] = (1 - fFactor
)* aP
[i
-1] + fFactor
* aP
[i
];
862 pNew
[nNewIndex
].PositionY
= aP
[nLow
];
863 pNew
[nNewIndex
].PositionZ
= fZCoordinate
;
867 } // next piece of the series
872 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */