File indexing completed on 2025-02-02 04:11:09

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 "zig_zag.hpp"
0008 #include "math/geom.hpp"
0009 #include "math/vector.hpp"
0010 #include "math/bezier/bezier_length.hpp"
0011 
0012 GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::ZigZag)
0013 
0014 using namespace glaxnimate;
0015 using namespace glaxnimate::math::bezier;
0016 using BezierSolver = glaxnimate::math::bezier::CubicBezierSolver<QPointF>;
0017 
0018 
0019 static double angle_mean(double a, double b)
0020 {
0021     if ( math::abs(a-b) > math::pi )
0022         return (a + b) / 2 + math::pi;
0023 
0024     return (a + b) / 2;
0025 }
0026 
0027 static void zig_zag_corner(Bezier& output_bezier, const BezierSolver* segment_before, const BezierSolver* segment_after, float amplitude, int direction, float tangent_length)
0028 {
0029     QPointF point;
0030     double angle;
0031     double tan_angle;
0032 
0033     // We use 0.01 and 0.99 instead of 0 and 1 because they yield better results
0034     if ( !segment_before )
0035     {
0036         point = segment_after->points()[0];
0037         angle = segment_after->normal_angle(0.01);
0038         tan_angle = segment_after->tangent_angle(0.01);
0039     }
0040     else if ( !segment_after )
0041     {
0042         point = segment_before->points()[3];
0043         angle = segment_before->normal_angle(0.99);
0044         tan_angle = segment_before->tangent_angle(0.99);
0045     }
0046     else
0047     {
0048         point = segment_after->points()[0];
0049         angle = -angle_mean(segment_after->normal_angle(0.01), segment_before->normal_angle(0.99));
0050         tan_angle = angle_mean(segment_after->tangent_angle(0.01), segment_before->tangent_angle(0.99));
0051     }
0052 
0053     output_bezier.add_point(point + math::from_polar<QPointF>(direction * amplitude, angle));
0054     auto& vertex = output_bezier.back();
0055 
0056     // It's ok to float-compare as it's a value we set explicitly to 0
0057     if ( tangent_length != 0 )
0058     {
0059         vertex.tan_in = vertex.pos + math::from_polar<QPointF>(-tangent_length, tan_angle);
0060         vertex.tan_out = vertex.pos + math::from_polar<QPointF>(tangent_length, tan_angle);
0061     }
0062 }
0063 
0064 static int zig_zag_segment(Bezier& output_bezier,const BezierSolver& segment, const LengthData& seg_len, float amplitude, int frequency, int direction, float tangent_length)
0065 {
0066     for ( int i = 0; i < frequency; i++ )
0067     {
0068         auto f = (i + 1.) / (frequency + 1.);
0069         auto t = seg_len.at_ratio(f).ratio;
0070         auto angle = segment.normal_angle(t);
0071         auto point = segment.solve(t);
0072 
0073         output_bezier.add_point(point + math::from_polar<QPointF>(direction * amplitude, -angle));
0074         auto& vertex = output_bezier.back();
0075 
0076         // It's ok to float-compare as it's a value we set explicitly to 0
0077         if ( tangent_length != 0 )
0078         {
0079             auto tan_angle = segment.tangent_angle(t);
0080             vertex.tan_in = vertex.pos + math::from_polar<QPointF>(-tangent_length, tan_angle);
0081             vertex.tan_out = vertex.pos + math::from_polar<QPointF>(tangent_length, tan_angle);
0082         }
0083 
0084         direction = -direction;
0085     }
0086 
0087     return direction;
0088 }
0089 
0090 static Bezier zig_zag_bezier(const Bezier& input_bezier, float amplitude, int frequency, model::ZigZag::Style style)
0091 {
0092     Bezier output_bezier;
0093 
0094     output_bezier.set_closed(input_bezier.closed());
0095     auto count = input_bezier.segment_count();
0096 
0097     if ( count == 0 )
0098         return output_bezier;
0099 
0100     auto direction = -1;
0101     BezierSolver segment = input_bezier.segment(count - 1);
0102     BezierSolver next_segment = input_bezier.segment(0);
0103     LengthData seg_len(next_segment, 20);
0104 
0105     auto tangent_length = style == model::ZigZag::Wave ? seg_len.length() / (frequency + 1.) / 2. : 0;
0106 
0107     zig_zag_corner(output_bezier, input_bezier.closed() ? &segment : nullptr, &next_segment, amplitude, direction, tangent_length);
0108 
0109     for ( auto i = 0; i < count; i++ )
0110     {
0111         segment = next_segment;
0112 
0113         direction = zig_zag_segment(output_bezier, segment, seg_len, amplitude, frequency, -direction, tangent_length);
0114 
0115         if ( i == count - 1 && !input_bezier.closed() )
0116         {
0117             zig_zag_corner(output_bezier, &segment, nullptr, amplitude, direction, tangent_length);
0118         }
0119         else
0120         {
0121             next_segment = input_bezier.segment((i + 1) % count);
0122 
0123             seg_len = LengthData (next_segment, 20);
0124             zig_zag_corner(output_bezier, &segment, &next_segment, amplitude, direction, tangent_length);
0125         }
0126     }
0127 
0128     return output_bezier;
0129 }
0130 
0131 
0132 QIcon glaxnimate::model::ZigZag::static_tree_icon()
0133 {
0134     return QIcon::fromTheme("path-simplify");
0135 }
0136 
0137 QString glaxnimate::model::ZigZag::static_type_name_human()
0138 {
0139     return i18n("Zig Zag");
0140 }
0141 
0142 bool glaxnimate::model::ZigZag::process_collected() const
0143 {
0144     return false;
0145 }
0146 
0147 glaxnimate::math::bezier::MultiBezier glaxnimate::model::ZigZag::process(
0148     glaxnimate::model::FrameTime t,
0149     const math::bezier::MultiBezier& mbez
0150 ) const
0151 {
0152     if ( mbez.empty() )
0153         return {};
0154 
0155     int frequency = math::max(0, qRound(this->frequency.get_at(t)));
0156     auto amplitude = this->amplitude.get_at(t);
0157     auto point_type = this->style.get();
0158 
0159     MultiBezier out;
0160 
0161     for ( const auto& inbez : mbez.beziers() )
0162         out.beziers().push_back(zig_zag_bezier(inbez, amplitude, frequency, point_type));
0163 
0164     return out;
0165 }