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 <set> 0010 #include <QPointF> 0011 #include <QPainterPath> 0012 #include "math/bezier/solver.hpp" 0013 #include "math/bezier/point.hpp" 0014 #include "math/bezier/segment.hpp" 0015 0016 namespace glaxnimate::math::bezier { 0017 0018 using Solver = math::bezier::CubicBezierSolver<QPointF>; 0019 0020 class Bezier 0021 { 0022 public: 0023 using value_type = Point; 0024 0025 Bezier() = default; 0026 explicit Bezier(const Point& initial_point) 0027 : points_(1, initial_point) 0028 {} 0029 explicit Bezier(const QPointF& initial_point) 0030 : points_(1, initial_point) 0031 {} 0032 0033 const std::vector<Point>& points() const { return points_; } 0034 std::vector<Point>& points() { return points_; } 0035 0036 int size() const { return points_.size(); } 0037 int closed_size() const { return points_.size() + (closed_ ? 1 : 0); } 0038 bool empty() const { return points_.empty(); } 0039 auto begin() { return points_.begin(); } 0040 auto begin() const { return points_.begin(); } 0041 auto cbegin() const { return points_.begin(); } 0042 auto end() { return points_.end(); } 0043 auto end() const { return points_.end(); } 0044 auto cend() const { return points_.end(); } 0045 void push_back(const Point& p) { points_.push_back(p); } 0046 void clear() { points_.clear(); closed_ = false; } 0047 const Point& back() const { return points_.back(); } 0048 Point& back() { return points_.back(); } 0049 0050 const Point& operator[](int index) const { return points_[index % points_.size()]; } 0051 Point& operator[](int index) { return points_[index % points_.size()]; } 0052 0053 bool closed() const { return closed_; } 0054 void set_closed(bool closed) { closed_ = closed; } 0055 0056 /** 0057 * \brief Inserts a point at the given index 0058 * \param index Index to insert the point at 0059 * \param p Point to add 0060 * \returns \c this, for easy chaining 0061 */ 0062 Bezier& insert_point(int index, const Point& p) 0063 { 0064 points_.insert(points_.begin() + qBound(0, index, size()), p); 0065 return *this; 0066 } 0067 0068 /** 0069 * \brief Appends a point to the curve (relative tangents) 0070 * \see insert_point() 0071 */ 0072 Bezier& add_point(const QPointF& p, const QPointF& in_t = {0, 0}, const QPointF& out_t = {0, 0}) 0073 { 0074 points_.push_back(Point::from_relative(p, in_t, out_t)); 0075 return *this; 0076 } 0077 0078 /** 0079 * \brief Appends a point with symmetrical (relative) tangents 0080 * \see insert_point() 0081 */ 0082 Bezier& add_smooth_point(const QPointF& p, const QPointF& in_t) 0083 { 0084 points_.push_back(Point::from_relative(p, in_t, -in_t, Smooth)); 0085 return *this; 0086 } 0087 0088 /** 0089 * \brief Closes the bezier curve 0090 * \returns \c this, for easy chaining 0091 */ 0092 Bezier& close() 0093 { 0094 closed_ = true; 0095 return *this; 0096 } 0097 0098 /** 0099 * \brief Line from the last point to \p p 0100 * \returns \c this, for easy chaining 0101 */ 0102 Bezier& line_to(const QPointF& p) 0103 { 0104 if ( !empty() ) 0105 points_.back().tan_out = points_.back().pos; 0106 points_.push_back(p); 0107 return *this; 0108 } 0109 0110 /** 0111 * \brief Quadratic bezier from the last point to \p dest 0112 * \param handle Quadratic bezier handle 0113 * \param dest Destination point 0114 * \returns \c this, for easy chaining 0115 */ 0116 Bezier& quadratic_to(const QPointF& handle, const QPointF& dest) 0117 { 0118 if ( !empty() ) 0119 points_.back().tan_out = points_.back().pos + 2.0/3.0 * (handle - points_.back().pos); 0120 0121 push_back(dest); 0122 points_.back().tan_in = points_.back().pos + 2.0/3.0 * (handle - points_.back().pos); 0123 0124 return *this; 0125 } 0126 0127 /** 0128 * \brief Cubic bezier from the last point to \p dest 0129 * \param handle1 First cubic bezier handle 0130 * \param handle2 Second cubic bezier handle 0131 * \param dest Destination point 0132 * \returns \c this, for easy chaining 0133 */ 0134 Bezier& cubic_to(const QPointF& handle1, const QPointF& handle2, const QPointF& dest) 0135 { 0136 if ( !empty() ) 0137 points_.back().tan_out = handle1; 0138 0139 push_back(dest); 0140 points_.back().tan_in = handle2; 0141 0142 return *this; 0143 } 0144 0145 /** 0146 * \brief Reverses the orders of the points 0147 */ 0148 void reverse(); 0149 0150 QRectF bounding_box() const; 0151 0152 /** 0153 * \brief Split a segmet 0154 * \param index index of the point at the beginning of the segment to split 0155 * \param factor Value between [0,1] to determine the split point 0156 * \post size() increased by one and points[index+1] is the new point 0157 */ 0158 void split_segment(int index, qreal factor); 0159 0160 /** 0161 * \brief The point you'd get by calling split_segment(index, factor) 0162 */ 0163 Point split_segment_point(int index, qreal factor) const; 0164 0165 void remove_point(int index) 0166 { 0167 if ( index >= 0 && index < size() ) 0168 points_.erase(points_.begin() + index); 0169 } 0170 0171 void add_to_painter_path(QPainterPath& out) const; 0172 0173 math::bezier::Bezier lerp(const math::bezier::Bezier& other, qreal factor) const; 0174 0175 void set_point(int index, const math::bezier::Point& p) 0176 { 0177 if ( index >= 0 && index < size() ) 0178 points_[index] = p; 0179 } 0180 0181 BezierSegment segment(int index) const; 0182 void set_segment(int index, const BezierSegment& s); 0183 BezierSegment inverted_segment(int index) const; 0184 0185 int segment_count() const; 0186 0187 Bezier transformed(const QTransform& t) const; 0188 void transform(const QTransform& t); 0189 0190 /** 0191 * \brief Returns a new bezier with the given points removed 0192 */ 0193 math::bezier::Bezier removed_points(const std::set<int>& indices) const; 0194 0195 /** 0196 * \brief For closed beziers, ensure the last segment is present 0197 */ 0198 void add_close_point(); 0199 0200 /** 0201 * \brief Sets the given point to the type, and adjusts its tangents as needed 0202 * \returns The updated point 0203 * \note This doesn't update the bezier itself 0204 */ 0205 Point point_with_type(int index, math::bezier::PointType point_type) const; 0206 0207 private: 0208 /** 0209 * \brief Solver for the point \p p to the point \p p + 1 0210 */ 0211 math::bezier::CubicBezierSolver<QPointF> solver_for_point(int p) const 0212 { 0213 return segment(p); 0214 } 0215 0216 std::vector<Point> points_; 0217 bool closed_ = false; 0218 }; 0219 0220 0221 class MultiBezier 0222 { 0223 public: 0224 const std::vector<Bezier>& beziers() const { return beziers_; } 0225 std::vector<Bezier>& beziers() { return beziers_; } 0226 0227 Bezier& back() { return beziers_.back(); } 0228 const Bezier& back() const { return beziers_.back(); } 0229 0230 MultiBezier& move_to(const QPointF& p) 0231 { 0232 beziers_.push_back(Bezier(p)); 0233 at_end = false; 0234 return *this; 0235 } 0236 0237 MultiBezier& line_to(const QPointF& p) 0238 { 0239 handle_end(); 0240 beziers_.back().line_to(p); 0241 return *this; 0242 } 0243 0244 MultiBezier& quadratic_to(const QPointF& handle, const QPointF& dest) 0245 { 0246 handle_end(); 0247 beziers_.back().quadratic_to(handle, dest); 0248 return *this; 0249 } 0250 0251 MultiBezier& cubic_to(const QPointF& handle1, const QPointF& handle2, const QPointF& dest) 0252 { 0253 handle_end(); 0254 beziers_.back().cubic_to(handle1, handle2, dest); 0255 return *this; 0256 } 0257 0258 MultiBezier& close() 0259 { 0260 if ( !beziers_.empty() ) 0261 beziers_.back().close(); 0262 at_end = true; 0263 return *this; 0264 } 0265 0266 QRectF bounding_box() const; 0267 0268 QPainterPath painter_path() const 0269 { 0270 QPainterPath p; 0271 for ( const Bezier& bez : beziers_ ) 0272 bez.add_to_painter_path(p); 0273 return p; 0274 } 0275 0276 void append(const MultiBezier& other) 0277 { 0278 beziers_.insert(beziers_.end(), other.beziers_.begin(), other.beziers_.end()); 0279 } 0280 0281 void append(const QPainterPath& path); 0282 0283 void transform(const QTransform& t); 0284 0285 static MultiBezier from_painter_path(const QPainterPath& path); 0286 0287 int size() const { return beziers_.size(); } 0288 bool empty() const { return beziers_.empty(); } 0289 0290 const Bezier& operator[](int index) const 0291 { 0292 return beziers_[index]; 0293 } 0294 0295 Bezier& operator[](int index) 0296 { 0297 return beziers_[index]; 0298 } 0299 0300 private: 0301 void handle_end() 0302 { 0303 if ( at_end ) 0304 { 0305 beziers_.push_back(Bezier()); 0306 if ( beziers_.size() > 1 ) 0307 beziers_.back().add_point(beziers_[beziers_.size()-2].points().back().pos); 0308 at_end = false; 0309 } 0310 } 0311 0312 std::vector<Bezier> beziers_; 0313 bool at_end = true; 0314 }; 0315 0316 } // namespace glaxnimate::math 0317 0318 namespace glaxnimate::math { 0319 0320 inline bezier::Bezier lerp(const math::bezier::Bezier& a, const math::bezier::Bezier& b, qreal factor) 0321 { 0322 return a.lerp(b, factor); 0323 } 0324 0325 } // namespace glaxnimate::math 0326 0327 Q_DECLARE_METATYPE(glaxnimate::math::bezier::Bezier)