1 /** @file gsBSpline.h
2 
3     @brief Represents a B-spline curve/function with one parameter
4 
5     This file is part of the G+Smo library.
6 
7     This Source Code Form is subject to the terms of the Mozilla Public
8     License, v. 2.0. If a copy of the MPL was not distributed with this
9     file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 
11     Author(s): A. Mantzaflaris
12 */
13 
14 #pragma once
15 
16 #include <gsCore/gsLinearAlgebra.h>
17 #include <gsCore/gsGeometry.h>
18 
19 #include <gsNurbs/gsBSplineBasis.h>
20 #include <gsNurbs/gsBoehm.h>
21 #include <gsNurbs/gsBSplineSolver.h>
22 
23 #include <gsUtils/gsPointGrid.h>
24 
25 
26 namespace gismo
27 {
28 
29 /** \brief
30     A B-spline function of one argument, with arbitrary target dimension.
31 
32     This is the geometry type associated with gsBSplineBasis.
33 
34     \tparam T coefficient type
35 
36     \ingroup geometry
37     \ingroup Nurbs
38 */
39 
40 
41 template<class T>
42 class gsBSpline : public gsGeoTraits<1,T>::GeometryBase
43 {
44 public:
45     typedef gsKnotVector<T> KnotVectorType;
46 
47     typedef typename gsGeoTraits<1,T>::GeometryBase Base;
48 
49     typedef gsBSplineBasis<T> Basis;
50 
51     /// Shared pointer for gsBSpline
52     typedef memory::shared_ptr< gsBSpline > Ptr;
53 
54     /// Unique pointer for gsBSpline
55     typedef memory::unique_ptr< gsBSpline > uPtr;
56 
57 public:
58 
59     /// Default empty constructor.
gsBSpline()60     gsBSpline() { }
61 
62     // enable swap
63     using gsGeometry<T>::swap;
64 
65     /// Construct B-Spline by basis and coefficient matrix.
gsBSpline(const Basis & basis,gsMatrix<T> coefs)66     gsBSpline( const Basis & basis, gsMatrix<T> coefs )
67     : Base( basis, give(coefs) )
68     {
69         if( this->basis().isPeriodic() )
70             this->basis().expandCoefs(m_coefs);
71     }
72 
73     /// Construct B-Spline by a knot vector and coefficient matrix.
74     gsBSpline(KnotVectorType KV, gsMatrix<T> coefs, bool periodic = false )
75     {
76         this->m_basis = new Basis(give(KV));
77         m_coefs.swap(coefs);
78 
79         if( periodic )
80         {
81             const index_t sz = this->basis().size();
82 
83             this->basis().setPeriodic();
84 
85             if ( m_coefs.rows() == sz )
86             {
87                 this->basis().trimCoefs(m_coefs);
88             }
89             else if  ( m_coefs.rows() == this->basis().size() )
90             {
91                 this->basis().expandCoefs(m_coefs);
92             }
93             else
94             {
95                 GISMO_ERROR("Wrong number of coefficients for periodic basis.");
96             }
97         }
98         else // non-periodic
99         {
100             if( this->m_coefs.rows() + KV.degree() + 1 != static_cast<int>( KV.size() ) )
101                 gsWarn << "gsBSpline Warning: #Knots="<< KV.size()<< ", #coefs="<< this->m_coefs.rows() <<"\n";
102         }
103     }
104 
105 
106     /// @brief Construct a B-spline from an interval and knot vector specification.
107     /// \param u0 starting parameter
108     /// \param u1 end parameter
109     /// \param interior number of interior knots
110     /// \param degree degree of the spline space
111     /// \param coefs coefficients of the spline space
112     /// \param mult_interior multiplicity at the interior knots
113     /// \param periodic specifies whether the B-spline is periodic
114     ///
115     /// \ingroup Nurbs
116     gsBSpline(T u0, T u1, unsigned interior, int degree,
117               gsMatrix<T> coefs, unsigned mult_interior=1, bool periodic = false)
118     {
119         this->m_basis = new Basis(u0, u1, interior, degree, mult_interior, periodic );
120         if( periodic )
121         {
122             GISMO_ASSERT( this->basis().numCrossingFunctions() == 1,
123                           "Knot vector with clamped knots should lead to m_periodic == 1.");
124 
125             this->m_coefs = this->basis().perCoefs(coefs);
126             GISMO_ASSERT( this->m_coefs.rows() >= this->basis().size(),
127                           "You should give at least as many coefficients as the size of the basis after converting to periodic.");
128             if( this->m_coefs.rows() > this->basis().size() )
129                 gsWarn << "Some of the last coefficients are going to be lost during conversion to periodic basis.\n";
130         }
131         else // non-periodic
132         {
133             this->m_coefs.swap(coefs);
134             GISMO_ASSERT( this->m_coefs.rows() == this->basis().size(),
135                           "Number of coefficients does not match the size of the basis.");
136         }
137     }
138 
GISMO_CLONE_FUNCTION(gsBSpline)139     GISMO_CLONE_FUNCTION(gsBSpline)
140 
141     GISMO_BASIS_ACCESSORS
142 
143 public:
144 
145     /// Prints the object as a string.
146     std::ostream &print(std::ostream &os) const
147     {
148         os << "BSpline curve "<< "of degree "<< this->basis().degree()<< ", "<<  this->basis().knots() <<".\n";
149         os << "with control points "<< this->m_coefs.row(0)<<" ... "<<this->m_coefs.bottomRows(1) << ".\n";
150         if( this->basis().isPeriodic() )
151             os << "Periodic with overlay " << this->basis().numCrossingFunctions() << ".\n";
152         return os;
153     }
154 
155     /// Returns the starting value of the domain of the basis
domainStart()156     T domainStart() const { return this->basis().knots().first(); }
157 
158     /// Returns the end value of the domain of the basis
domainEnd()159     T domainEnd() const { return this->basis().knots().last(); }
160 
161     /// Returns a reference to the knot vector
162     KnotVectorType & knots(const int i = 0)
163     {
164         GISMO_UNUSED(i);
165         GISMO_ASSERT( i==0, "Requested knots of invalid direction "<< i );
166         return this->basis().knots();
167     }
168 
169     /// Returns a (const )reference to the knot vector
170     const KnotVectorType & knots(const int i = 0) const
171     {
172         GISMO_UNUSED(i);
173         GISMO_ASSERT( i==0, "Requested knots of invalid direction "<< i );
174         return this->basis().knots();
175     }
176 
177     /// Returns the degree of the B-spline
178     short_t degree(short_t i = 0) const
179     {
180         GISMO_UNUSED(i);
181         GISMO_ASSERT( i==0, "Requested knots of invalid direction "<< i );
182         return this->basis().degree();
183     }
184 
185     // compatible curves: same degree, same first/last p+1 knots
isCompatible(gsGeometry<T> *)186     void isCompatible( gsGeometry<T> * )
187     { GISMO_NO_IMPLEMENTATION }
188 
189     // compatible curves: same degree, same first/last p+1 knots
makeCompatible(gsGeometry<T> *)190     void makeCompatible( gsGeometry<T> * )
191     { GISMO_NO_IMPLEMENTATION }
192 
193 
194     /// Merge other B-spline into this one.
195     void merge( gsGeometry<T> * otherG );
196 
197     /// Insert the given new knot (multiplicity \a i) without changing the curve.
198     void insertKnot( T knot, int i = 1)
199     {
200         if (i==0) return;
201         //if ( i==1)
202         //single knot insertion: Boehm's algorithm
203         //else
204         //knot with multiplicity:   Oslo algorithm
205         if( this->basis().isPeriodic() )
206         {
207             int borderKnotMult = this->basis().borderKnotMult();
208             KnotVectorType & knots = this->knots();
209             unsigned deg = this->basis().degree();
210 
211 
212             GISMO_ASSERT( knot != knots[deg] && knot != knots[knots.size() - deg - 1],
213                           "You are trying to increase the multiplicity of the p+1st knot but the code is not ready for that.\n");
214 
215             // If we would be inserting to "passive" regions, we rather insert the knot into the mirrored part.
216             // Adjustment of the mirrored knots is then desirable.
217             if( knot < knots[deg - borderKnotMult + 1] )
218             {
219                 knot += this->basis()._activeLength();
220             }
221             else if( knot > knots[knots.size() - deg + borderKnotMult - 2] )
222             {
223                 knot -= this->basis()._activeLength();
224             }
225             // If necessary, we update the mirrored part of the knot vector.
226             if((knot < knots[2*deg + 1 - borderKnotMult]) || (knot >= knots[knots.size() - 2*deg - 2 + borderKnotMult]))
227                 this->basis().enforceOuterKnotsPeriodic();
228 
229             // We copy some of the control points to pretend for a while that the basis is not periodic.
230             //gsMatrix<T> trueCoefs = this->basis().perCoefs( this->coefs() );
231             gsBoehm( this->basis().knots(), this->coefs(), knot, i );
232             //this->coefs() = trueCoefs;
233             //this->coefs().conservativeResize( this->basis().size(), this->coefs().cols() );
234         }
235         else // non-periodic
236             gsBoehm( this->basis().knots(), this->coefs() , knot, i);
237     }
238 
239     /// Insert the given new knots in the range \a [\em inBegin .. \em inEend )
240     /// without changing the curve.
241     /// \param inBegin iterator pointing to the first knot to be inserted
242     /// \param inEnd iterator pointing to one position after the last
243     /// knot to be inserted
244     template <class It>
insertKnots(It inBegin,It inEnd)245     void insertKnots( It inBegin, It inEnd)
246     {
247         if( this->basis().isPeriodic() )
248         {
249             // We assume we got valid (i.e., non-NULL) iterators; I don't think we have a reasonable way to test it in GISMO_ASSERT.
250 
251             GISMO_ASSERT( (*inBegin > *(this->knots().begin()))
252                           && (*(inEnd-1) < *(this->knots().end()-1)),
253                           "Please, ask me to insert knots inside the knot interval only." );
254 
255             std::vector<T> newKnots(inBegin,inEnd);
256 
257             // It can happen that user would ask us to insert knots outside the active range.
258             // Should it happen, we shift the values and then sort the knot vector to remain non-decreasing.
259 
260             T activeLength = this->basis()._activeLength();
261             T blue1 = *(this->basis().knots().begin()  + this->degree() );
262             T blue2 = *(this->basis().knots().end()    - this->degree() );
263             typename std::vector<T>::iterator begin = newKnots.begin();
264             typename std::vector<T>::iterator end   = newKnots.end();
265             for( typename std::vector<T>::iterator it = begin; it != end; ++it )
266             {
267                 if( *it < blue1 )
268                     *it += activeLength;
269                 else if( *it > blue2 )
270                     *it -= activeLength;
271                 else if( (*it == blue1) || (*it == blue2) )
272                 {
273                     // I would like to erase it and go on but for that the iterator is not enough.
274                     // ANGELOS, wouldn't it be better to put a GISMO_ASSERT here instead? Or throw an exception?
275                     gsWarn << "Interrupting insertKnots(): Cannot insert value equal to the p+1st knot from the beginning or end into a periodic curve.\n";
276                     return;
277                 }
278             }
279             /* // This way it would be nicer but it does not cover all the dubious cases.
280             for( It it = begin; *it < blue1; ++it )
281                 *it += activeLength;
282             for( It it = end-1; *it > blue2; --it )
283                 *it -= activeLength;*/
284 
285             std::sort( begin, end );
286 
287             // We copy some of the control points to pretend for a while that the basis is not periodic.
288             //gsMatrix<T> trueCoefs = this->basis().perCoefs( this->coefs() );
289             gsBoehmRefine( this->basis().knots(), this->coefs(), this->degree(), begin, end );
290             //this->basis().enforceOuterKnotsPeriodic();
291             //this->coefs() = trueCoefs; // Isn't this memory-leaking?
292             // Periodic basis is restored using the so-called Student's Algorithm: forget as much as possible =).
293             //this->coefs().conservativeResize( this->basis().size(), this->coefs().cols() );
294         }
295         else // non-periodic
296             gsBoehmRefine( this->basis().knots(), this->coefs(), this->degree(), inBegin, inEnd);
297     }
298 
299     // Look at gsGeometry class for a description
300     void degreeElevate(short_t const i = 1, short_t const dir = -1);
301 
302     /// @brief Returns true iff the point p is contained (approximately) on
303     /// the curve, with the given tolerance.
304     // Under Construction..( TO DO )
305     bool contains( gsMatrix<T> const & p, T const & tol = 1e-6 )
306         {
307             assert( p.cols()==1 );
308             gsBSplineSolver<T> slv;
309             std::vector<T> roots;
310             short_t dim = this->geoDim();
311             gsMatrix<T> ev, tmp(1,1);
312             int i(1);
313 
314             slv.allRoots( *this, roots, 0, p(0,0), 1e-6, 100);
315             for ( typename std::vector<T>::const_iterator it=roots.begin();
316                   it != roots.end(); ++it)
317             {
318                 tmp(0,0)= *it;
319                 this->eval_into(tmp,ev);
320                 for (i=1; i!=dim; ++i )
321                     if ( math::abs( ev(i,0) - p(i,0) ) > tol )
322                         break;
323                 if (i==dim)
324                     return true;
325             }
326             return false;
327         }
328 
329     /// Sample \a npoints uniformly distributed (in parameter domain) points on the curve.
330     gsMatrix<T> sample(int npoints = 50) const
331     {
332         gsMatrix<T> images;
333         gsMatrix<T> interval = this->parameterRange();
334         const gsMatrix<T> pts = gsPointGrid(interval, npoints );
335         this->eval_into( pts, images );
336         return images;
337     }
338 
339     /// Tries to convert the curve into periodic.
340     void setPeriodic(bool flag = true)
341     {
342         this->basis().setPeriodic(flag);
343         this->m_coefs = this->basis().perCoefs( this->m_coefs );
344     }
345 
numCoefs()346     inline int numCoefs() const { return this->m_coefs.rows() - this->basis().numCrossingFunctions(); }
347 
isPeriodic()348     inline bool isPeriodic() const { return this->basis().isPeriodic(); }
349 
350 
351     /// Returns true if this curve is closed
352     bool isClosed(T tol = 1e-10) const
353     {
354         return ( this->basis().isPeriodic() ||
355                  (m_coefs.row(0) - m_coefs.row(m_coefs.rows()-1)).squaredNorm()<tol );
356     }
357 
358     /// \brief Return true if point \a u is on the curve with
359     /// tolerance \a tol
360     bool isOn(gsMatrix<T> const &u, T tol = 1e-3) const;
361 
362     /// \brief Return true if point \a u is an endpoint (corner) of
363     /// the curve with tolerance \a tol
364     bool isPatchCorner(gsMatrix<T> const &v, T tol = 1e-3) const;
365 
366     /// \brief returns the tensor-index \a curr of the corner control
367     /// point \a v, or an invalid index if the corner is not found
368     /// within the tolerance \a tol
369     void findCorner(const gsMatrix<T>   & v,
370                     gsVector<index_t,1> & curr,
371                     T tol = 1e-3);
372 
373     /// \brief Modifies the parameterization such that the point \a v
374     /// is the starting point of the curve. Assumes that \a v is
375     /// either the starting or the ending point of the curve
376     void setOriginCorner(gsMatrix<T> const &v);
377 
378     /// \brief Modifies the parameterization such that the point \a v
379     /// is the ending point of the curve. Assumes that \a v is
380     /// either the starting or the ending point of the curve
381     void setFurthestCorner(gsMatrix<T> const &v);
382 
383     void swapDirections(const unsigned i, const unsigned j);
384 
385 protected:
386 
387     using Base::m_coefs;
388 
389     // TODO Check function
390     // check function: check the coefficient number, degree, knot vector ...
391 
392 }; // class gsBSpline
393 
394 
395 /*
396 // Product of spline functions
397 template<class T>
398 gsBSpline<T> operator*(const gsBSpline<T> & lhs, const gsBSpline<T> & rhs)
399 {
400     // Note: quick-fix implementation. Better algorithms  exist
401     gsKnotVector<T> kv = lhs.knots().knotUnion( lhs.knots() );
402     kv.degreeElevate( lhs.degree() + rhs.degree() - kv.degree() );
403 
404     gsMatrix<T> pts;
405     kv.greville_into(pts);
406     const gsMatrix<T> ev = (lhs.eval(pts)).array() * (rhs.eval(pts)).array();
407 
408     // fixme: avoid temporaries here
409     return *(static_cast<gsBSpline<T>*>(gsBSplineBasis<T>(kv).interpolateData(ev,pts)));
410 }
411 */
412 
413 } // namespace gismo
414 
415 
416 #ifndef GISMO_BUILD_LIB
417 #include GISMO_HPP_HEADER(gsBSpline.hpp)
418 #endif
419