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

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 "animatable.hpp"
0008 
0009 #include "command/animation_commands.hpp"
0010 #include "model/object.hpp"
0011 #include "math/bezier/segment.hpp"
0012 
0013 
0014 std::vector<std::unique_ptr<glaxnimate::model::KeyframeBase>> glaxnimate::model::KeyframeBase::split(const KeyframeBase* other, std::vector<qreal> splits) const
0015 {
0016     std::vector<std::unique_ptr<KeyframeBase>> kfs;
0017     if ( transition().hold() )
0018     {
0019         kfs.push_back(clone());
0020         kfs.push_back(other->clone());
0021         return kfs;
0022     }
0023 
0024     auto splitter = this->splitter(other);
0025 
0026     kfs.reserve(splits.size()+2);
0027     qreal prev_split = 0;
0028     const KeyframeBase* to_split = this;
0029     std::unique_ptr<KeyframeBase> split_right;
0030     QPointF old_p;
0031     for ( qreal split : splits )
0032     {
0033         // Skip zeros
0034         if ( qFuzzyIsNull(split) )
0035             continue;
0036 
0037         qreal split_ratio = (split - prev_split) / (1 - prev_split);
0038         prev_split = split;
0039         auto transitions = to_split->transition().split_t(split_ratio);
0040         // split_ratio is t
0041         // p.x() is time lerp
0042         // p.y() is value lerp
0043         QPointF p = transition().bezier().solve(split);
0044         splitter->step(p);
0045         auto split_left = splitter->left(old_p);
0046         split_left->set_transition(transitions.first);
0047         old_p = p;
0048         split_right = splitter->right(p);
0049         split_right->set_transition(transitions.second);
0050         to_split = split_right.get();
0051         kfs.push_back(std::move(split_left));
0052     }
0053     kfs.push_back(std::move(split_right));
0054     kfs.push_back(splitter->last());
0055     kfs.back()->set_transition(other->transition());
0056 
0057     return kfs;
0058 }
0059 
0060 
0061 class glaxnimate::model::Keyframe<QPointF>::PointKeyframeSplitter : public KeyframeSplitter
0062 {
0063 public:
0064     const Keyframe* self;
0065     const Keyframe* other;
0066     math::bezier::CubicBezierSolver<QPointF> solver;
0067     math::bezier::LengthData len;
0068     QPointF tan_in;
0069     math::bezier::Point point_before;
0070     math::bezier::Point point_mid;
0071     qreal prev_split = 0;
0072     bool linear;
0073 
0074     PointKeyframeSplitter(const Keyframe<QPointF>* self, const Keyframe<QPointF>* other)
0075         : self(self),
0076           other(other),
0077           solver(self->bezier_solver(*other)),
0078           len(solver, 20),
0079           tan_in(self->point_.tan_in),
0080           linear(self->is_linear())
0081     {
0082     }
0083 
0084     void step(const QPointF& p) override
0085     {
0086         if ( linear )
0087             return;
0088 
0089         // TODO: would need the ability to access other keyframes to properly handle values outside [0, 1]
0090         qreal split = math::bound(0., p.y(), 1.);
0091         auto beziers = solver.split((split - prev_split) / (1 - prev_split));
0092         prev_split = split;
0093         solver = beziers.second;
0094         point_before = math::bezier::Point (beziers.first[0], tan_in, beziers.first[1]);
0095         point_mid = math::bezier::Point (beziers.first[3], beziers.first[2], beziers.second[1]);
0096         tan_in = beziers.second[2];
0097     }
0098 
0099     std::unique_ptr<KeyframeBase> left(const QPointF& p) const override
0100     {
0101         if ( linear )
0102         {
0103             return std::make_unique<Keyframe>(
0104                 math::lerp(self->time(), other->time(), p.x()),
0105                 math::lerp(self->get(), other->get(), p.y())
0106             );
0107         }
0108 
0109         return std::make_unique<Keyframe>(math::lerp(self->time(), other->time(), p.x()), point_before);
0110     }
0111 
0112     std::unique_ptr<KeyframeBase> right(const QPointF& p) const override
0113     {
0114         if ( linear )
0115         {
0116             return std::make_unique<Keyframe>(
0117                 math::lerp(self->time(), other->time(), p.x()),
0118                 math::lerp(self->get(), other->get(), p.y())
0119             );
0120         }
0121 
0122         return std::make_unique<Keyframe>(math::lerp(self->time(), other->time(), p.x()), point_mid);
0123     }
0124 
0125     std::unique_ptr<KeyframeBase> last() const override
0126     {
0127         if ( linear )
0128             return other->clone();
0129 
0130         math::bezier::Point point_after = other->point();
0131         point_after.tan_in = tan_in;
0132         return std::make_unique<Keyframe>(other->time(), point_after);
0133     }
0134 
0135 };
0136 
0137 std::unique_ptr<glaxnimate::model::KeyframeBase::KeyframeSplitter> glaxnimate::model::Keyframe<QPointF>::splitter(const KeyframeBase* other) const
0138 {
0139     return std::make_unique<PointKeyframeSplitter>(this, static_cast<const Keyframe<QPointF>*>(other));
0140 }
0141 
0142 bool glaxnimate::model::AnimatableBase::assign_from(const model::BaseProperty* prop)
0143 {
0144     if ( prop->traits().flags != traits().flags || prop->traits().type != traits().type )
0145         return false;
0146 
0147     const AnimatableBase* other = static_cast<const AnimatableBase*>(prop);
0148 
0149     clear_keyframes();
0150 
0151     if ( !other->animated() )
0152         return set_value(other->value());
0153 
0154     for ( int i = 0, e = other->keyframe_count(); i < e; i++ )
0155     {
0156         const KeyframeBase* kf_other = other->keyframe(i);
0157         KeyframeBase* kf = set_keyframe(kf_other->time(), kf_other->value());
0158         if ( kf )
0159             kf->set_transition(kf_other->transition());
0160     }
0161 
0162     return true;
0163 }
0164 
0165 bool glaxnimate::model::AnimatableBase::set_undoable(const QVariant& val, bool commit)
0166 {
0167     if ( !valid_value(val) )
0168         return false;
0169 
0170     object()->push_command(new command::SetMultipleAnimated(
0171         i18n("Update %1", name()),
0172         {this},
0173         {value()},
0174         {val},
0175         commit
0176     ));
0177     return true;
0178 }
0179 
0180 glaxnimate::model::AnimatableBase::MidTransition glaxnimate::model::AnimatableBase::mid_transition(model::FrameTime time) const
0181 {
0182     int keyframe_index = this->keyframe_index(time);
0183     const KeyframeBase* kf_before = this->keyframe(keyframe_index);
0184     if ( !kf_before )
0185         return {MidTransition::Invalid, value(), {}, {}};
0186 
0187     auto before_time = kf_before->time();
0188 
0189     if ( before_time >= time )
0190         return {MidTransition::SingleKeyframe, kf_before->value(), {}, kf_before->transition(),};
0191 
0192 
0193     const KeyframeBase* kf_after = this->keyframe(keyframe_index + 1);
0194 
0195     if ( !kf_after )
0196         return {MidTransition::SingleKeyframe, kf_before->value(), kf_before->transition(), {},};
0197 
0198     auto after_time = kf_after->time();
0199 
0200     if ( after_time <= time )
0201         return {
0202             MidTransition::SingleKeyframe,
0203             kf_after->value(),
0204             kf_before->transition(),
0205             kf_after->transition(),
0206         };
0207 
0208     qreal x = math::unlerp(before_time, after_time, time);
0209     return do_mid_transition(kf_before, kf_after, x, keyframe_index);
0210 }
0211 
0212 
0213 glaxnimate::model::AnimatableBase::MidTransition glaxnimate::model::AnimatableBase::do_mid_transition(
0214     const model::KeyframeBase* kf_before,
0215     const model::KeyframeBase* kf_after,
0216     qreal x,
0217     int index
0218 ) const
0219 {
0220     const auto& beftrans = kf_before->transition();
0221     if ( beftrans.hold() || (beftrans.before() == QPointF(0, 0) && beftrans.after() == QPointF(1,1)) )
0222         return {MidTransition::Middle, kf_before->value(), beftrans, beftrans};
0223 
0224     qreal t = beftrans.bezier_parameter(x);
0225 
0226     if ( t <= 0 )
0227     {
0228         KeyframeTransition from_previous = {{}, {1, 1}};
0229         if ( index > 0 )
0230             from_previous = keyframe(index-1)->transition();
0231 
0232         return {MidTransition::SingleKeyframe, kf_before->value(), from_previous, beftrans};
0233     }
0234     else if ( t >= 1 )
0235     {
0236         return {MidTransition::SingleKeyframe, kf_before->value(), beftrans, kf_after->transition(),};
0237     }
0238 
0239 
0240     model::AnimatableBase::MidTransition mt;
0241     mt.type = MidTransition::Middle;
0242     mt.value = do_mid_transition_value(kf_before, kf_after, x);
0243     std::tie(mt.from_previous, mt.to_next) = beftrans.split(x);
0244     return mt;
0245 }
0246 
0247 void glaxnimate::model::AnimatableBase::clear_keyframes_undoable(QVariant value)
0248 {
0249     if ( !value.isValid() || value.isNull() )
0250         value = this->value();
0251 
0252     object()->push_command(new command::RemoveAllKeyframes(this, std::move(value)));
0253 }
0254 
0255 void glaxnimate::model::AnimatableBase::add_smooth_keyframe_undoable(FrameTime time, const QVariant& val)
0256 {
0257     object()->push_command(
0258         new command::SetKeyframe(this, time, val.isNull() ? value() : val, true)
0259     );
0260 }
0261 
0262 void glaxnimate::model::detail::AnimatedPropertyPosition::split_segment(int index, qreal factor)
0263 {
0264     if ( keyframes_.size() < 2 )
0265         return;
0266 
0267     auto before = bezier();
0268     auto after = before;
0269     after.split_segment(index, factor);
0270 
0271     auto parent = std::make_unique<command::ReorderedUndoCommand>(i18n("Split Segment"));
0272 
0273     FrameTime time = 0;
0274     QVariant value;
0275 
0276     if ( index <= 0 && factor <= 0 )
0277     {
0278         time = keyframes_[0]->time();
0279         value = keyframes_[0]->value();
0280     }
0281     else if ( index >= int(keyframes_.size()) - 1 && factor >= 1 )
0282     {
0283         time = keyframes_.back()->time();
0284         value = keyframes_.back()->value();
0285     }
0286     else
0287     {
0288         auto kf_before = keyframes_[index].get();
0289         auto kf_after = keyframes_[index + 1].get();
0290 
0291         value = kf_before->lerp(*kf_after, factor);
0292 
0293         // Reverse length.at_ratio() to get the correct time at which the transition is equal to `value`
0294         math::bezier::Solver segment(kf_before->get(), kf_before->point().tan_out, kf_after->point().tan_in, kf_after->get());
0295         math::bezier::LengthData length(segment, 20);
0296         qreal time_factor = qFuzzyIsNull(length.length()) ? 0 : length.from_ratio(factor) / length.length();
0297         time = qRound(math::lerp(kf_before->time(), kf_after->time(), time_factor));
0298     }
0299 
0300     parent->add_command(
0301         std::make_unique<command::SetKeyframe>(this, time, value, true, true),
0302         0, 0
0303     );
0304 
0305     parent->add_command(
0306         std::make_unique<command::SetPositionBezier>(this, before, after, true),
0307         1, 1
0308     );
0309 
0310     object()->push_command(parent.release());
0311 }
0312 
0313 bool glaxnimate::model::detail::AnimatedPropertyPosition::set_bezier(math::bezier::Bezier bezier)
0314 {
0315     bezier.add_close_point();
0316 
0317     // TODO if sizes don't match, re-arrange keyframes based on
0318     // how far keyframes are in the bezier
0319     // eg: a point at 50% of the length will result in a keyframe
0320     // at time (keyframes[0].time + keyframes[-1].time) / 2
0321     if ( bezier.size() != int(keyframes_.size()) )
0322         return false;
0323 
0324     for ( int i = 0; i < bezier.size(); i++ )
0325     {
0326         keyframes_[i]->set_point(bezier[i]);
0327         on_keyframe_updated(keyframes_[i]->time(), i-1, i+1);
0328     }
0329 
0330     value_ = get_at_impl(time()).second;
0331     emitter(this->object(), value_);
0332     Q_EMIT bezier_set(bezier);
0333 
0334     return true;
0335 }
0336 
0337 
0338 void glaxnimate::model::detail::AnimatedPropertyPosition::remove_points(const std::set<int>& indices)
0339 {
0340     auto parent = std::make_unique<command::ReorderedUndoCommand>(i18n("Remove Nodes"));
0341 
0342     auto before = bezier();
0343     auto after = before.removed_points(indices);
0344 
0345     int order = 0;
0346     for ( int index : indices )
0347     {
0348         parent->add_command(std::make_unique<command::RemoveKeyframeIndex>(this, index), -order, order);
0349         ++order;
0350     }
0351 
0352     object()->push_command(parent.release());
0353 }
0354 
0355 glaxnimate::math::bezier::Bezier glaxnimate::model::detail::AnimatedPropertyPosition::bezier() const
0356 {
0357     math::bezier::Bezier bez;
0358     for ( const auto& kf : keyframes_ )
0359         bez.push_back(kf->point());
0360 
0361     return bez;
0362 }
0363 
0364 glaxnimate::model::detail::AnimatedPropertyPosition::keyframe_type*
0365     glaxnimate::model::detail::AnimatedPropertyPosition::set_keyframe(
0366         FrameTime time, const QVariant& val, SetKeyframeInfo* info, bool force_insert
0367 )
0368 {
0369     if ( val.userType() == QMetaType::QPointF )
0370         return detail::AnimatedProperty<QPointF>::set_keyframe(time, val.value<QPointF>(), info, force_insert);
0371 
0372     if ( auto v = detail::variant_cast<math::bezier::Point>(val) )
0373     {
0374         auto kf = detail::AnimatedProperty<QPointF>::set_keyframe(time, v->pos, info, force_insert);
0375         kf->set_point(*v);
0376         Q_EMIT bezier_set(bezier());
0377         return kf;
0378     }
0379 
0380     // We accept a bezier here so it can be used with SetMultipleAnimated
0381     if ( auto v = detail::variant_cast<math::bezier::Bezier>(val) )
0382     {
0383         set_bezier(*v);
0384         return nullptr;
0385     }
0386 
0387     return nullptr;
0388 }
0389 
0390 glaxnimate::model::detail::AnimatedPropertyPosition::keyframe_type*
0391     glaxnimate::model::detail::AnimatedPropertyPosition::set_keyframe(
0392         FrameTime time, reference value, SetKeyframeInfo* info, bool force_insert
0393 )
0394 {
0395     return detail::AnimatedProperty<QPointF>::set_keyframe(time, value, info, force_insert);
0396 }
0397 
0398 bool glaxnimate::model::detail::AnimatedPropertyPosition::set_value(const QVariant& val)
0399 {
0400     if ( auto v = detail::variant_cast<QPointF>(val) )
0401         return detail::AnimatedProperty<QPointF>::set(*v);
0402 
0403     if ( auto v = detail::variant_cast<math::bezier::Bezier>(val) )
0404         return set_bezier(*v);
0405 
0406     return false;
0407 }
0408 
0409 bool glaxnimate::model::detail::AnimatedPropertyPosition::valid_value(const QVariant& val) const
0410 {
0411     if ( detail::variant_cast<QPointF>(val) || detail::variant_cast<math::bezier::Bezier>(val) )
0412         return true;
0413     return false;
0414 }
0415 
0416 void glaxnimate::model::detail::AnimatedPropertyPosition::add_smooth_keyframe_undoable(FrameTime time, const QVariant& val)
0417 {
0418     auto parent = std::make_unique<command::ReorderedUndoCommand>(i18n("Add Keyframe"));
0419 
0420     auto value = val.isNull() ? QVariant(value_) : val;
0421 
0422     parent->add_command(std::make_unique<command::SetKeyframe>(this, time, value, true), 0, 0);
0423 
0424     int count = keyframes_.size();
0425 
0426     if ( value.userType() == QMetaType::QPointF && count >= 2 && keyframes_[0]->time() < time && keyframes_.back()->time() > time )
0427     {
0428         int index = keyframe_index(time);
0429         auto first = keyframe(index);
0430         const keyframe_type* second = keyframe(index+1);
0431 
0432         if ( !first->is_linear() || second->is_linear() )
0433         {
0434             double scaled_time = (time - first->time()) / (second->time() - first->time());
0435 
0436             auto factor = first->transition().lerp_factor(scaled_time);
0437             auto solver = first->bezier_solver(*second);
0438             math::bezier::LengthData len(solver, 20);
0439             auto t = len.at_ratio(factor).ratio;
0440             auto split = solver.split(t);
0441 
0442             auto before = bezier();
0443             auto after = before;
0444             after[index].tan_out = split.first[1];
0445             after[index+1].tan_in = split.second[2];
0446             math::bezier::Point p(split.first[3], split.first[2], split.second[1]);
0447             p.translate_to(value.value<QPointF>());
0448             after.insert_point(index+1, p);
0449 
0450             parent->add_command(std::make_unique<command::SetPositionBezier>(this, before, after, true), 1, 1);
0451         }
0452     }
0453 
0454     object()->document()->push_command(parent.release());
0455 }
0456 
0457 std::optional<QPointF> glaxnimate::model::detail::AnimatedPropertyPosition::derivative_at(glaxnimate::model::FrameTime time) const
0458 {
0459     int count = keyframe_count();
0460     if ( count < 2 )
0461         return {};
0462 
0463     int index_before = keyframe_index(time);
0464     const keyframe_type* kf_before = keyframe(index_before);
0465     const keyframe_type* kf_after = nullptr;
0466     qreal factor = 1;
0467 
0468     if ( index_before == count - 1 )
0469     {
0470         kf_after = kf_before;
0471         kf_before = keyframe(index_before - 1);
0472     }
0473     else
0474     {
0475         kf_after = keyframe(index_before + 1);
0476         factor = math::unlerp(kf_before->time(), kf_after->time(), time);
0477     }
0478 
0479     const math::bezier::Point& point_before = kf_before->point();
0480     const math::bezier::Point& point_after = kf_after->point();
0481     math::bezier::CubicBezierSolver solver(point_before.pos, point_before.tan_out, point_after.tan_in, point_after.pos);
0482     return QPointF(
0483         solver.derivative(factor, 0),
0484         solver.derivative(factor, 1)
0485     );
0486 }