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)