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 }