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 }