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 #include "bezier.hpp"
0008 
0009 using namespace glaxnimate;
0010 
0011 QRectF math::bezier::Bezier::bounding_box() const
0012 {
0013     if ( size() < 2 )
0014         return {};
0015 
0016     auto pair = solver_for_point(0).bounds();
0017     QRectF box(pair.first, pair.second);
0018     for ( int i = 1; i < size() - 1; i++ )
0019     {
0020         pair = solver_for_point(i).bounds();
0021         box |= QRectF(pair.first, pair.second);
0022     }
0023 
0024     if ( closed_ )
0025     {
0026         pair = solver_for_point(size()-1).bounds();
0027         box |= QRectF(pair.first, pair.second);
0028     }
0029 
0030     return box;
0031 }
0032 
0033 void math::bezier::Bezier::split_segment(int index, qreal factor)
0034 {
0035     if ( points_.empty() )
0036         return;
0037 
0038     if ( index < 0 )
0039     {
0040         points_.insert(points_.begin(), points_[0]);
0041         return;
0042     }
0043     else if ( index >= size() )
0044     {
0045         points_.insert(points_.end(), points_.back());
0046         return;
0047     }
0048 
0049     auto split_points = solver_for_point(index).split(factor);
0050     points_[index].tan_out = split_points.first[1];
0051     points_[(index+1) % size()].tan_in = split_points.second[2];
0052 
0053     auto type = Smooth;
0054     if ( factor <= 0 )
0055         type = points_[index].type;
0056     else if ( factor >= 1 )
0057         type = points_[(index+1) % size()].type;
0058 
0059     points_.insert(points_.begin() + index + 1, Point(
0060         split_points.first[3],
0061         split_points.first[2],
0062         split_points.second[1],
0063         type
0064     ));
0065 }
0066 
0067 math::bezier::Point math::bezier::Bezier::split_segment_point(int index, qreal factor) const
0068 {
0069     if ( index < 0 )
0070         return points_[0];
0071     else if ( index >= size() )
0072         return points_.back();
0073 
0074     if ( factor <= 0 )
0075         return points_[index];
0076     else if ( factor >= 1 )
0077         return points_[(index+1) % size()];
0078 
0079     auto split_points = solver_for_point(index).split(factor);
0080     return Point(
0081         split_points.first[3],
0082         split_points.first[2],
0083         split_points.second[1],
0084         Smooth
0085     );
0086 }
0087 
0088 void math::bezier::Bezier::add_to_painter_path(QPainterPath& out) const
0089 {
0090     if ( size() < 2 )
0091         return;
0092 
0093     out.moveTo(points_[0].pos);
0094     for ( int i = 1; i < size(); i++ )
0095     {
0096         out.cubicTo(points_[i-1].tan_out, points_[i].tan_in, points_[i].pos);
0097     }
0098 
0099     if ( closed_ )
0100     {
0101         out.cubicTo(points_.back().tan_out, points_[0].tan_in, points_[0].pos);
0102         out.closeSubpath();
0103     }
0104 }
0105 
0106 math::bezier::Bezier math::bezier::Bezier::lerp(const math::bezier::Bezier& other, qreal factor) const
0107 {
0108     if ( other.closed_ != closed_ || other.size() != size() )
0109         return *this;
0110 
0111     math::bezier::Bezier lerped;
0112     lerped.closed_ = closed_;
0113     lerped.points_.reserve(size());
0114     for ( int i = 0; i < size(); i++ )
0115         lerped.points_.push_back(Point::from_relative(
0116             math::lerp(points_[i].pos, other.points_[i].pos, factor),
0117             math::lerp(
0118                 points_[i].tan_in - points_[i].pos,
0119                 other.points_[i].tan_in - other.points_[i].pos,
0120                 factor
0121             ),
0122             math::lerp(
0123                 points_[i].tan_out - points_[i].pos,
0124                 other.points_[i].tan_out - other.points_[i].pos,
0125                 factor
0126             )
0127         ));
0128     return lerped;
0129 }
0130 
0131 void math::bezier::Bezier::reverse()
0132 {
0133     std::reverse(points_.begin(), points_.end());
0134 
0135     if ( closed_ && points_.size() > 1 )
0136     {
0137         auto back = points_.back();
0138         points_.pop_back();
0139         points_.insert(points_.begin(), back);
0140     }
0141 
0142     for ( auto& p : points_ )
0143         std::swap(p.tan_in, p.tan_out);
0144 }
0145 
0146 math::bezier::BezierSegment math::bezier::Bezier::segment(int index) const
0147 {
0148     return {
0149         points_[index].pos,
0150         points_[index].tan_out,
0151         points_[(index+1) % points_.size()].tan_in,
0152         points_[(index+1) % points_.size()].pos
0153     };
0154 }
0155 
0156 math::bezier::BezierSegment math::bezier::Bezier::inverted_segment(int index) const
0157 {
0158     return {
0159         points_[(index+1) % points_.size()].pos,
0160         points_[(index+1) % points_.size()].tan_in,
0161         points_[index].tan_out,
0162         points_[index].pos
0163     };
0164 }
0165 
0166 void math::bezier::Bezier::set_segment(int index, const math::bezier::BezierSegment& s)
0167 {
0168     points_[index].pos = s[0];
0169     points_[index].drag_tan_out(s[1]);
0170     points_[(index+1) % points_.size()].pos = s[3];
0171     points_[(index+1) % points_.size()].drag_tan_in(s[2]);
0172 }
0173 
0174 math::bezier::Bezier math::bezier::Bezier::transformed(const QTransform& t) const
0175 {
0176     auto copy = *this;
0177     copy.transform(t);
0178     return copy;
0179 }
0180 
0181 void math::bezier::Bezier::transform(const QTransform& t)
0182 {
0183     for ( auto& p : points_ )
0184         p.transform(t);
0185 }
0186 
0187 int glaxnimate::math::bezier::Bezier::segment_count() const
0188 {
0189     return closed_ || points_.empty() ? points_.size() : points_.size() - 1;
0190 }
0191 
0192 math::bezier::Bezier math::bezier::Bezier::removed_points(const std::set<int>& indices) const
0193 {
0194     math::bezier::Bezier new_bez;
0195     new_bez.set_closed(closed_);
0196 
0197     for ( int i = 0; i < size(); i++ )
0198         if ( !indices.count(i) )
0199             new_bez.push_back(points_[i]);
0200 
0201     return new_bez;
0202 }
0203 
0204 void glaxnimate::math::bezier::Bezier::add_close_point()
0205 {
0206     if ( closed_ && !points_.empty() && !math::fuzzy_compare(points_[0].pos, points_.back().pos) )
0207     {
0208         points_.push_back(points_[0]);
0209         points_.back().tan_out = points_[0].tan_in = points_[0].pos;
0210     }
0211 }
0212 
0213 glaxnimate::math::bezier::Point glaxnimate::math::bezier::Bezier::point_with_type(int index, PointType point_type) const
0214 {
0215     auto point = points_[index];
0216 
0217     if ( point.type == point_type && point.type == math::bezier::PointType::Corner )
0218     {
0219         point.tan_in = point.tan_out = point.pos;
0220     }
0221 
0222     point.type = point_type;
0223 
0224     if ( point_type != math::bezier::PointType::Corner )
0225     {
0226         if ( math::fuzzy_compare(point.tan_in, point.pos) && (index > 0 || closed_) )
0227         {
0228             const auto& prev = index == 0 ? points_.back() : points_[index - 1];
0229             point.tan_in = (prev.pos - point.pos) / 6 + point.pos;
0230         }
0231 
0232         if ( math::fuzzy_compare(point.tan_out, point.pos) && (index < size() - 1 || closed_) )
0233         {
0234             const auto& next = points_[index + 1];
0235             point.tan_out = (next.pos - point.pos) / 6 + point.pos;
0236         }
0237     }
0238 
0239     point.adjust_handles_from_type();
0240     return point;
0241 }
0242 
0243 QRectF math::bezier::MultiBezier::bounding_box() const
0244 {
0245     if ( beziers_.empty() )
0246         return {};
0247 
0248     QRectF box;
0249     for ( const Bezier& bez : beziers_ )
0250     {
0251         QRectF bb = bez.bounding_box();
0252         if ( box.isNull() )
0253             box = bb;
0254         else if ( !bb.isNull() )
0255             box |= bb;
0256     }
0257     return box;
0258 }
0259 
0260 void math::bezier::MultiBezier::append(const QPainterPath& path)
0261 {
0262     std::array<QPointF, 3> data;
0263     int data_i = 0;
0264     for ( int i = 0; i < path.elementCount(); i++ )
0265     {
0266         auto element = path.elementAt(i);
0267         switch ( element.type )
0268         {
0269             case QPainterPath::MoveToElement:
0270                 if ( !beziers_.empty() && beziers_.back()[0].pos == beziers_.back().back().pos )
0271                     close();
0272                 move_to(element);
0273                 break;
0274             case QPainterPath::LineToElement:
0275                 line_to(element);
0276                 break;
0277             case QPainterPath::CurveToElement:
0278                 data_i = 0;
0279                 data[0] = element;
0280                 break;
0281             case QPainterPath::CurveToDataElement:
0282                 ++data_i;
0283                 data[data_i] = element;
0284                 if ( data_i == 2 )
0285                 {
0286                     cubic_to(data[0], data[1], data[2]);
0287                     data_i = -1;
0288                 }
0289                 break;
0290         }
0291     }
0292 }
0293 
0294 void math::bezier::MultiBezier::transform(const QTransform& t)
0295 {
0296     for ( auto& bez : beziers_ )
0297         bez.transform(t);
0298 }
0299 
0300 math::bezier::MultiBezier math::bezier::MultiBezier::from_painter_path(const QPainterPath& path)
0301 {
0302     math::bezier::MultiBezier bez;
0303     bez.append(path);
0304     return bez;
0305 }