From ae84067084350c5cef20e1c1da9ed05c84f57e6d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 30 Dec 2019 02:12:48 +1100 Subject: [PATCH] reworked NodeInput to accept more than one keyframe "track" To allow keyframing of each axis of a vector 2/3/4, without simply separating the inputs for each axis, the NodeInputs need to support more than one keyframe track with the ability to merge them into a single value when necessary. --- app/node/input.cpp | 157 +++++++++++++++++++++++++++++------------- app/node/input.h | 34 ++++++--- app/node/keyframe.cpp | 9 +-- app/node/keyframe.h | 22 ++++-- 4 files changed, 159 insertions(+), 63 deletions(-) diff --git a/app/node/input.cpp b/app/node/input.cpp index fcb32ed43..c3d1ee2b0 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -40,6 +40,19 @@ NodeInput::NodeInput(const QString& id, const DataType &type, const QVariant &de has_minimum_(false), has_maximum_(false) { + switch (data_type_) { + case kVec2: + keyframes_.resize(2); + break; + case kVec3: + keyframes_.resize(3); + break; + case kVec4: + keyframes_.resize(4); + break; + default: + keyframes_.resize(1); + } } bool NodeInput::IsArray() @@ -96,25 +109,56 @@ bool NodeInput::type_can_be_interpolated(NodeParam::DataType type) } QVariant NodeInput::get_value_at_time(const rational &time) const +{ + switch (data_type_) { + case kVec2: + { + QVariant x = get_value_at_time_for_track(time, 0); + QVariant y = get_value_at_time_for_track(time, 1); + return QVector2D(x.toFloat(), y.toFloat()); + } + case kVec3: + { + QVariant x = get_value_at_time_for_track(time, 0); + QVariant y = get_value_at_time_for_track(time, 1); + QVariant z = get_value_at_time_for_track(time, 2); + return QVector3D(x.toFloat(), y.toFloat(), z.toFloat()); + } + case kVec4: + { + QVariant x = get_value_at_time_for_track(time, 0); + QVariant y = get_value_at_time_for_track(time, 1); + QVariant z = get_value_at_time_for_track(time, 2); + QVariant w = get_value_at_time_for_track(time, 3); + return QVector4D(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()); + } + default: + return get_value_at_time_for_track(time, 0); + } +} + +QVariant NodeInput::get_value_at_time_for_track(const rational& time, int track) const { if (is_using_standard_value()) { return standard_value_; } - if (keyframes_.first()->time() >= time) { + const QList& key_track = keyframes_.at(track); + + if (key_track.first()->time() >= time) { // This time precedes any keyframe, so we just return the first value - return keyframes_.first()->value(); + return key_track.first()->value(); } - if (keyframes_.last()->time() <= time) { + if (key_track.last()->time() <= time) { // This time is after any keyframes so we return the last value - return keyframes_.last()->value(); + return key_track.last()->value(); } // If we're here, the time must be somewhere in between the keyframes - for (int i=0;itime() == time || !type_can_be_interpolated(data_type()) @@ -199,38 +243,51 @@ QVariant NodeInput::get_value_at_time(const rational &time) const return standard_value_; } -NodeKeyframePtr NodeInput::get_keyframe_at_time(const rational &time) const +QList NodeInput::get_keyframe_at_time(const rational &time) const { - if (is_using_standard_value()) { - return nullptr; + QList keys; + + if (!is_using_standard_value()) { + for (int i=0;itime() == time) { - return key; + return keys; +} + +NodeKeyframePtr NodeInput::get_keyframe_at_time_on_track(const rational &time, int track) const +{ + if (!is_using_standard_value()) { + foreach (NodeKeyframePtr key, keyframes_.at(track)) { + if (key->time() == time) { + return key; + } } } return nullptr; } -NodeKeyframePtr NodeInput::get_closest_keyframe_to_time(const rational &time) const +NodeKeyframePtr NodeInput::get_closest_keyframe_to_time(const rational &time, int track) const { if (is_using_standard_value()) { return nullptr; } - if (time <= keyframes_.first()->time()) { - return keyframes_.first(); + const QList& key_track = keyframes_.at(track); + + if (time <= key_track.first()->time()) { + return key_track.first(); } - if (time >= keyframes_.last()->time()) { - return keyframes_.last(); + if (time >= key_track.last()->time()) { + return key_track.last(); } - for (int i=1;itime() <= time && next_key->time() >= time) { // Return whichever is closer @@ -248,9 +305,9 @@ NodeKeyframePtr NodeInput::get_closest_keyframe_to_time(const rational &time) co return nullptr; } -NodeKeyframe::Type NodeInput::get_best_keyframe_type_for_time(const rational &time) const +NodeKeyframe::Type NodeInput::get_best_keyframe_type_for_time(const rational &time, int track) const { - NodeKeyframePtr closest_key = get_closest_keyframe_to_time(time); + NodeKeyframePtr closest_key = get_closest_keyframe_to_time(time, track); if (closest_key) { return closest_key->type(); @@ -288,7 +345,7 @@ void NodeInput::remove_keyframe(NodeKeyframePtr key) disconnect(key.get(), &NodeKeyframe::BezierControlInChanged, this, &NodeInput::KeyframeBezierInChanged); disconnect(key.get(), &NodeKeyframe::BezierControlOutChanged, this, &NodeInput::KeyframeBezierOutChanged); - keyframes_.removeOne(key); + keyframes_[key->track()].removeOne(key); emit KeyframeRemoved(key); emit_time_range(time_affected); @@ -301,11 +358,11 @@ void NodeInput::KeyframeTimeChanged() Q_ASSERT(keyframe_index > -1); - TimeRange original_range = get_range_around_index(keyframe_index); + TimeRange original_range = get_range_around_index(keyframe_index, key->track()); if (!(original_range.in() < key->time() && original_range.out() > key->time())) { // This keyframe needs resorting, store it and remove it from the list - NodeKeyframePtr key_shared_ptr = keyframes_.at(keyframe_index); + NodeKeyframePtr key_shared_ptr = keyframes_.at(key->track()).at(keyframe_index); keyframes_.removeAt(keyframe_index); @@ -313,7 +370,7 @@ void NodeInput::KeyframeTimeChanged() insert_keyframe_internal(key_shared_ptr); // Invalidate new area that the keyframe has been moved to - emit_time_range(get_range_around_index(FindIndexOfKeyframeFromRawPtr(key))); + emit_time_range(get_range_around_index(FindIndexOfKeyframeFromRawPtr(key), key->track())); } // Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it @@ -337,7 +394,7 @@ void NodeInput::KeyframeTypeChanged() } // Invalidate entire range - emit_time_range(get_range_around_index(keyframe_index)); + emit_time_range(get_range_around_index(keyframe_index, key->track())); } void NodeInput::KeyframeBezierInChanged() @@ -349,7 +406,7 @@ void NodeInput::KeyframeBezierInChanged() rational end = key->time(); if (keyframe_index > 0) { - start = keyframes_.at(keyframe_index - 1)->time(); + start = keyframes_.at(key->track()).at(keyframe_index - 1)->time(); } emit ValueChanged(start, end); @@ -364,7 +421,7 @@ void NodeInput::KeyframeBezierOutChanged() rational end = RATIONAL_MAX; if (keyframe_index < keyframes_.size() - 1) { - end = keyframes_.at(keyframe_index + 1)->time(); + end = keyframes_.at(key->track()).at(keyframe_index + 1)->time(); } emit ValueChanged(start, end); @@ -373,7 +430,7 @@ void NodeInput::KeyframeBezierOutChanged() int NodeInput::FindIndexOfKeyframeFromRawPtr(NodeKeyframe *raw_ptr) const { for (int i=0;itrack()).at(i).get() == raw_ptr) { return i; } } @@ -383,19 +440,21 @@ int NodeInput::FindIndexOfKeyframeFromRawPtr(NodeKeyframe *raw_ptr) const void NodeInput::insert_keyframe_internal(NodeKeyframePtr key) { - for (int i=0;i& key_track = keyframes_[key->track()]; + + for (int i=0;itime() != key->time()); if (compare->time() > key->time()) { - keyframes_.insert(i, key); + key_track.insert(i, key); return; } } - keyframes_.append(key); + key_track.append(key); } bool NodeInput::is_using_standard_value() const @@ -407,19 +466,19 @@ TimeRange NodeInput::get_range_affected_by_keyframe(NodeKeyframe *key) const { int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); - TimeRange range = get_range_around_index(keyframe_index); + TimeRange range = get_range_around_index(keyframe_index, key->track()); // If a previous key exists and it's a hold, we don't need to invalidate those frames if (keyframes().size() > 1 && keyframe_index > 0 - && keyframes_.at(keyframe_index - 1)->type() == NodeKeyframe::kHold) { + && keyframes_.at(key->track()).at(keyframe_index - 1)->type() == NodeKeyframe::kHold) { range.set_in(key->time()); } return range; } -TimeRange NodeInput::get_range_around_index(int index) const +TimeRange NodeInput::get_range_around_index(int index, int track) const { rational range_begin = RATIONAL_MIN; rational range_end = RATIONAL_MAX; @@ -427,11 +486,11 @@ TimeRange NodeInput::get_range_around_index(int index) const if (keyframes_.size() > 1) { if (index > 0) { // If this is not the first key, we'll need to limit it to the key just before - range_begin = keyframes_.at(index - 1)->time(); + range_begin = keyframes_.at(track).at(index - 1)->time(); } if (index < keyframes_.size() - 1) { // If this is not the last key, we'll need to limit it to the key just after - range_end = keyframes_.at(index + 1)->time(); + range_end = keyframes_.at(track).at(index + 1)->time(); } } @@ -456,9 +515,11 @@ bool NodeInput::has_keyframe_at_time(const rational &time) const } // Loop through keyframes to see if any match - foreach (NodeKeyframePtr key, keyframes_) { - if (key->time() == time) { - return true; + foreach (const QList& track, keyframes_) { + foreach (NodeKeyframePtr key, track) { + if (key->time() == time) { + return true; + } } } @@ -498,7 +559,7 @@ void NodeInput::set_standard_value(const QVariant &value) } } -const QList &NodeInput::keyframes() const +const QVector< QList > &NodeInput::keyframes() const { return keyframes_; } @@ -548,9 +609,11 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn dest->standard_value_ = source->standard_value_; // Copy keyframes - dest->keyframes_.clear(); - foreach (NodeKeyframePtr key, source->keyframes_) { - dest->keyframes_.append(key->copy()); + for (int i=0;ikeyframes_.size();i++) { + dest->keyframes_[i].clear(); + foreach (NodeKeyframePtr key, source->keyframes_.at(i)) { + dest->keyframes_[i].append(key->copy()); + } } // Copy keyframing state diff --git a/app/node/input.h b/app/node/input.h index f5c5b805a..999ae7dd8 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -80,29 +80,46 @@ public: /** * @brief Calculate what the stored value should be at a certain time + * + * If this is a multi-track data type (e.g. kVec2), this will automatically combine the result into a QVector2D. */ QVariant get_value_at_time(const rational& time) const; /** - * @brief Retrieve the keyframe object at a given time + * @brief Calculate the stored value for a specific track + * + * For most data types, there is only one track (e.g. `track == 0`), but multi-track data types like kVec2 will + * produce the X value on track 0 and the Y value on track 1. + */ + QVariant get_value_at_time_for_track(const rational& time, int track) const; + + /** + * @brief Retrieve a list of keyframe objects for all tracks at a given time + * + * List may be empty if this input is not keyframing or has no keyframes at this time. + */ + QList get_keyframe_at_time(const rational& time) const; + + /** + * @brief Retrieve the keyframe object at a given time for a given track * * @return * * The keyframe object at this time or nullptr if there isn't one or if is_keyframing() is false. */ - NodeKeyframePtr get_keyframe_at_time(const rational& time) const; + NodeKeyframePtr get_keyframe_at_time_on_track(const rational& time, int track) const; /** * @brief Gets the closest keyframe to a time * * If is_keyframing() is false or keyframes_ is empty, this will return nullptr. */ - NodeKeyframePtr get_closest_keyframe_to_time(const rational& time) const; + NodeKeyframePtr get_closest_keyframe_to_time(const rational& time, int track) const; /** * @brief A heuristic to determine what type a keyframe should be if it's inserted at a certain time (between keyframes) */ - NodeKeyframe::Type get_best_keyframe_type_for_time(const rational& time) const; + NodeKeyframe::Type get_best_keyframe_type_for_time(const rational& time, int track) const; /** * @brief Inserts a keyframe at the given time and returns a reference to it @@ -117,7 +134,8 @@ public: /** * @brief Return whether a keyframe exists at this time * - * If is_keyframing() is false, this will always return false. + * If is_keyframing() is false, this will always return false. This checks all tracks and will return true if *any* + * track has a keyframe. */ bool has_keyframe_at_time(const rational &time) const; @@ -149,7 +167,7 @@ public: /** * @brief Return list of keyframes in this parameter */ - const QList& keyframes() const; + const QVector > &keyframes() const; /** * @brief Set whether this input can be keyframed or not @@ -208,7 +226,7 @@ private: /** * @brief Gets a time range between the previous and next keyframes of index */ - TimeRange get_range_around_index(int index) const; + TimeRange get_range_around_index(int index, int track) const; /** * @brief Convenience function - equivalent to calling `emit ValueChanged(range.in(), range.out())` @@ -242,7 +260,7 @@ private: * * If keyframing is enabled, this data is used instead of standard_value. */ - QList keyframes_; + QVector< QList > keyframes_; /** * @brief Internal keyframing enabled setting diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 9f07c2085..fa8f1482c 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -20,18 +20,19 @@ #include "keyframe.h" -NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, const NodeKeyframe::Type &type) : +NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, const NodeKeyframe::Type &type, const int &track) : time_(time), value_(value), type_(type), bezier_control_in_(QPointF(-1.0, 0.0)), - bezier_control_out_(QPointF(1.0, 0.0)) + bezier_control_out_(QPointF(1.0, 0.0)), + track_(track) { } -NodeKeyframePtr NodeKeyframe::Create(const rational &time, const QVariant &value, const NodeKeyframe::Type &type) +NodeKeyframePtr NodeKeyframe::Create(const rational &time, const QVariant &value, const NodeKeyframe::Type &type, const int& track) { - return std::make_shared(time, value, type); + return std::make_shared(time, value, type, track); } NodeKeyframePtr NodeKeyframe::copy() const diff --git a/app/node/keyframe.h b/app/node/keyframe.h index 9a3d3a6de..f86dc9ab4 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -53,14 +53,14 @@ public: kOutHandle }; + static const Type kDefaultType = kLinear; + /** * @brief NodeKeyframe Constructor */ - NodeKeyframe(const rational& time, const QVariant& value, const Type& type); + NodeKeyframe(const rational& time, const QVariant& value, const Type& type, const int& track = 0); - static const Type kDefaultType = kLinear; - - static NodeKeyframePtr Create(const rational& time, const QVariant& value, const Type& type); + static NodeKeyframePtr Create(const rational& time, const QVariant& value, const Type& type, const int &track = 0); NodeKeyframePtr copy() const; @@ -100,6 +100,17 @@ public: const QPointF& bezier_control(BezierType type) const; void set_bezier_control(BezierType type, const QPointF& control); + /** + * @brief The track that this keyframe belongs to + * + * For the majority of keyfreames, this will be 0, but for some types, such as kVec2, this will be 0 for X keyframes + * and 1 for Y keyframes, etc. + */ + const int& track() const; + + /** + * @brief Convenience function for getting the opposite handle type (e.g. kInHandle <-> kOutHandle) + */ static BezierType get_opposing_bezier_type(BezierType type); signals: @@ -138,6 +149,9 @@ private: QPointF bezier_control_in_; QPointF bezier_control_out_; + + int track_; + }; Q_DECLARE_METATYPE(NodeKeyframe::Type)