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 }