cgv
Loading...
Searching...
No Matches
interpolate.h
1#pragma once
2
3#include <functional>
4#include <memory>
5#include <vector>
6
7#include "interval.h"
8#include "compare_float.h"
9
10namespace cgv {
11namespace math {
12
13namespace detail {
14
21template<typename ParamT = float>
22ParamT map_to_domain(ParamT t, const interval<ParamT>& domain = { 0.0f, 1.0f }) {
23 ParamT domain_size = domain.size();
24 if(!is_zero(domain_size))
25 return (t - domain.lower_bound) / domain_size;
26 return domain.lower_bound;
27}
28
45template<typename PointT, typename InterpolationOp, typename ParamT = float>
46PointT interpolate_piece(const std::vector<PointT>& points, ParamT t, InterpolationOp operation) {
47 if(t <= ParamT(0))
48 return points.front();
49
50 if(t >= ParamT(1))
51 return points.back();
52
53 size_t num_pairs = points.size() - 1;
54 size_t index = static_cast<size_t>(t * static_cast<ParamT>(num_pairs));
55 ParamT step = ParamT(1) / static_cast<ParamT>(num_pairs);
56 ParamT prev = static_cast<ParamT>(index) * step;
57 ParamT curr = static_cast<ParamT>(index + 1) * step;
58 t = (t - prev) / (curr - prev);
59 return operation(points[index], points[index + 1], t);
60}
61
80template<typename PointT, typename InterpolationOp, typename ParamT = float>
81PointT interpolate_piece(const std::vector<std::pair<ParamT, PointT>>& points, ParamT t, InterpolationOp operation) {
82 using pair_type = std::pair<ParamT, PointT>;
83
84 if(t <= points.front().first)
85 return points.front().second;
86
87 auto it = std::lower_bound(points.begin(), points.end(), t, [](const pair_type& point, float value) { return point.first < value; });
88 if(it == points.end())
89 return points.back().second;
90
91 // "it" points to the first point whose parameter position is larger than t
92 const pair_type& left = *(it == points.begin() ? it : it - 1);
93 const pair_type& right = *it;
94 t = (t - left.first) / (right.first - left.first);
95 return operation(left.second, right.second, t);
96}
97
113template<typename PointT, typename InterpolationOp, typename ParamT = float>
114std::vector<PointT> interpolate_n(const std::vector<std::pair<ParamT, PointT>>& points, InterpolationOp operation, size_t n) {
115 using pair_type = std::pair<ParamT, PointT>;
116
117 if(n == 0 || points.size() == 0)
118 return {};
119
120 if(points.size() == 1)
121 return std::vector<PointT>(n, points.front().second);
122
123 std::vector<PointT> res;
124 res.reserve(n);
125
126 size_t l = 0;
127 size_t r = 1;
128 ParamT size = points.back().first - points.front().first;
129 ParamT step = size / static_cast<ParamT>(n - 1);
130 for(size_t i = 0; i < n; ++i) {
131 ParamT x = points.front().first + step * static_cast<ParamT>(i);
132
133 while(x > points[r].first && l < points.size() - 1) {
134 l = r;
135 r = std::min(l + 1, points.size() - 1);
136 }
137
138 if(l == r) {
139 res.push_back(points[r].second);
140 } else {
141 const pair_type& p0 = points[l];
142 const pair_type& p1 = points[r];
143 ParamT t = (x - p0.first) / (p1.first - p0.first);
144 res.push_back(operation(p0.second, p1.second, t));
145 }
146 }
147 return res;
148}
149
150} // namespace detail
151
162template<typename ParamT = float, typename OutputIt, typename UnaryOp>
163static void sequence_transform(OutputIt output_first, UnaryOp operation, size_t n, ParamT a = ParamT(0), ParamT b = ParamT(1)) {
164 if(n == 1) {
165 *output_first = operation(ParamT(0.5) * (a + b));
166 ++output_first;
167 } else if(n > 1) {
168 ParamT size = b - a;
169 ParamT step = size / static_cast<ParamT>(n - 1);
170 for(size_t i = 0; i < n; ++i) {
171 ParamT t = a + step * static_cast<ParamT>(i);
172 *output_first = operation(t);
173 ++output_first;
174 }
175 }
176}
177
186template<typename PointT, typename ParamT = float>
187PointT interpolate_nearest(const PointT& p0, const PointT& p1, ParamT t) {
188 return t < ParamT(0.5) ? p0 : p1;
189};
190
199template<typename PointT, typename ParamT = float>
200PointT interpolate_nearest(const std::vector<PointT>& points, ParamT t, const interval<ParamT>& domain = { 0.0f, 1.0f }) {
201 return detail::interpolate_piece(points, t, static_cast<PointT(*)(const PointT&, const PointT&, ParamT)>(&interpolate_nearest<PointT, ParamT>));
202};
203
215template<typename PointT, typename ParamT = float>
216PointT interpolate_nearest(const std::vector<std::pair<ParamT, PointT>>& points, ParamT t) {
217 return detail::interpolate_piece(points, t, static_cast<PointT(*)(const PointT&, const PointT&, ParamT)>(&interpolate_nearest<PointT, ParamT>));
218}
219
227template<typename PointT, typename ParamT = float>
228std::vector<PointT> interpolate_nearest_n(const std::vector<PointT>& points, size_t n) {
229 std::vector<PointT> res;
230 res.reserve(n);
231 sequence_transform(std::back_inserter(res), [&points](float t) { return interpolate_nearest(points, t); }, n);
232 return res;
233};
234
245template<typename PointT, typename ParamT = float>
246std::vector<PointT> interpolate_nearest_n(const std::vector<std::pair<ParamT, PointT>>& points, size_t n) {
247 return detail::interpolate_n(points, static_cast<PointT(*)(const PointT&, const PointT&, ParamT)>(&interpolate_nearest<PointT, ParamT>), n);
248}
249
258template<typename PointT, typename ParamT = float>
259static PointT interpolate_linear(const PointT& p0, const PointT& p1, ParamT t) {
260 PointT v0 = (ParamT(1) - t) * p0;
261 PointT v1 = t * p1;
262 return v0 + v1;
263}
264
273template<typename PointT, typename ParamT = float>
274PointT interpolate_linear(const std::vector<PointT>& points, ParamT t, const interval<ParamT>& domain = { 0.0f, 1.0f }) {
275 t = detail::map_to_domain(t, domain);
276 return detail::interpolate_piece(points, t, static_cast<PointT(*)(const PointT&, const PointT&, ParamT)>(&interpolate_linear<PointT, ParamT>));
277};
278
290template<typename PointT, typename ParamT = float>
291PointT interpolate_linear(const std::vector<std::pair<ParamT, PointT>>& points, ParamT t) {
292 return detail::interpolate_piece(points, t, static_cast<PointT(*)(const PointT&, const PointT&, ParamT)>(&interpolate_linear<PointT, ParamT>));
293}
294
302template<typename PointT, typename ParamT = float>
303std::vector<PointT> interpolate_linear_n(const std::vector<PointT>& points, size_t n) {
304 std::vector<PointT> res;
305 res.reserve(n);
306 sequence_transform(std::back_inserter(res), [&points](float t) { return interpolate_linear(points, t); }, n);
307 return res;
308};
309
320template<typename PointT, typename ParamT = float>
321std::vector<PointT> interpolate_linear_n(const std::vector<std::pair<ParamT, PointT>>& points, size_t n) {
322 return detail::interpolate_n(points, static_cast<PointT(*)(const PointT&, const PointT&, ParamT)>(&interpolate_linear<PointT, ParamT>), n);
323}
324
334template<typename PointT, typename ParamT = float>
335static PointT interpolate_quadratic_bezier(const PointT& p0, const PointT& p1, const PointT& p2, ParamT t) {
336 ParamT mt = ParamT(1) - t;
337 PointT v0 = mt * mt * p0;
338 PointT v1 = ParamT(2) * mt * t * p1;
339 PointT v2 = t * t * p2;
340 return v0 + v1 + v2;
341}
342
353template<typename PointT, typename ParamT = float>
354static PointT interpolate_cubic_bezier(const PointT& p0, const PointT& p1, const PointT& p2, const PointT& p3, ParamT t) {
355 ParamT mt = ParamT(1) - t;
356 PointT v0 = mt * mt * mt * p0;
357 PointT v1 = ParamT(3) * mt * mt * t * p1;
358 PointT v2 = ParamT(3) * mt * t * t * p2;
359 PointT v3 = t * t * t * p3;
360 return v0 + v1 + v2 + v3;
361}
362
373template<typename PointT, typename ParamT = float>
374static PointT interpolate_cubic_hermite(const PointT& p0, const PointT& m0, const PointT& p1, const PointT& m1, ParamT t) {
375 ParamT t2 = t * t;
376 ParamT t3 = t * t * t;
377 PointT w0 = ParamT(2) * t3 - ParamT(3) * t2 + ParamT(1);
378 PointT w1 = t3 - ParamT(2) * t2 + t;
379 PointT w2 = ParamT(-2) * t3 + ParamT(3) * t2;
380 PointT w3 = t3 - t2;
381 return w0 * p0 + w1 * m0 + w2 * p1 + w3 * m1;
382}
383
394template<typename PointT, typename ParamT = float>
395static PointT interpolate_cubic_basis(const PointT& b0, const PointT& b1, const PointT& b2, const PointT& b3, ParamT t) {
396 ParamT t2 = t * t;
397 ParamT t3 = t * t * t;
398 PointT v0 = (ParamT(1) - ParamT(3) * t + ParamT(3) * t2 - t3) * b0;
399 PointT v1 = (ParamT(4) - ParamT(6) * t2 + ParamT(3) * t3) * b1;
400 PointT v2 = (ParamT(1) + ParamT(3) * t + ParamT(3) * t2 - ParamT(3) * t3) * b2;
401 PointT v3 = t3 * b3;
402 return (ParamT(1) / ParamT(6)) * (v0 + v1 + v2 + v3);
403}
404
417template<typename PointT, typename ParamT = float>
418PointT interpolate_smooth_cubic(const std::vector<PointT>& points, ParamT t, const interval<ParamT>& domain = { 0.0f, 1.0f }) {
419 t = detail::map_to_domain(t, domain);
420
421 size_t num_pairs = points.size() - 1;
422
423 size_t i = 0;
424 if(t <= ParamT(0)) {
425 t = ParamT(0);
426 } else if(t >= ParamT(1)) {
427 t = ParamT(1);
428 i = num_pairs - 1;
429 } else {
430 i = static_cast<size_t>(t * static_cast<float>(num_pairs));
431 }
432
433 PointT v1 = points[i];
434 PointT v2 = points[i + 1];
435 PointT v0 = i > 0 ? points[i - 1] : PointT(2) * v1 - v2;
436 PointT v3 = i < num_pairs - 1 ? points[i + 2] : PointT(2) * v2 - v1;
437 t = (t - static_cast<float>(i) / static_cast<float>(num_pairs)) * static_cast<float>(num_pairs);
438 return interpolate_cubic_basis(v0, v1, v2, v3, t);
439};
440
454template<typename PointT, typename ParamT = float>
455PointT interpolate_smooth_cubic(const std::vector<std::pair<ParamT, PointT>>& points, ParamT t) {
456 using pair_type = std::pair<ParamT, PointT>;
457
458 size_t num_pairs = points.size() - 1;
459
460 if(t <= ParamT(0))
461 return points.front().second;
462
463 auto it = std::lower_bound(points.begin(), points.end(), t, [](const pair_type& point, float value) { return point.first < value; });
464 if(it == points.end())
465 return points.back().second;
466
467 if(it != points.begin())
468 --it;
469
470 size_t i = std::distance(points.begin(), it);
471
472 pair_type v1 = points[i];
473 pair_type v2 = points[i + 1];
474 PointT v0 = i > 0 ? points[i - 1].second : PointT(2) * v1.second - v2.second;
475 PointT v3 = i < num_pairs - 1 ? points[i + 2].second : PointT(2) * v2.second - v1.second;
476 t = (t - v1.first) / (v2.first - v1.first);
477 return interpolate_cubic_basis(v0, v1.second, v2.second, v3, t);
478}
479
489template<typename PointT, typename ParamT = float>
490std::vector<PointT> interpolate_smooth_cubic_n(const std::vector<PointT>& points, size_t n) {
491 std::vector<PointT> res;
492 res.reserve(n);
493 sequence_transform(std::back_inserter(res), [&points](float t) { return interpolate_smooth_cubic(points, t); }, n);
494 return res;
495}
496
506template<typename PointT, typename ParamT = float>
507std::vector<PointT> interpolate_smooth_cubic_n(const std::vector<std::pair<ParamT, PointT>>& points, size_t n) {
508 std::vector<PointT> res;
509 res.reserve(n);
510 sequence_transform(std::back_inserter(res), [&points](float t) { return interpolate_smooth_cubic(points, t); }, n);
511 return res;
512}
513
515template<typename X, typename Y>
518 interval<X> domain = { X(0), X(1) };
520 std::vector<Y> breakpoints;
521
523 Y evaluate(X x) const {
524 x = detail::map_to_domain(x, domain);
525
526 if(x <= X(0))
527 return breakpoints.front();
528
529 if(x >= X(1))
530 return breakpoints.back();
531
532 size_t num_pieces = breakpoints.size() - 1;
533 size_t index = static_cast<size_t>(x * static_cast<X>(num_pieces));
534
535 X step = X(1) / static_cast<X>(num_pieces);
536 X prev = static_cast<X>(index) * step;
537 X curr = static_cast<X>(index + 1) * step;
538 X t = (x - prev) / (curr - prev);
539 return interpolate_linear(breakpoints[index], breakpoints[index + 1], t);
540 }
541};
542
544template<typename ValueT, typename ParamT = float>
546public:
547 virtual ~interpolator() = default;
548
549 virtual std::unique_ptr<interpolator> clone() const = 0;
550
554 virtual ValueT at(ParamT t) const = 0;
555
563 virtual std::vector<ValueT> quantize(size_t n) const {
564 std::vector<ValueT> samples;
565 samples.reserve(n);
566 sequence_transform<ParamT>(std::back_inserter(samples), [this](ParamT t) { return at(t); }, n);
567 return samples;
568 }
569};
570
571template<typename PointT, typename ValueT, typename ParamT = float>
572class piecewise_interpolator_storage : public interpolator<ValueT, ParamT> {
573public:
574 using point_type = PointT;
575
576 piecewise_interpolator_storage() {}
577
578 piecewise_interpolator_storage(std::initializer_list<point_type> points) : points(points) {}
579
580 piecewise_interpolator_storage(const std::vector<point_type>& points) : points(points) {}
581
582 template<class IteratorT>
583 piecewise_interpolator_storage(IteratorT first, IteratorT last) {
584 points.assign(first, last);
585 }
586
587 std::vector<point_type> points;
588};
589
590template<typename ValueT, typename ParamT = float>
591class identity_interpolator : public interpolator<ValueT, ParamT> {
592public:
593 identity_interpolator() {}
594
595 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
596 return std::unique_ptr<interpolator<ValueT, ParamT>>(new identity_interpolator(*this));
597 }
598
599 ValueT at(ParamT t) const override {
600 return ValueT(t);
601 }
602};
603
604template<typename ValueT, typename ParamT = float>
605class uniform_piecewise_interpolator : public piecewise_interpolator_storage<ValueT, ValueT, ParamT> {
606 using base = piecewise_interpolator_storage<ValueT, ValueT, ParamT>;
607
608public:
609 using base::base;
610};
611
612template<typename ValueT, typename ParamT = float>
613class piecewise_interpolator : public piecewise_interpolator_storage<std::pair<ParamT, ValueT>, ValueT, ParamT> {
614 using base = piecewise_interpolator_storage<std::pair<ParamT, ValueT>, ValueT, ParamT>;
615
616public:
617 using base::base;
618};
619
620template<typename ValueT, typename ParamT = float>
623
624public:
625 using base::base;
626
627 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
628 return std::unique_ptr<interpolator<ValueT, ParamT>>(new discrete_interpolator(*this));
629 }
630
631 ValueT at(ParamT t) const override {
632 size_t num_points = this->points.size();
633
634 if(t <= ParamT(0))
635 return this->points.front();
636 else if(t >= ParamT(1))
637 return this->points.back();
638 else
639 return this->points[std::min(static_cast<size_t>(t * static_cast<ParamT>(num_points)), num_points - 1)];
640 }
641};
642
643template<typename ValueT, typename ParamT = float>
646
647public:
648 using base::base;
649
650 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
651 return std::unique_ptr<interpolator<ValueT, ParamT>>(new uniform_linear_interpolator(*this));
652 }
653
654 ValueT at(ParamT t) const override {
655 return interpolate_linear(this->points, t);
656 }
657
658 std::vector<ValueT> quantize(size_t n) const override {
659 return interpolate_linear_n(this->points, n);
660 }
661};
662
663template<typename ValueT, typename ParamT = float>
664class linear_interpolator : public piecewise_interpolator<ValueT, ParamT> {
666
667public:
668 using base::base;
669
670 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
671 return std::unique_ptr<interpolator<ValueT, ParamT>>(new linear_interpolator(*this));
672 }
673
674 ValueT at(ParamT t) const override {
675 return interpolate_linear(this->points, t);
676 }
677
678 std::vector<ValueT> quantize(size_t n) const override {
679 return interpolate_linear_n(this->points, n);
680 }
681};
682
683template<typename ValueT, typename ParamT = float>
686
687public:
688 using base::base;
689
690 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
691 return std::unique_ptr<interpolator<ValueT, ParamT>>(new uniform_smooth_interpolator(*this));
692 }
693
694 ValueT at(ParamT t) const override {
695 return interpolate_smooth_cubic(this->points, t);
696 }
697};
698
699template<typename ValueT, typename ParamT = float>
700class smooth_interpolator : public piecewise_interpolator<ValueT, ParamT> {
702
703public:
704 using base::base;
705
706 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
707 return std::unique_ptr<interpolator<ValueT, ParamT>>(new smooth_interpolator(*this));
708 }
709
710 ValueT at(ParamT t) const override {
711 return interpolate_smooth_cubic(this->points, t);
712 }
713};
714
715template<typename ValueT, typename ParamT = float>
716class function_ref_interpolator : public interpolator<ValueT, ParamT> {
717public:
718 function_ref_interpolator(std::function<ValueT(ParamT)> function) : function(function) {}
719
720 std::unique_ptr<interpolator<ValueT, ParamT>> clone() const override {
721 return std::unique_ptr<interpolator<ValueT, ParamT>>(new function_ref_interpolator(*this));
722 }
723
724 ValueT at(ParamT t) const override {
725 if(function)
726 return function(t);
727 return {};
728 }
729
730 std::function<ValueT(ParamT)> function;
731};
732
733} // namespace math
734} // namespace cgv
base class for all classes that can be registered with support for dynamic properties (see also secti...
Definition base.h:75
Template of an abstract interface for interpolators that can be evaluated at a position t and return ...
virtual ValueT at(ParamT t) const =0
Return the interpolated value at position t.
virtual std::vector< ValueT > quantize(size_t n) const
Return a sequence of n uniformly-spaced samples from the interpolator within the parameter range [0,...
The interval template represents a closed interval of two numbers, i.e.
Definition interval.h:27
std::vector< ValueT > quantize(size_t n) const override
Return a sequence of n uniformly-spaced samples from the interpolator within the parameter range [0,...
std::vector< ValueT > quantize(size_t n) const override
Return a sequence of n uniformly-spaced samples from the interpolator within the parameter range [0,...
this header is dependency free
Definition print.h:11
Template class representing a piecewise linear function with uniformly spaced breakpoints that maps f...
std::vector< Y > breakpoints
The points that define the piecewise intervals and are assumed to be spaced uniformly over the domain...
interval< X > domain
The input parameter domain.
Y evaluate(X x) const
Return the piecewise linear interpolated value at position x.