File indexing completed on 2025-02-02 04:11:08
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 "trim.hpp" 0008 0009 #include "math/bezier/bezier_length.hpp" 0010 #include "model/shapes/group.hpp" 0011 #include "model/shapes/path.hpp" 0012 0013 #include "model/animation/join_animatables.hpp" 0014 0015 GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Trim) 0016 0017 QIcon glaxnimate::model::Trim::static_tree_icon() 0018 { 0019 return QIcon::fromTheme("edit-cut"); 0020 } 0021 0022 QString glaxnimate::model::Trim::static_type_name_human() 0023 { 0024 return i18n("Trim Path"); 0025 } 0026 0027 bool glaxnimate::model::Trim::process_collected() const 0028 { 0029 return multiple.get() == Simultaneously; 0030 } 0031 0032 static void chunk_start(const glaxnimate::math::bezier::Bezier& in, glaxnimate::math::bezier::Bezier& out, const glaxnimate::math::bezier::LengthData::SplitInfo& split, int max = -1) 0033 { 0034 using namespace glaxnimate::math::bezier; 0035 0036 if ( max == -1 ) 0037 max = in.closed_size(); 0038 0039 // empty 0040 if ( split.ratio == 0 && split.index == 0 && max == in.closed_size() ) 0041 { 0042 out = in; 0043 return; 0044 } 0045 0046 int index = split.index; 0047 0048 // gotta split mid segment 0049 if ( split.ratio < 1 && split.ratio > 0 ) 0050 { 0051 0052 auto split_points = CubicBezierSolver<QPointF>(in.segment(split.index)).split(split.ratio); 0053 out.push_back(Point( 0054 split_points.first[3], 0055 split_points.first[2], 0056 split_points.second[1], 0057 Smooth 0058 )); 0059 index += 1; 0060 0061 //we use the tangents from the split for the next point 0062 if ( index < max ) 0063 { 0064 out.push_back(Point( 0065 split_points.second[3], 0066 split_points.second[2], 0067 in[index].tan_out, 0068 in[index].type 0069 )); 0070 index += 1; 0071 } 0072 } 0073 0074 for ( int i = index; i < max; i++ ) 0075 out.push_back(in[i]); 0076 } 0077 0078 static void chunk_end(const glaxnimate::math::bezier::Bezier& in, glaxnimate::math::bezier::Bezier& out, const glaxnimate::math::bezier::LengthData::SplitInfo& split, int min = 0) 0079 { 0080 using namespace glaxnimate::math::bezier; 0081 0082 if ( split.ratio == 1 && min == 0 ) 0083 { 0084 out = in; 0085 return; 0086 } 0087 0088 for ( int i = min; i <= split.index; i++ ) 0089 out.push_back(in[i]); 0090 0091 // gotta split mid segment 0092 if ( split.ratio > 0 ) 0093 { 0094 auto split_points = CubicBezierSolver<QPointF>(in.segment(split.index)).split(split.ratio); 0095 0096 // adjust tangents for the pevious point 0097 if ( !out.empty() ) 0098 out[out.size()-1].tan_out = split_points.first[1]; 0099 0100 out.push_back(Point( 0101 split_points.first[3], 0102 split_points.first[2], 0103 split_points.second[1], 0104 Smooth 0105 )); 0106 } 0107 } 0108 0109 glaxnimate::math::bezier::MultiBezier glaxnimate::model::Trim::process( 0110 glaxnimate::model::FrameTime t, 0111 const math::bezier::MultiBezier& mbez 0112 ) const 0113 { 0114 if ( mbez.empty() ) 0115 return {}; 0116 0117 auto offset = this->offset.get_at(t); 0118 auto start = this->start.get_at(t); 0119 auto end = this->end.get_at(t); 0120 0121 // Normalize Inputs 0122 offset = math::fmod(offset, 1.f); 0123 start = math::bound(0.f, start, 1.f) + offset; 0124 end = math::bound(0.f, end, 1.f) + offset; 0125 if ( end < start ) 0126 std::swap(start, end); 0127 0128 // Handle the degenerate cases 0129 if ( math::abs(start * 1000 - end * 1000) < 1 ) 0130 return {}; 0131 0132 if ( qFuzzyIsNull(start) && qFuzzyCompare(end, 1.f) ) 0133 return mbez; 0134 0135 // Get the bezier chunk ratios 0136 // Note that now 0 <= s < e <= 2 0137 struct Chunk 0138 { 0139 float start; 0140 float end; 0141 }; 0142 std::vector<Chunk> chunks; 0143 if ( end <= 1 ) 0144 { 0145 // Simplest case, the segment is in [0, 1] 0146 chunks.push_back({start, end}); 0147 } 0148 else if ( start > 1 ) 0149 { 0150 // The whole segment is outside [0, 1] 0151 chunks.push_back({start - 1, end - 1}); 0152 } 0153 else 0154 { 0155 // The segment goes over the end point, so we need two splits 0156 chunks.push_back({start, 1}); 0157 chunks.push_back({0, end - 1}); 0158 } 0159 0160 0161 const int length_steps = 5; 0162 math::bezier::MultiBezier out; 0163 math::bezier::LengthData length_data(mbez, length_steps); 0164 0165 for ( const auto& chunk : chunks ) 0166 { 0167 auto start_data = length_data.at_ratio(chunk.start); 0168 auto end_data = length_data.at_ratio(chunk.end); 0169 0170 /* Chunk of a single curve 0171 * 0172 * [ bez[0] ... bez[start==end] ... bez[n] ] 0173 * aa|BBCCCCDDD|ee 0174 * 0175 * [ seg[0] ... seg[single_start] ... seg[single_end] ... seg[m] ] 0176 * aaaaa|BBBBBBBBBBB|CCC|DDDDDDDD|eeeeee 0177 */ 0178 if ( start_data.index == end_data.index ) 0179 { 0180 auto single_start_data = start_data.descend(); 0181 auto single_end_data = end_data.descend(); 0182 0183 math::bezier::Bezier b; 0184 0185 /** 0186 * Same bezier segment 0187 * [ seg[0] ... seg[single_start=single_end] ... seg[m] ] 0188 * aaaaa|BBBBBBBBBBBBBBBB|ccccc 0189 */ 0190 if ( single_start_data.index == single_end_data.index ) 0191 { 0192 const auto& in = mbez.beziers()[start_data.index]; 0193 int index = single_start_data.index; 0194 // split the segment at start 0195 qreal ratio_start = single_start_data.ratio; 0196 auto truncated_segment = math::bezier::CubicBezierSolver<QPointF>(in.segment(index)).split(ratio_start).second; 0197 // find the end ratio for the truncated segment and split it there 0198 qreal ratio_end = (single_end_data.ratio - ratio_start) / (1-ratio_start); 0199 auto result = math::bezier::CubicBezierSolver<QPointF>(truncated_segment).split(ratio_end).first; 0200 0201 // add to the bezier 0202 b.push_back(math::bezier::Point( 0203 result[0], 0204 result[0], 0205 result[1], 0206 math::bezier::Corner 0207 )); 0208 0209 b.push_back(math::bezier::Point( 0210 result[3], 0211 result[2], 0212 result[3], 0213 math::bezier::Corner 0214 )); 0215 } 0216 else 0217 { 0218 int start_max = single_end_data.index; 0219 if ( single_end_data.index == single_start_data.index + 1 ) 0220 start_max += 1; 0221 0222 chunk_start(mbez.beziers()[start_data.index], b, single_start_data, start_max); 0223 int end_min = qMax(start_max, single_start_data.index + 1); 0224 chunk_end(mbez.beziers()[start_data.index], b, single_end_data, end_min); 0225 0226 } 0227 if ( !b.empty() && !out.beziers().empty() && !out.back().empty() && 0228 out.back().back().pos == b[0].pos ) 0229 { 0230 auto& out_bez = out.back(); 0231 out_bez.back().tan_out = b[0].tan_out; 0232 out_bez.back().type = math::bezier::Corner; 0233 out_bez.points().insert(out_bez.end(), b.begin() + 1, b.end()); 0234 } 0235 else 0236 { 0237 out.beziers().push_back(b); 0238 } 0239 continue; 0240 } 0241 0242 /* Sequential chunk 0243 * 0244 * [ bez[0] ... bez[start] ... bez[end] ... bez[n] ] 0245 * aa|BBBBBBB|CCC|DDDDD|ee 0246 */ 0247 out.beziers().reserve(end_data.index - start_data.index); 0248 0249 0250 /* we skip the "a" part and get the "B" part 0251 * [ seg[0] ... seg[single_start] ... seg[m] ] 0252 * aaaaaaaaaaaaaaa|BBBBBBBBBBBBBBBBBBBBBBB 0253 */ 0254 { 0255 math::bezier::Bezier b; 0256 auto single_start_data = start_data.descend(); 0257 chunk_start(mbez.beziers()[start_data.index], b, single_start_data); 0258 out.beziers().push_back(b); 0259 } 0260 0261 // We get the segment between start and end ("C" part) 0262 for ( int i = start_data.index + 1; i < end_data.index; i++ ) 0263 { 0264 out.beziers().push_back(mbez.beziers()[i]); 0265 } 0266 0267 /* we get the "D" part and skip the "e" part 0268 * [ seg[0] ... seg[single_start] ... seg[m] ] 0269 * DDDDDDDDDDDDDDDDDDDDDDD|eeeeeeeeeeeeeee 0270 */ 0271 if ( end_data.ratio > 0 ) 0272 { 0273 math::bezier::Bezier b; 0274 auto single_end_data = end_data.descend(); 0275 chunk_end(mbez.beziers()[end_data.index], b, single_end_data); 0276 out.beziers().push_back(b); 0277 } 0278 } 0279 0280 for ( auto& bez : out.beziers() ) 0281 { 0282 if ( !bez.empty() && math::fuzzy_compare(bez[0].pos, bez.back().pos) ) 0283 { 0284 bez[0].tan_in = bez.back().tan_in; 0285 bez.points().pop_back(); 0286 bez.close(); 0287 } 0288 } 0289 0290 return out; 0291 }