File indexing completed on 2024-12-15 04:01:12

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #pragma once
0008 
0009 #include <array>
0010 
0011 #include "math/vector.hpp"
0012 #include "math/polynomial.hpp"
0013 
0014 #include <QPointF>
0015 
0016 namespace glaxnimate::math::bezier {
0017 
0018 template<class Vec>
0019 class CubicBezierSolver
0020 {
0021 public:
0022     using vector_type = Vec;
0023     using scalar = scalar_type<Vec>;
0024     using argument_type = scalar;
0025     using bounding_box_type = std::pair<Vec, Vec>;
0026 
0027 
0028     constexpr CubicBezierSolver(const std::array<Vec, 4>& points) noexcept
0029     : points_(points)
0030     {
0031         rebuild_coeff();
0032     }
0033 
0034     constexpr CubicBezierSolver(Vec p0, Vec p1, Vec p2, Vec p3) noexcept
0035     : CubicBezierSolver{{p0, p1, p2, p3}} {}
0036 
0037     /**
0038      * \brief Finds the point along the bezier curve
0039      * \param t between 0 and 1
0040      */
0041     constexpr Vec solve(argument_type t) const noexcept
0042     {
0043         return ((a_ * t + b_ ) * t +  c_ ) * t + d_;
0044     }
0045 
0046     constexpr scalar solve_component(argument_type t, int component) const noexcept
0047     {
0048         scalar a = detail::get(a_, component);
0049         scalar b = detail::get(b_, component);
0050         scalar c = detail::get(c_, component);
0051         scalar d = detail::get(d_, component);
0052         return ((a * t + b ) * t +  c ) * t + d;
0053     }
0054 
0055     constexpr scalar derivative(argument_type factor, int component) const noexcept
0056     {
0057         scalar a = detail::get(a_, component);
0058         scalar b = detail::get(b_, component);
0059         scalar c = detail::get(c_, component);
0060         return (3 * a * factor + 2 * b) * factor + c;
0061     }
0062 
0063     /**
0064      * \brief Finds the tangent of the point on the bezier
0065      * \param factor between 0 and 1
0066      * \return Angle in radians
0067      */
0068     scalar tangent_angle(argument_type factor) const
0069     {
0070         return std::atan2(derivative(factor, 1), derivative(factor, 0));
0071     }
0072 
0073     /**
0074      * \brief Finds the normal of the point on the bezier
0075      * \param factor between 0 and 1
0076      * \return Angle in radians
0077      */
0078     scalar normal_angle(argument_type factor) const
0079     {
0080         return std::atan2(derivative(factor, 0), derivative(factor, 1));
0081     }
0082 
0083     constexpr const std::array<Vec, 4>& points() const noexcept
0084     {
0085         return points_;
0086     }
0087 
0088 //     constexpr std::array<Vec, 4>& points() noexcept
0089 //     {
0090 //         return points_;
0091 //     }
0092 
0093     template<int i>
0094     constexpr void set(const Vec& v) noexcept
0095     {
0096         points_[i] = v;
0097         rebuild_coeff();
0098     }
0099 
0100     /**
0101      * \brief Splits a bezier
0102      * \param factor value between 0 and 1 determining the split position
0103      * \return Two vectors for the two resulting cubic beziers
0104      */
0105     std::pair<std::array<Vec, 4>, std::array<Vec, 4>> split(argument_type factor) const
0106     {
0107         // linear
0108         if ( points_[0] == points_[1] && points_[2] == points_[3] )
0109         {
0110             Vec mid = lerp(points_[0], points_[3], factor);
0111             return {
0112                 {points_[0], points_[0], mid, mid},
0113                 {mid, mid, points_[3], points_[3]},
0114             };
0115         }
0116 
0117         Vec p01 = lerp(points_[0], points_[1], factor);
0118         Vec p12 = lerp(points_[1], points_[2], factor);
0119         Vec p23 = lerp(points_[2], points_[3], factor);
0120 
0121         Vec p012 = lerp(p01, p12, factor);
0122         Vec p123 = lerp(p12, p23, factor);
0123 
0124         Vec p0123 = lerp(p012, p123, factor);
0125 
0126         return {
0127             {points_[0], p01, p012, p0123},
0128             {p0123, p123, p23, points_[3]}
0129         };
0130     }
0131 
0132     bounding_box_type bounds() const
0133     {
0134         std::vector<scalar> solutions;
0135         for ( int i = 0; i < detail::VecSize<Vec>::value; i++ )
0136         {
0137             bounds_solve(3 * detail::get(a_, i), 2 * detail::get(b_, i), detail::get(c_, i), solutions);
0138         }
0139 
0140         std::vector<Vec> boundary_points;
0141         //Add Begin and end point not the control points!
0142         boundary_points.push_back(points_[0]);
0143         boundary_points.push_back(points_[3]);
0144 
0145         for ( scalar e : solutions )
0146             boundary_points.push_back(solve(e));
0147 
0148         Vec min;
0149         Vec max;
0150         for ( int i = 0; i < detail::VecSize<Vec>::value; i++ )
0151         {
0152             scalar cmin = std::numeric_limits<scalar>::max();
0153             scalar cmax = std::numeric_limits<scalar>::lowest();
0154             for ( const Vec& p : boundary_points )
0155             {
0156                if ( detail::get(p, i) < cmin )
0157                    cmin = detail::get(p, i);
0158                if ( detail::get(p, i) > cmax )
0159                    cmax = detail::get(p, i);
0160             }
0161             detail::get(max, i) = cmax;
0162             detail::get(min, i) = cmin;
0163         }
0164 
0165         return {min, max};
0166     }
0167 
0168 
0169     /**
0170      * \brief Returns the \p t for the min/max points for the given component
0171      *
0172      * \note The returned value is sorted so that \p first <= \p second
0173      */
0174     std::pair<argument_type, argument_type> extrema(int component) const
0175     {
0176         std::vector<scalar> solutions;
0177         bounds_solve(3 * detail::get(a_, component), 2 * detail::get(b_, component), detail::get(c_, component), solutions);
0178 
0179         // No solution: end points have the extrema
0180         if ( solutions.size() == 0 )
0181             return {0, 1};
0182 
0183         // One solution: one of the end points has an extreme
0184         if ( solutions.size() == 1 )
0185         {
0186             auto val = solve_component(solutions[0], component);
0187             // The end point is a minimum
0188             if ( detail::get(points_[0], component) < val )
0189             {
0190                 // Last point is min
0191                 if ( detail::get(points_[3], component) < detail::get(points_[0], component) )
0192                     return {solutions[0], 1};
0193 
0194                 // First point is min
0195                 return {0, solutions[0]};
0196             }
0197             // Last point is min
0198             else if ( detail::get(points_[3], component) < val )
0199             {
0200                 return {solutions[0], 1};
0201             }
0202             // end point is a maximum
0203             else
0204             {
0205                 // First point is max
0206                 if ( detail::get(points_[0], component) > val )
0207                     return {0, solutions[0]};
0208 
0209                 // Last point is max
0210                 return {solutions[0], 1};
0211             }
0212         }
0213 
0214         if ( solutions[0] > solutions[1] )
0215             return {solutions[1], solutions[0]};
0216 
0217         return {solutions[0], solutions[1]};
0218     }
0219 
0220     /**
0221      * \brief Return inflection points for a 2D bezier
0222      */
0223     std::vector<argument_type> inflection_points() const
0224     {
0225         auto denom = detail::get(a_, 1) * detail::get(b_, 0) - detail::get(a_, 0) * detail::get(b_, 1);
0226         if ( qFuzzyIsNull(denom) )
0227             return {};
0228 
0229         auto t_cusp = -0.5 * (detail::get(a_, 1) * detail::get(c_, 0) - detail::get(a_, 0) * detail::get(c_, 1)) / denom;
0230 
0231         auto square = t_cusp * t_cusp - 1./3. * (detail::get(b_, 1) * detail::get(c_, 0) - detail::get(b_, 0) * detail::get(c_, 1)) / denom;
0232 
0233         if ( square < 0 )
0234             return {};
0235 
0236         auto root = std::sqrt(square);
0237         if ( qFuzzyIsNull(root) )
0238         {
0239             if ( is_valid_inflection(t_cusp) )
0240                 return {t_cusp};
0241             return {};
0242         }
0243 
0244         std::vector<argument_type> roots;
0245         roots.reserve(2);
0246 
0247         if ( is_valid_inflection(t_cusp - root) )
0248             roots.push_back(t_cusp - root);
0249 
0250 
0251         if ( is_valid_inflection(t_cusp + root) )
0252             roots.push_back(t_cusp + root);
0253 
0254         return roots;
0255     }
0256 
0257 
0258     std::vector<std::pair<argument_type, argument_type>> intersections(
0259         const CubicBezierSolver& other, int max_count = 1, scalar tolerance = 2, int max_recursion = 10
0260     ) const
0261     {
0262         std::vector<std::pair<argument_type, argument_type>> intersections;
0263         intersects_impl(IntersectData(*this), IntersectData(other), max_count, tolerance, intersections, 0, max_recursion);
0264         return intersections;
0265     }
0266 
0267     /**
0268      * \brief Returns the t corresponding to the given value for a component.
0269      * \returns `t` or -1 in case of no root has been found
0270      */
0271     scalar t_at_value(scalar value, int comp = 0) const
0272     {
0273         auto roots = math::cubic_roots(
0274             detail::get(a_, comp),
0275             detail::get(b_, comp),
0276             detail::get(c_, comp),
0277             detail::get(d_, comp) - value
0278         );
0279 
0280         for ( auto root : roots )
0281         {
0282             if ( valid_solution(root) )
0283                 return root;
0284         }
0285 
0286         return -1;
0287     }
0288 
0289 private:
0290     static constexpr bool is_valid_inflection(scalar root) noexcept
0291     {
0292         return root > 0 && root < 1;
0293     }
0294 
0295     // You get these terms by expanding the Bezier definition and re-arranging them as a polynomial in t
0296     // B(t) = a t^3 + b t^2 + c t + d
0297     static constexpr scalar a(const scalar& k0, const scalar& k1, const scalar& k2, const scalar& k3) noexcept
0298     {
0299         return -k0 + k1*3 + k2 * -3 + k3;
0300     }
0301     static constexpr scalar b(const scalar& k0, const scalar& k1, const scalar& k2) noexcept
0302     {
0303         return k0 * 3 + k1 * -6 + k2 * 3;
0304     }
0305     static constexpr scalar c(const scalar& k0, const scalar& k1) noexcept
0306     {
0307         return k0 * -3 + k1 * 3;
0308     }
0309     static constexpr scalar d(const scalar& k0) noexcept
0310     {
0311         return k0;
0312     }
0313 
0314     static void bounds_solve(scalar a, scalar b, scalar c, std::vector<argument_type>& solutions)
0315     {
0316         scalar d = b * b - 4. * a * c;
0317 
0318         // no real solution
0319         if ( d < 0 )
0320             return;
0321 
0322         if ( qFuzzyIsNull(a) )
0323         {
0324             // linear case
0325             add_bounds_solution(-c / b, solutions);
0326         }
0327         else
0328         {
0329             add_bounds_solution((-b + std::sqrt(d)) / (2 * a), solutions);
0330 
0331             if ( d != 0 )
0332                 add_bounds_solution((-b - std::sqrt(d)) / (2 * a), solutions);
0333         }
0334     }
0335 
0336     static bool valid_solution(scalar& root)
0337     {
0338         if ( root >= 0 && root <= 1 )
0339             return true;
0340 
0341         if ( qFuzzyIsNull(root) )
0342         {
0343             root = 0;
0344             return true;
0345         }
0346 
0347         if ( qFuzzyCompare(root, 1) )
0348         {
0349             root = 1;
0350             return true;
0351         }
0352 
0353         return false;
0354     }
0355 
0356     static void add_bounds_solution(scalar solution, std::vector<argument_type>& solutions)
0357     {
0358         if ( valid_solution(solution) )
0359             solutions.push_back(solution);
0360     }
0361 
0362     struct IntersectData
0363     {
0364         IntersectData(
0365             const CubicBezierSolver& solver,
0366             const bounding_box_type& box,
0367             argument_type t1,
0368             argument_type t2
0369         ) : bez(solver),
0370             width(box.second.x() - box.first.x()),
0371             height(box.second.y() - box.first.y()),
0372             center((box.first + box.second) / 2),
0373             t1(t1),
0374             t2(t2),
0375             t((t1 + t2) / 2)
0376         {}
0377 
0378         IntersectData(const CubicBezierSolver& solver)
0379             : IntersectData(solver, solver.bounds(), 0, 1)
0380         {}
0381 
0382         std::pair<IntersectData, IntersectData> split() const
0383         {
0384             auto split = bez.split(0.5);
0385             return {
0386                 IntersectData(split.first),
0387                 IntersectData(split.second)
0388             };
0389         }
0390 
0391         bool intersects(const IntersectData& other) const
0392         {
0393             return std::abs(center.x() - other.center.x()) * 2 < width + other.width &&
0394                    std::abs(center.y() - other.center.y()) * 2 < height + other.height;
0395         }
0396 
0397         CubicBezierSolver bez;
0398         scalar width;
0399         scalar height;
0400         Vec center;
0401         argument_type t1, t2, t;
0402     };
0403 
0404     static void intersects_impl(
0405         const IntersectData& d1,
0406         const IntersectData& d2,
0407         std::size_t max_count,
0408         scalar tolerance,
0409         std::vector<std::pair<argument_type, argument_type>>& intersections,
0410         int depth,
0411         int max_recursion
0412     )
0413     {
0414         if ( !d1.intersects(d2) )
0415             return;
0416 
0417         if ( depth >= max_recursion || (d1.width <= tolerance && d1.height <= tolerance && d2.width <= tolerance && d2.height <= tolerance) )
0418         {
0419             intersections.emplace_back(d1.t, d2.t);
0420             return;
0421         }
0422 
0423         auto d1s = d1.split();
0424         auto d2s = d2.split();
0425         std::array<std::pair<const IntersectData&, const IntersectData&>, 4> splits = {{
0426             {d1s.first,  d2s.first },
0427             {d1s.first,  d2s.second},
0428             {d1s.second, d2s.first },
0429             {d1s.second, d2s.second}
0430         }};
0431 
0432         for ( const auto& p : splits )
0433         {
0434             intersects_impl(p.first, p.second, max_count, tolerance, intersections, depth + 1, max_recursion);
0435             if ( intersections.size() >= max_count )
0436                 return;
0437         }
0438     }
0439 
0440     void rebuild_coeff()
0441     {
0442         for ( int component = 0; component < detail::VecSize<Vec>::value; component++ )
0443         {
0444             detail::get(a_, component) = a(
0445                 detail::get(points_[0], component),
0446                 detail::get(points_[1], component),
0447                 detail::get(points_[2], component),
0448                 detail::get(points_[3], component)
0449             );
0450             detail::get(b_, component) = b(
0451                 detail::get(points_[0], component),
0452                 detail::get(points_[1], component),
0453                 detail::get(points_[2], component)
0454             );
0455             detail::get(c_, component) = c(
0456                 detail::get(points_[0], component),
0457                 detail::get(points_[1], component)
0458             );
0459             detail::get(d_, component) = d(
0460                 detail::get(points_[0], component)
0461             );
0462         }
0463     }
0464 
0465     std::array<Vec, 4> points_;
0466     // Polynomial coefficients (a t^3 + b t^2 + c t + d = 0)
0467     Vec a_, b_, c_, d_;
0468 };
0469 
0470 } // namespace glaxnimate::math::bezier