From 245aa44efc4016721f38f72c4cc56b64aa304ffa Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 30 Dec 2019 04:44:13 +1100 Subject: [PATCH] implemented support for multilayered keyframes This adds the ability to keyframe more than one value per input (e.g. a vec2, vec3, etc.) so that you can animate, for example, an X axis separately from a Y axis. --- app/node/input.cpp | 439 ++++++++++++------ app/node/input.h | 56 ++- app/node/keyframe.cpp | 7 +- app/node/keyframe.h | 4 +- app/widget/curvewidget/curvewidget.cpp | 6 +- .../nodeparamview/nodeparamviewitem.cpp | 22 +- .../nodeparamviewkeyframecontrol.cpp | 87 ++-- .../nodeparamview/nodeparamviewundo.cpp | 10 +- app/widget/nodeparamview/nodeparamviewundo.h | 6 +- .../nodeparamviewwidgetbridge.cpp | 56 +-- .../nodeparamview/nodeparamviewwidgetbridge.h | 2 +- 11 files changed, 444 insertions(+), 251 deletions(-) diff --git a/app/node/input.cpp b/app/node/input.cpp index c3d1ee2b0..b59960c05 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -34,24 +34,35 @@ NodeInput::NodeInput(const QString& id, const DataType &type, const QVariant &de NodeParam(id), data_type_(type), keyframable_(true), - standard_value_(default_value), keyframing_(false), dependent_(true), has_minimum_(false), has_maximum_(false) { + int track_size; + switch (data_type_) { case kVec2: - keyframes_.resize(2); + track_size = 2; break; case kVec3: - keyframes_.resize(3); + track_size = 3; break; case kVec4: - keyframes_.resize(4); + track_size = 4; break; default: - keyframes_.resize(1); + track_size = 1; + } + + keyframe_tracks_.resize(track_size); + + if (!default_value.isNull()) { + standard_value_ = split_normal_value_into_track_values(default_value); + + Q_ASSERT(standard_value_.size() == track_size); + } else { + standard_value_.resize(track_size); } } @@ -110,145 +121,120 @@ 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); + return combine_track_values_into_normal_value(get_split_values_at_time(time)); +} + +QVector NodeInput::get_split_values_at_time(const rational &time) const +{ + QVector vals; + + for (int i=0;i& key_track = keyframes_.at(track); + if (key_track.first()->time() >= time) { + // This time precedes any keyframe, so we just return the first value + return key_track.first()->value(); + } - if (key_track.first()->time() >= time) { - // This time precedes any keyframe, so we just return the first value - return key_track.first()->value(); - } + if (key_track.last()->time() <= time) { + // This time is after any keyframes so we return the last value + return key_track.last()->value(); + } - if (key_track.last()->time() <= time) { - // This time is after any keyframes so we return the 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()) + || (before->time() < time && before->type() == NodeKeyframe::kHold)) { - if (before->time() == time - || !type_can_be_interpolated(data_type()) - || (before->time() < time && before->type() == NodeKeyframe::kHold)) { + // Time == keyframe time, so value is precise + return before->value(); - // Time == keyframe time, so value is precise - return before->value(); + } else if (after->time() == time) { - } else if (before->time() < time && after->time() > time) { - // We must interpolate between these keyframes + // Time == keyframe time, so value is precise + return after->value(); - if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { - // Perform a cubic bezier with two control points + } else if (before->time() < time && after->time() > time) { + // We must interpolate between these keyframes - double t = Bezier::CubicXtoT(time.toDouble(), - before->time().toDouble(), - before->time().toDouble() + before->bezier_control_out().x(), - after->time().toDouble() + after->bezier_control_in().x(), - after->time().toDouble()); + if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { + // Perform a cubic bezier with two control points - double y = Bezier::CubicTtoY(before->value().toDouble(), - before->value().toDouble() + before->bezier_control_out().y(), - after->value().toDouble() + after->bezier_control_in().y(), - after->value().toDouble(), - t); + double t = Bezier::CubicXtoT(time.toDouble(), + before->time().toDouble(), + before->time().toDouble() + before->bezier_control_out().x(), + after->time().toDouble() + after->bezier_control_in().x(), + after->time().toDouble()); - return y; + double y = Bezier::CubicTtoY(before->value().toDouble(), + before->value().toDouble() + before->bezier_control_out().y(), + after->value().toDouble() + after->bezier_control_in().y(), + after->value().toDouble(), + t); - } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { - // Perform a quadratic bezier with only one control point + return y; - QPointF control_point; - double control_point_time; - double control_point_value; + } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { + // Perform a quadratic bezier with only one control point + + QPointF control_point; + double control_point_time; + double control_point_value; + + if (before->type() == NodeKeyframe::kBezier) { + control_point = before->bezier_control_out(); + control_point_time = before->time().toDouble() + control_point.x(); + control_point_value = before->value().toDouble() + control_point.y(); + } else { + control_point = after->bezier_control_in(); + control_point_time = after->time().toDouble() + control_point.x(); + control_point_value = after->value().toDouble() + control_point.y(); + } + + // Generate T from time values - used to determine bezier progress + double t = Bezier::QuadraticXtoT(time.toDouble(), before->time().toDouble(), control_point_time, after->time().toDouble()); + + // Generate value using T + double y = Bezier::QuadraticTtoY(before->value().toDouble(), control_point_value, after->value().toDouble(), t); + + return y; - if (before->type() == NodeKeyframe::kBezier) { - control_point = before->bezier_control_out(); - control_point_time = before->time().toDouble() + control_point.x(); - control_point_value = before->value().toDouble() + control_point.y(); } else { - control_point = after->bezier_control_in(); - control_point_time = after->time().toDouble() + control_point.x(); - control_point_value = after->value().toDouble() + control_point.y(); + // To have arrived here, the keyframes must both be linear + qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble()); + + return lerp(before->value().toDouble(), after->value().toDouble(), period_progress); } - - // Generate T from time values - used to determine bezier progress - double t = Bezier::QuadraticXtoT(time.toDouble(), before->time().toDouble(), control_point_time, after->time().toDouble()); - - // Generate value using T - double y = Bezier::QuadraticTtoY(before->value().toDouble(), control_point_value, after->value().toDouble(), t); - - return y; - - } else { - // To have arrived here, the keyframes must both be linear - qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble()); - - QVariant interpolated_value; - - switch (data_type()) { - case kFloat: - interpolated_value = lerp(before->value().toDouble(), after->value().toDouble(), period_progress); - break; - case kVec2: - interpolated_value = lerp(before->value().value(), after->value().value(), static_cast(period_progress)); - break; - case kVec3: - interpolated_value = lerp(before->value().value(), after->value().value(), static_cast(period_progress)); - break; - case kVec4: - interpolated_value = lerp(before->value().value(), after->value().value(), static_cast(period_progress)); - break; - default: - interpolated_value = before->value(); - } - - return interpolated_value; } } } - return standard_value_; + return standard_value_.at(track); } QList NodeInput::get_keyframe_at_time(const rational &time) const { QList keys; - if (!is_using_standard_value()) { - for (int i=0;i NodeInput::get_keyframe_at_time(const rational &time) con 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 (!is_using_standard_value(track)) { + foreach (NodeKeyframePtr key, keyframe_tracks_.at(track)) { if (key->time() == time) { return key; } @@ -269,13 +255,13 @@ NodeKeyframePtr NodeInput::get_keyframe_at_time_on_track(const rational &time, i return nullptr; } -NodeKeyframePtr NodeInput::get_closest_keyframe_to_time(const rational &time, int track) const +NodeKeyframePtr NodeInput::get_closest_keyframe_to_time_on_track(const rational &time, int track) const { - if (is_using_standard_value()) { + if (is_using_standard_value(track)) { return nullptr; } - const QList& key_track = keyframes_.at(track); + const KeyframeTrack& key_track = keyframe_tracks_.at(track); if (time <= key_track.first()->time()) { return key_track.first(); @@ -305,9 +291,45 @@ NodeKeyframePtr NodeInput::get_closest_keyframe_to_time(const rational &time, in return nullptr; } +NodeKeyframePtr NodeInput::get_closest_keyframe_before_time(const rational &time) const +{ + NodeKeyframePtr key = nullptr; + + foreach (const KeyframeTrack& track, keyframe_tracks_) { + foreach (NodeKeyframePtr k, track) { + if (k->time() >= time) { + break; + } else if (!key || k->time() > key->time()) { + key = k; + } + } + } + + return key; +} + +NodeKeyframePtr NodeInput::get_closest_keyframe_after_time(const rational &time) const +{ + NodeKeyframePtr key = nullptr; + + foreach (const KeyframeTrack& track, keyframe_tracks_) { + for (int i=track.size()-1;i>=0;i--) { + NodeKeyframePtr k = track.at(i); + + if (k->time() <= time) { + break; + } else if (!key || k->time() < key->time()) { + key = k; + } + } + } + + return key; +} + NodeKeyframe::Type NodeInput::get_best_keyframe_type_for_time(const rational &time, int track) const { - NodeKeyframePtr closest_key = get_closest_keyframe_to_time(time, track); + NodeKeyframePtr closest_key = get_closest_keyframe_to_time_on_track(time, track); if (closest_key) { return closest_key->type(); @@ -316,9 +338,50 @@ NodeKeyframe::Type NodeInput::get_best_keyframe_type_for_time(const rational &ti return NodeKeyframe::kDefaultType; } +int NodeInput::get_number_of_keyframe_tracks() const +{ + return keyframe_tracks_.size(); +} + +NodeKeyframePtr NodeInput::get_earliest_keyframe() const +{ + NodeKeyframePtr earliest = nullptr; + + foreach (const KeyframeTrack& track, keyframe_tracks_) { + if (!track.isEmpty()) { + NodeKeyframePtr earliest_in_track = track.first(); + + if (!earliest + || earliest_in_track->time() < earliest->time()) { + earliest = earliest_in_track; + } + } + } + + return earliest; +} + +NodeKeyframePtr NodeInput::get_latest_keyframe() const +{ + NodeKeyframePtr latest = nullptr; + + foreach (const KeyframeTrack& track, keyframe_tracks_) { + if (!track.isEmpty()) { + NodeKeyframePtr latest_in_track = track.last(); + + if (!latest + || latest_in_track->time() > latest->time()) { + latest = latest_in_track; + } + } + } + + return latest; +} + void NodeInput::insert_keyframe(NodeKeyframePtr key) { - Q_ASSERT(is_keyframable() || keyframes_.isEmpty()); + Q_ASSERT(is_keyframable()); insert_keyframe_internal(key); @@ -335,7 +398,7 @@ void NodeInput::insert_keyframe(NodeKeyframePtr key) void NodeInput::remove_keyframe(NodeKeyframePtr key) { - Q_ASSERT(is_keyframable() && keyframes_.size() > 1); + Q_ASSERT(is_keyframable()); TimeRange time_affected = get_range_affected_by_keyframe(key.get()); @@ -345,7 +408,7 @@ void NodeInput::remove_keyframe(NodeKeyframePtr key) disconnect(key.get(), &NodeKeyframe::BezierControlInChanged, this, &NodeInput::KeyframeBezierInChanged); disconnect(key.get(), &NodeKeyframe::BezierControlOutChanged, this, &NodeInput::KeyframeBezierOutChanged); - keyframes_[key->track()].removeOne(key); + keyframe_tracks_[key->track()].removeOne(key); emit KeyframeRemoved(key); emit_time_range(time_affected); @@ -362,9 +425,9 @@ void NodeInput::KeyframeTimeChanged() 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(key->track()).at(keyframe_index); + NodeKeyframePtr key_shared_ptr = keyframe_tracks_.at(key->track()).at(keyframe_index); - keyframes_.removeAt(keyframe_index); + keyframe_tracks_.removeAt(keyframe_index); // Automatically insertion sort insert_keyframe_internal(key_shared_ptr); @@ -388,7 +451,7 @@ void NodeInput::KeyframeTypeChanged() NodeKeyframe* key = static_cast(sender()); int keyframe_index = FindIndexOfKeyframeFromRawPtr(key); - if (keyframes_.size() <= 1) { + if (keyframe_tracks_.at(key->track()).size() == 1) { // If there are no other frames, the interpolation won't do anything return; } @@ -406,7 +469,7 @@ void NodeInput::KeyframeBezierInChanged() rational end = key->time(); if (keyframe_index > 0) { - start = keyframes_.at(key->track()).at(keyframe_index - 1)->time(); + start = keyframe_tracks_.at(key->track()).at(keyframe_index - 1)->time(); } emit ValueChanged(start, end); @@ -420,8 +483,8 @@ void NodeInput::KeyframeBezierOutChanged() rational start = key->time(); rational end = RATIONAL_MAX; - if (keyframe_index < keyframes_.size() - 1) { - end = keyframes_.at(key->track()).at(keyframe_index + 1)->time(); + if (keyframe_index < keyframe_tracks_.at(key->track()).size() - 1) { + end = keyframe_tracks_.at(key->track()).at(keyframe_index + 1)->time(); } emit ValueChanged(start, end); @@ -429,8 +492,10 @@ void NodeInput::KeyframeBezierOutChanged() int NodeInput::FindIndexOfKeyframeFromRawPtr(NodeKeyframe *raw_ptr) const { - for (int i=0;itrack()).at(i).get() == raw_ptr) { + const KeyframeTrack& track = keyframe_tracks_.at(raw_ptr->track()); + + for (int i=0;i& key_track = keyframes_[key->track()]; + KeyframeTrack& key_track = keyframe_tracks_[key->track()]; for (int i=0;itrack()); + const KeyframeTrack& key_track = keyframe_tracks_.at(key->track()); + // If a previous key exists and it's a hold, we don't need to invalidate those frames - if (keyframes().size() > 1 + if (key_track.size() > 1 && keyframe_index > 0 - && keyframes_.at(key->track()).at(keyframe_index - 1)->type() == NodeKeyframe::kHold) { + && key_track.at(keyframe_index - 1)->type() == NodeKeyframe::kHold) { range.set_in(key->time()); } @@ -483,14 +550,16 @@ TimeRange NodeInput::get_range_around_index(int index, int track) const rational range_begin = RATIONAL_MIN; rational range_end = RATIONAL_MAX; - if (keyframes_.size() > 1) { + const KeyframeTrack& key_track = keyframe_tracks_.at(track); + + if (key_track.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(track).at(index - 1)->time(); + range_begin = key_track.at(index - 1)->time(); } - if (index < keyframes_.size() - 1) { + if (index < key_track.size() - 1) { // If this is not the last key, we'll need to limit it to the key just after - range_end = keyframes_.at(track).at(index + 1)->time(); + range_end = key_track.at(index + 1)->time(); } } @@ -509,13 +578,12 @@ void NodeInput::emit_range_affected_by_keyframe(NodeKeyframe *key) bool NodeInput::has_keyframe_at_time(const rational &time) const { - // If we aren't keyframing, there definitely isn't a keyframe at a given time - if (is_using_standard_value()) { + if (!is_keyframing()) { return false; } // Loop through keyframes to see if any match - foreach (const QList& track, keyframes_) { + foreach (const KeyframeTrack& track, keyframe_tracks_) { foreach (NodeKeyframePtr key, track) { if (key->time() == time) { return true; @@ -544,24 +612,29 @@ bool NodeInput::is_keyframable() const return keyframable_; } -const QVariant &NodeInput::get_standard_value() const +QVariant NodeInput::get_standard_value() const +{ + return combine_track_values_into_normal_value(standard_value_); +} + +const QVector &NodeInput::get_split_standard_value() const { return standard_value_; } -void NodeInput::set_standard_value(const QVariant &value) +void NodeInput::set_standard_value(const QVariant &value, int track) { - standard_value_ = value; + standard_value_.replace(track, value); - if (is_using_standard_value()) { + if (is_using_standard_value(track)) { // If this standard value is being used, we need to send a value changed signal emit ValueChanged(RATIONAL_MIN, RATIONAL_MAX); } } -const QVector< QList > &NodeInput::keyframes() const +const QVector &NodeInput::keyframe_tracks() const { - return keyframes_; + return keyframe_tracks_; } void NodeInput::set_is_keyframable(bool k) @@ -609,10 +682,10 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn dest->standard_value_ = source->standard_value_; // Copy keyframes - for (int i=0;ikeyframes_.size();i++) { - dest->keyframes_[i].clear(); - foreach (NodeKeyframePtr key, source->keyframes_.at(i)) { - dest->keyframes_[i].append(key->copy()); + for (int i=0;ikeyframe_tracks_.size();i++) { + dest->keyframe_tracks_[i].clear(); + foreach (NodeKeyframePtr key, source->keyframe_tracks_.at(i)) { + dest->keyframe_tracks_[i].append(key->copy()); } } @@ -638,3 +711,65 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn emit dest->ValueChanged(RATIONAL_MIN, RATIONAL_MAX); } + +QVector NodeInput::split_normal_value_into_track_values(const QVariant &value) const +{ + QVector vals(get_number_of_keyframe_tracks()); + + switch (data_type_) { + case kVec2: + { + QVector2D vec = value.value(); + vals.replace(0, vec.x()); + vals.replace(1, vec.y()); + break; + } + case kVec3: + { + QVector3D vec = value.value(); + vals.replace(0, vec.x()); + vals.replace(1, vec.y()); + vals.replace(2, vec.z()); + break; + } + case kVec4: + { + QVector4D vec = value.value(); + vals.replace(0, vec.x()); + vals.replace(1, vec.y()); + vals.replace(2, vec.z()); + vals.replace(3, vec.w()); + break; + } + default: + vals.replace(0, value); + } + + return vals; +} + +QVariant NodeInput::combine_track_values_into_normal_value(const QVector &split) const +{ + switch (data_type_) { + case kVec2: + { + return QVector2D(split.at(0).toFloat(), + split.at(1).toFloat()); + } + case kVec3: + { + return QVector3D(split.at(0).toFloat(), + split.at(1).toFloat(), + split.at(2).toFloat()); + } + case kVec4: + { + return QVector4D(split.at(0).toFloat(), + split.at(1).toFloat(), + split.at(2).toFloat(), + split.at(3).toFloat()); + } + default: + return split.first(); + } +} diff --git a/app/node/input.h b/app/node/input.h index 999ae7dd8..aae120b9a 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -32,6 +32,8 @@ class NodeInput : public NodeParam { Q_OBJECT public: + using KeyframeTrack = QList; + /** * @brief NodeInput Constructor * @@ -85,6 +87,8 @@ public: */ QVariant get_value_at_time(const rational& time) const; + QVector get_split_values_at_time(const rational& time) const; + /** * @brief Calculate the stored value for a specific track * @@ -114,13 +118,42 @@ public: * * If is_keyframing() is false or keyframes_ is empty, this will return nullptr. */ - NodeKeyframePtr get_closest_keyframe_to_time(const rational& time, int track) const; + NodeKeyframePtr get_closest_keyframe_to_time_on_track(const rational& time, int track) const; + + /** + * @brief Get closest keyframe that's before the time on any track + * + * If no keyframe is before this time, returns nullptr. + */ + NodeKeyframePtr get_closest_keyframe_before_time(const rational& time) const; + + /** + * @brief Get closest keyframe that's before the time on any track + * + * If no keyframe is before this time, returns nullptr. + */ + NodeKeyframePtr get_closest_keyframe_after_time(const rational& time) 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, int track) const; + /** + * @brief Retrieve the number of + */ + int get_number_of_keyframe_tracks() const; + + /** + * @brief Gets the earliest keyframe on any track + */ + NodeKeyframePtr get_earliest_keyframe() const; + + /** + * @brief Gets the latest keyframe on any track + */ + NodeKeyframePtr get_latest_keyframe() const; + /** * @brief Inserts a keyframe at the given time and returns a reference to it */ @@ -157,17 +190,22 @@ public: /** * @brief Get non-keyframed value */ - const QVariant& get_standard_value() const; + QVariant get_standard_value() const; + + /** + * @brief Get non-keyframed value split into components (the way it's stored) + */ + const QVector& get_split_standard_value() const; /** * @brief Set non-keyframed value */ - void set_standard_value(const QVariant& value); + void set_standard_value(const QVariant& value, int track = 0); /** * @brief Return list of keyframes in this parameter */ - const QVector > &keyframes() const; + const QVector &keyframe_tracks() const; /** * @brief Set whether this input can be keyframed or not @@ -187,6 +225,10 @@ public: */ static void CopyValues(NodeInput* source, NodeInput* dest, bool include_connections = true, bool lock_connections = true); + QVector split_normal_value_into_track_values(const QVariant &value) const; + + QVariant combine_track_values_into_normal_value(const QVector& split) const; + signals: void ValueChanged(const rational& start, const rational& end); @@ -216,7 +258,7 @@ private: /** * @brief Return whether the standard value should be used over keyframe data */ - bool is_using_standard_value() const; + bool is_using_standard_value(int track) const; /** * @brief Intelligently determine how what time range is affected by a keyframe @@ -253,14 +295,14 @@ private: /** * @brief Non-keyframed value */ - QVariant standard_value_; + QVector standard_value_; /** * @brief Internal keyframe array * * If keyframing is enabled, this data is used instead of standard_value. */ - QVector< QList > keyframes_; + QVector< QList > keyframe_tracks_; /** * @brief Internal keyframing enabled setting diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index fa8f1482c..7223ddbf4 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -37,7 +37,7 @@ NodeKeyframePtr NodeKeyframe::Create(const rational &time, const QVariant &value NodeKeyframePtr NodeKeyframe::copy() const { - NodeKeyframePtr copy = std::make_shared(time_, value_, type_); + NodeKeyframePtr copy = std::make_shared(time_, value_, type_, track_); copy->bezier_control_in_ = bezier_control_in_; copy->bezier_control_out_ = bezier_control_out_; return copy; @@ -116,6 +116,11 @@ void NodeKeyframe::set_bezier_control(NodeKeyframe::BezierType type, const QPoin } } +const int &NodeKeyframe::track() const +{ + return track_; +} + NodeKeyframe::BezierType NodeKeyframe::get_opposing_bezier_type(NodeKeyframe::BezierType type) { if (type == kInHandle) { diff --git a/app/node/keyframe.h b/app/node/keyframe.h index f86dc9ab4..a3aa59827 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -58,9 +58,9 @@ public: /** * @brief NodeKeyframe Constructor */ - NodeKeyframe(const rational& time, const QVariant& value, const Type& type, const int& track = 0); + NodeKeyframe(const rational& time, const QVariant& value, const Type& type, const int& track); - static NodeKeyframePtr Create(const rational& time, const QVariant& value, const Type& type, const int &track = 0); + static NodeKeyframePtr Create(const rational& time, const QVariant& value, const Type& type, const int &track); NodeKeyframePtr copy() const; diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index c71c9fe6e..df1e7ec39 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -114,8 +114,10 @@ void CurveWidget::SetInput(NodeInput *input) connect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe); connect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe); - foreach (NodeKeyframePtr key, input_->keyframes()) { - view_->AddKeyframe(key); + foreach (const NodeInput::KeyframeTrack& track, input_->keyframe_tracks()) { + foreach (NodeKeyframePtr key, track) { + view_->AddKeyframe(key); + } } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index be61ffc41..781cd201c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -93,8 +93,10 @@ void NodeParamViewItem::SignalAllKeyframes() if (param->type() == NodeParam::kInput) { NodeInput* input = static_cast(param); - foreach (NodeKeyframePtr key, input->keyframes()) { - InputAddedKeyframeInternal(input, key); + foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) { + foreach (NodeKeyframePtr key, track) { + InputAddedKeyframeInternal(input, key); + } } } } @@ -220,13 +222,15 @@ void NodeParamViewItem::InputKeyframeEnableChanged(bool e) { NodeInput* input = static_cast(sender()); - foreach (NodeKeyframePtr key, input->keyframes()) { - if (e) { - // Add a keyframe item for each keyframe - InputAddedKeyframeInternal(input, key); - } else { - // Remove each keyframe item - emit KeyframeRemoved(key); + foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) { + foreach (NodeKeyframePtr key, track) { + if (e) { + // Add a keyframe item for each keyframe + InputAddedKeyframeInternal(input, key); + } else { + // Remove each keyframe item + emit KeyframeRemoved(key); + } } } } diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index 79331f851..ca701d908 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -111,24 +111,32 @@ void NodeParamViewKeyframeControl::ShowButtonsFromKeyframeEnable(bool e) void NodeParamViewKeyframeControl::ToggleKeyframe(bool e) { - NodeKeyframePtr key = input_->get_keyframe_at_time(time_); + QList keys = input_->get_keyframe_at_time(time_); QUndoCommand* command = new QUndoCommand(); - if (e && !key) { - // Add a keyframe here - key = NodeKeyframe::Create(time_, - input_->get_value_at_time(time_), - input_->get_best_keyframe_type_for_time(time_)); + if (e && keys.isEmpty()) { + // Add a keyframe here (one for each track) + for (int i=0;iget_number_of_keyframe_tracks();i++) { + NodeKeyframePtr key = NodeKeyframe::Create(time_, + input_->get_value_at_time_for_track(time_, i), + input_->get_best_keyframe_type_for_time(time_, i), + i); - new NodeParamInsertKeyframeCommand(input_, key, command); - } else if (!e && key) { - // Remove a keyframe here - new NodeParamRemoveKeyframeCommand(input_, key, command); + new NodeParamInsertKeyframeCommand(input_, key, command); + } + } else if (!e && !keys.isEmpty()) { + // Remove all keyframes at this time + foreach (NodeKeyframePtr key, keys) { + new NodeParamRemoveKeyframeCommand(input_, key, command); - // If this was the last keyframe, we'll set the standard value to the value at this time too - if (input_->keyframes().size() == 1) { - new NodeParamSetStandardValueCommand(input_, key->value(), command); + if (input_->keyframe_tracks().at(key->track()).size() == 1) { + // If this was the last keyframe on this track, set the standard value to the value at this time too + new NodeParamSetStandardValueCommand(input_, + key->track(), + input_->get_value_at_time_for_track(time_, key->track()), + command); + } } } @@ -141,34 +149,29 @@ void NodeParamViewKeyframeControl::UpdateState() return; } - prev_key_btn_->setEnabled(!input_->keyframes().isEmpty() && time_ > input_->keyframes().first()->time()); - next_key_btn_->setEnabled(!input_->keyframes().isEmpty() && time_ < input_->keyframes().last()->time()); + NodeKeyframePtr earliest_key = input_->get_earliest_keyframe(); + NodeKeyframePtr latest_key = input_->get_latest_keyframe(); + + prev_key_btn_->setEnabled(earliest_key && time_ > earliest_key->time()); + next_key_btn_->setEnabled(latest_key && time_ < latest_key->time()); toggle_key_btn_->setChecked(input_->has_keyframe_at_time(time_)); } void NodeParamViewKeyframeControl::GoToPreviousKey() { - for (int i=input_->keyframes().size()-1;i>=0;i--) { - // Find closest keyframe that is before this time - const rational& this_key_time = input_->keyframes().at(i)->time(); + NodeKeyframePtr previous_key = input_->get_closest_keyframe_before_time(time_); - if (this_key_time < time_) { - emit RequestSetTime(this_key_time); - break; - } + if (previous_key) { + emit RequestSetTime(previous_key->time()); } } void NodeParamViewKeyframeControl::GoToNextKey() { - for (int i=0;ikeyframes().size();i++) { - // Find closest keyframe that is before this time - const rational& this_key_time = input_->keyframes().at(i)->time(); + NodeKeyframePtr next_key = input_->get_closest_keyframe_after_time(time_); - if (this_key_time > time_) { - emit RequestSetTime(this_key_time); - break; - } + if (next_key) { + emit RequestSetTime(next_key->time()); } } @@ -185,9 +188,17 @@ void NodeParamViewKeyframeControl::KeyframeEnableChanged(bool e) // Enable keyframing new NodeParamSetKeyframingCommand(input_, true, command); - // NodeInputs already have one keyframe by default, we move it to the current time here - NodeKeyframePtr key = NodeKeyframe::Create(time_, input_->get_standard_value(), NodeKeyframe::kDefaultType); - new NodeParamInsertKeyframeCommand(input_, key, command); + // Create one keyframe across all tracks here + QVector key_vals = input_->get_split_standard_value(); + + for (int i=0;iget_value_at_time(time_); + QVector stored_vals = input_->get_split_values_at_time(time_); // Delete all keyframes - for (int i=input_->keyframes().size()-1;i>=0;i--) { - new NodeParamRemoveKeyframeCommand(input_, input_->keyframes().at(i), command); + foreach (const NodeInput::KeyframeTrack& track, input_->keyframe_tracks()) { + for (int i=track.size()-1;i>=0;i--) { + new NodeParamRemoveKeyframeCommand(input_, track.at(i), command); + } } // Update standard value - new NodeParamSetStandardValueCommand(input_, stored_val, command); + for (int i=0;iset_time(old_time_); } -NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *input, const QVariant &value, QUndoCommand *parent) : +NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *input, int track, const QVariant &value, QUndoCommand *parent) : QUndoCommand(parent), input_(input), + track_(track), old_value_(input_->get_standard_value()), new_value_(value) { } -NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *input, const QVariant &new_value, const QVariant &old_value, QUndoCommand *parent) : +NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *input, int track, const QVariant &new_value, const QVariant &old_value, QUndoCommand *parent) : QUndoCommand(parent), input_(input), + track_(track), old_value_(old_value), new_value_(new_value) { @@ -135,10 +137,10 @@ NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *in void NodeParamSetStandardValueCommand::redo() { - input_->set_standard_value(new_value_); + input_->set_standard_value(new_value_, track_); } void NodeParamSetStandardValueCommand::undo() { - input_->set_standard_value(old_value_); + input_->set_standard_value(old_value_, track_); } diff --git a/app/widget/nodeparamview/nodeparamviewundo.h b/app/widget/nodeparamview/nodeparamviewundo.h index 2fb41359f..b6f7a9f7f 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.h +++ b/app/widget/nodeparamview/nodeparamviewundo.h @@ -82,17 +82,19 @@ private: class NodeParamSetStandardValueCommand : public QUndoCommand { public: - NodeParamSetStandardValueCommand(NodeInput* input, const QVariant& value, QUndoCommand* parent = nullptr); - NodeParamSetStandardValueCommand(NodeInput* input, const QVariant& new_value, const QVariant& old_value, QUndoCommand* parent = nullptr); + NodeParamSetStandardValueCommand(NodeInput* input, int track, const QVariant& value, QUndoCommand* parent = nullptr); + NodeParamSetStandardValueCommand(NodeInput* input, int track, const QVariant& new_value, const QVariant& old_value, QUndoCommand* parent = nullptr); virtual void redo() override; virtual void undo() override; private: NodeInput* input_; + int track_; QVariant old_value_; QVariant new_value_; + }; #endif // NODEPARAMVIEWUNDO_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index c563f773a..223dd05f4 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -251,12 +251,12 @@ void NodeParamViewWidgetBridge::CreateWidgets() } } -void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value) +void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track) { QUndoCommand* command = new QUndoCommand(); if (input_->is_keyframing()) { - NodeKeyframePtr existing_key = input_->get_keyframe_at_time(time_); + NodeKeyframePtr existing_key = input_->get_keyframe_at_time_on_track(time_, track); if (existing_key) { new NodeParamSetKeyframeValueCommand(existing_key, value, command); @@ -264,12 +264,13 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value) // No existing key, create a new one NodeKeyframePtr new_key = NodeKeyframe::Create(time_, value, - input_->get_best_keyframe_type_for_time(time_)); + input_->get_best_keyframe_type_for_time(time_, track), + track); new NodeParamInsertKeyframeCommand(input_, new_key, command); } } else { - new NodeParamSetStandardValueCommand(input_, value, command); + new NodeParamSetStandardValueCommand(input_, track, value, command); } Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -277,6 +278,8 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value) void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant &value) { + int slider_track = widgets_.indexOf(slider); + if (slider->IsDragging()) { // While we're dragging, we block the input's normal signalling and create our own @@ -287,17 +290,18 @@ void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant dragging_ = true; // Cache current value - drag_old_value_ = input_->get_value_at_time(time_); + drag_old_value_ = input_->get_value_at_time_for_track(time_, slider_track); // Determine whether we are creating a keyframe or not if (input_->is_keyframing()) { - dragging_keyframe_ = input_->get_keyframe_at_time(time_); + dragging_keyframe_ = input_->get_keyframe_at_time_on_track(time_, slider_track); drag_created_keyframe_ = !dragging_keyframe_; if (drag_created_keyframe_) { dragging_keyframe_ = NodeKeyframe::Create(time_, value, - input_->get_best_keyframe_type_for_time(time_)); + input_->get_best_keyframe_type_for_time(time_, slider_track), + slider_track); input_->insert_keyframe(dragging_keyframe_); @@ -312,7 +316,7 @@ void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant if (input_->is_keyframing()) { dragging_keyframe_->set_value(value); } else { - input_->set_standard_value(value); + input_->set_standard_value(value, slider_track); } input_->blockSignals(false); @@ -337,14 +341,14 @@ void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant new NodeParamSetKeyframeValueCommand(dragging_keyframe_, value, drag_old_value_, command); } else { // We just set the standard value - new NodeParamSetStandardValueCommand(input_, value, drag_old_value_, command); + new NodeParamSetStandardValueCommand(input_, slider_track, value, drag_old_value_, command); } Core::instance()->undo_stack()->push(command); } else { // No drag was involved, we can just push the value - SetInputValue(value); + SetInputValue(value, slider_track); } } } @@ -387,12 +391,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() // Widget is a FloatSlider FloatSlider* slider = static_cast(sender()); - QVector2D value( - static_cast(static_cast(widgets_.at(0))->GetValue()), - static_cast(static_cast(widgets_.at(1))->GetValue()) - ); - - ProcessSlider(slider, value); + ProcessSlider(slider, slider->GetValue()); break; } case NodeParam::kVec3: @@ -400,13 +399,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() // Widget is a FloatSlider FloatSlider* slider = static_cast(sender()); - QVector3D value( - static_cast(static_cast(widgets_.at(0))->GetValue()), - static_cast(static_cast(widgets_.at(1))->GetValue()), - static_cast(static_cast(widgets_.at(2))->GetValue()) - ); - - ProcessSlider(slider, value); + ProcessSlider(slider, slider->GetValue()); break; } case NodeParam::kVec4: @@ -414,14 +407,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() // Widget is a FloatSlider FloatSlider* slider = static_cast(sender()); - QVector4D value( - static_cast(static_cast(widgets_.at(0))->GetValue()), - static_cast(static_cast(widgets_.at(1))->GetValue()), - static_cast(static_cast(widgets_.at(2))->GetValue()), - static_cast(static_cast(widgets_.at(3))->GetValue()) - ); - - ProcessSlider(slider, value); + ProcessSlider(slider, slider->GetValue()); break; } case NodeParam::kFile: @@ -433,25 +419,25 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeParam::kText: { // Sender is a QLineEdit - SetInputValue(static_cast(sender())->text()); + SetInputValue(static_cast(sender())->text(), 0); break; } case NodeParam::kBoolean: { // Widget is a QCheckBox - SetInputValue(static_cast(sender())->isChecked()); + SetInputValue(static_cast(sender())->isChecked(), 0); break; } case NodeParam::kFont: { // Widget is a QFontComboBox - SetInputValue(static_cast(sender())->currentFont()); + SetInputValue(static_cast(sender())->currentFont(), 0); break; } case NodeParam::kFootage: { // Widget is a FootageComboBox - SetInputValue(QVariant::fromValue(static_cast(sender())->SelectedFootage())); + SetInputValue(QVariant::fromValue(static_cast(sender())->SelectedFootage()), 0); break; } } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index 4fff55bd2..40c9346f9 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -19,7 +19,7 @@ public: private: void CreateWidgets(); - void SetInputValue(const QVariant& value); + void SetInputValue(const QVariant& value, int track); void ProcessSlider(SliderBase* slider, const QVariant& value);