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