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 }