diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 32fb3fed2ef1f..45c49e7db4657 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1607,6 +1607,14 @@ ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.cc + .. ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_types.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/runtime_stage/runtime_types.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_clip.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_clip.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_player.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/animation_player.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/property_resolver.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/scene/animation/property_resolver.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/scene/camera.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/scene/camera.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/scene/geometry.cc + ../../../flutter/LICENSE @@ -4067,6 +4075,14 @@ FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.cc FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.h FILE: ../../../flutter/impeller/runtime_stage/runtime_types.cc FILE: ../../../flutter/impeller/runtime_stage/runtime_types.h +FILE: ../../../flutter/impeller/scene/animation/animation.cc +FILE: ../../../flutter/impeller/scene/animation/animation.h +FILE: ../../../flutter/impeller/scene/animation/animation_clip.cc +FILE: ../../../flutter/impeller/scene/animation/animation_clip.h +FILE: ../../../flutter/impeller/scene/animation/animation_player.cc +FILE: ../../../flutter/impeller/scene/animation/animation_player.h +FILE: ../../../flutter/impeller/scene/animation/property_resolver.cc +FILE: ../../../flutter/impeller/scene/animation/property_resolver.h FILE: ../../../flutter/impeller/scene/camera.cc FILE: ../../../flutter/impeller/scene/camera.h FILE: ../../../flutter/impeller/scene/geometry.cc diff --git a/impeller/scene/BUILD.gn b/impeller/scene/BUILD.gn index 978885867b496..69c024f9e4232 100644 --- a/impeller/scene/BUILD.gn +++ b/impeller/scene/BUILD.gn @@ -6,6 +6,14 @@ import("//flutter/impeller/tools/impeller.gni") impeller_component("scene") { sources = [ + "animation/animation.cc", + "animation/animation.h", + "animation/animation_clip.cc", + "animation/animation_clip.h", + "animation/animation_player.cc", + "animation/animation_player.h", + "animation/property_resolver.cc", + "animation/property_resolver.h", "camera.cc", "camera.h", "geometry.cc", diff --git a/impeller/scene/animation/animation.cc b/impeller/scene/animation/animation.cc new file mode 100644 index 0000000000000..b8e4b557e2939 --- /dev/null +++ b/impeller/scene/animation/animation.cc @@ -0,0 +1,125 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/animation.h" + +#include +#include +#include +#include + +#include "impeller/geometry/quaternion.h" +#include "impeller/scene/importer/scene_flatbuffers.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +std::shared_ptr Animation::MakeFromFlatbuffer( + const fb::Animation& animation, + const std::vector>& scene_nodes) { + auto result = std::shared_ptr(new Animation()); + + result->name_ = animation.name()->str(); + for (auto channel : *animation.channels()) { + if (channel->node() < 0 || + static_cast(channel->node()) >= scene_nodes.size() || + !channel->timeline()) { + continue; + } + + Animation::Channel out_channel; + out_channel.bind_target.node_name = scene_nodes[channel->node()]->GetName(); + + auto* times = channel->timeline(); + std::vector out_times; + out_times.resize(channel->timeline()->size()); + std::copy(times->begin(), times->end(), out_times.begin()); + + // TODO(bdero): Why are the entries in the keyframe value arrays not + // contiguous in the flatbuffer? We should be able to get rid + // of the subloops below and just memcpy instead. + switch (channel->keyframes_type()) { + case fb::Keyframes::TranslationKeyframes: { + out_channel.bind_target.property = Animation::Property::kTranslation; + auto* keyframes = channel->keyframes_as_TranslationKeyframes(); + if (!keyframes->values()) { + continue; + } + std::vector out_values; + out_values.resize(keyframes->values()->size()); + for (size_t value_i = 0; value_i < keyframes->values()->size(); + value_i++) { + auto val = (*keyframes->values())[value_i]; + out_values[value_i] = Vector3(val->x(), val->y(), val->z()); + } + out_channel.resolver = PropertyResolver::MakeTranslationTimeline( + std::move(out_times), std::move(out_values)); + break; + } + case fb::Keyframes::RotationKeyframes: { + out_channel.bind_target.property = Animation::Property::kRotation; + auto* keyframes = channel->keyframes_as_RotationKeyframes(); + if (!keyframes->values()) { + continue; + } + std::vector out_values; + out_values.resize(keyframes->values()->size()); + for (size_t value_i = 0; value_i < keyframes->values()->size(); + value_i++) { + auto val = (*keyframes->values())[value_i]; + out_values[value_i] = + Quaternion(val->x(), val->y(), val->z(), val->w()); + } + out_channel.resolver = PropertyResolver::MakeRotationTimeline( + std::move(out_times), std::move(out_values)); + break; + } + case fb::Keyframes::ScaleKeyframes: { + out_channel.bind_target.property = Animation::Property::kScale; + auto* keyframes = channel->keyframes_as_ScaleKeyframes(); + if (!keyframes->values()) { + continue; + } + std::vector out_values; + out_values.resize(keyframes->values()->size()); + for (size_t value_i = 0; value_i < keyframes->values()->size(); + value_i++) { + auto val = (*keyframes->values())[value_i]; + out_values[value_i] = Vector3(val->x(), val->y(), val->z()); + } + out_channel.resolver = PropertyResolver::MakeScaleTimeline( + std::move(out_times), std::move(out_values)); + break; + } + case fb::Keyframes::NONE: + continue; + } + + result->end_time_ = + std::max(result->end_time_, out_channel.resolver->GetEndTime()); + result->channels_.push_back(std::move(out_channel)); + } + + return result; +} + +Animation::Animation() = default; + +Animation::~Animation() = default; + +const std::string& Animation::GetName() const { + return name_; +} + +const std::vector& Animation::GetChannels() const { + return channels_; +} + +Scalar Animation::GetEndTime() const { + return end_time_; +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation.h b/impeller/scene/animation/animation.h new file mode 100644 index 0000000000000..3f4799fbd6d36 --- /dev/null +++ b/impeller/scene/animation/animation.h @@ -0,0 +1,76 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "impeller/geometry/quaternion.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/vector.h" +#include "impeller/scene/animation/property_resolver.h" +#include "impeller/scene/importer/scene_flatbuffers.h" + +namespace impeller { +namespace scene { + +class Node; + +class Animation final { + public: + static std::shared_ptr MakeFromFlatbuffer( + const fb::Animation& animation, + const std::vector>& scene_nodes); + + enum class Property { + kTranslation, + kRotation, + kScale, + }; + + struct BindKey { + std::string node_name; + Property property = Property::kTranslation; + + struct Hash { + std::size_t operator()(const BindKey& o) const { + return fml::HashCombine(o.node_name, o.property); + } + }; + + struct Equal { + bool operator()(const BindKey& lhs, const BindKey& rhs) const { + return lhs.node_name == rhs.node_name && lhs.property == rhs.property; + } + }; + }; + + struct Channel { + BindKey bind_target; + std::unique_ptr resolver; + }; + ~Animation(); + + const std::string& GetName() const; + + const std::vector& GetChannels() const; + + Scalar GetEndTime() const; + + private: + Animation(); + + std::string name_; + std::vector channels_; + Scalar end_time_ = 0; + + FML_DISALLOW_COPY_AND_ASSIGN(Animation); +}; + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation_clip.cc b/impeller/scene/animation/animation_clip.cc new file mode 100644 index 0000000000000..eabfffbe54377 --- /dev/null +++ b/impeller/scene/animation/animation_clip.cc @@ -0,0 +1,136 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/animation_clip.h" + +#include +#include +#include +#include + +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +AnimationClip::AnimationClip(std::shared_ptr animation, + Node* bind_target) + : animation_(std::move(animation)) { + BindToTarget(bind_target); +} + +AnimationClip::~AnimationClip() = default; + +AnimationClip::AnimationClip(AnimationClip&&) = default; +AnimationClip& AnimationClip::operator=(AnimationClip&&) = default; + +bool AnimationClip::IsPlaying() const { + return playing_; +} + +void AnimationClip::SetPlaying(bool playing) { + playing_ = playing; +} + +void AnimationClip::Play() { + SetPlaying(true); +} + +void AnimationClip::Pause() { + SetPlaying(false); +} + +void AnimationClip::Stop() { + SetPlaying(false); + Seek(0); +} + +bool AnimationClip::GetLoop() const { + return loop_; +} + +void AnimationClip::SetLoop(bool looping) { + loop_ = looping; +} + +Scalar AnimationClip::GetPlaybackTimeScale() const { + return playback_time_scale_; +} + +void AnimationClip::SetPlaybackTimeScale(Scalar playback_speed) { + playback_time_scale_ = playback_speed; +} + +Scalar AnimationClip::GetWeight() const { + return weight_; +} + +void AnimationClip::SetWeight(Scalar weight) { + weight_ = weight; +} + +Scalar AnimationClip::GetPlaybackTime() const { + return playback_time_; +} + +void AnimationClip::Seek(Scalar time) { + playback_time_ = std::clamp(time, 0.0f, animation_->GetEndTime()); +} + +void AnimationClip::Advance(Scalar delta_time) { + if (!playing_ || delta_time <= 0) { + return; + } + delta_time *= playback_time_scale_; + playback_time_ += delta_time; + + /// Handle looping behavior. + + Scalar end_time = animation_->GetEndTime(); + if (end_time == 0) { + playback_time_ = 0; + return; + } + if (!loop_ && (playback_time_ < 0 || playback_time_ > end_time)) { + // If looping is disabled, clamp to the end (or beginning, if playing in + // reverse) and pause. + Pause(); + playback_time_ = std::clamp(playback_time_, 0.0f, end_time); + } else if (/* loop && */ playback_time_ > end_time) { + // If looping is enabled and we ran off the end, loop to the beginning. + playback_time_ = std::fmod(std::abs(playback_time_), end_time); + } else if (/* loop && */ playback_time_ < 0) { + // If looping is enabled and we ran off the beginning, loop to the end. + playback_time_ = end_time - std::fmod(std::abs(playback_time_), end_time); + } +} + +void AnimationClip::ApplyToBindings() const { + for (auto& binding : bindings_) { + binding.channel.resolver->Apply(*binding.node, playback_time_, weight_); + } +} + +void AnimationClip::BindToTarget(Node* node) { + const auto& channels = animation_->GetChannels(); + bindings_.clear(); + bindings_.reserve(channels.size()); + + for (const auto& channel : channels) { + Node* channel_target; + if (channel.bind_target.node_name == node->GetName()) { + channel_target = node; + } else if (auto result = + node->FindChildByName(channel.bind_target.node_name, true)) { + channel_target = result.get(); + } else { + continue; + } + bindings_.push_back( + ChannelBinding{.channel = channel, .node = channel_target}); + } +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation_clip.h b/impeller/scene/animation/animation_clip.h new file mode 100644 index 0000000000000..1bd18935fdc10 --- /dev/null +++ b/impeller/scene/animation/animation_clip.h @@ -0,0 +1,88 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/scene/animation/animation.h" + +namespace impeller { +namespace scene { + +class Node; +class AnimationPlayer; + +class AnimationClip final { + public: + AnimationClip(std::shared_ptr animation, Node* bind_target); + ~AnimationClip(); + + AnimationClip(AnimationClip&&); + AnimationClip& operator=(AnimationClip&&); + + bool IsPlaying() const; + + void SetPlaying(bool playing); + + void Play(); + + void Pause(); + + void Stop(); + + bool GetLoop() const; + + void SetLoop(bool looping); + + Scalar GetPlaybackTimeScale() const; + + /// @brief Sets the animation playback speed. Negative values make the clip + /// play in reverse. + void SetPlaybackTimeScale(Scalar playback_speed); + + Scalar GetWeight() const; + + void SetWeight(Scalar weight); + + /// @brief Get the current playback time of the animation. + Scalar GetPlaybackTime() const; + + /// @brief Move the animation to the specified time. The given `time` is + /// clamped to the animation's playback range. + void Seek(Scalar time); + + /// @brief Advance the animation by `delta_time` seconds. Negative + /// `delta_time` values do nothing. + void Advance(Scalar delta_time); + + /// @brief Applies the animation to all binded properties in the scene. + void ApplyToBindings() const; + + private: + void BindToTarget(Node* node); + + struct ChannelBinding { + const Animation::Channel& channel; + Node* node; + }; + + std::shared_ptr animation_; + std::vector bindings_; + + Scalar playback_time_ = 0; + Scalar playback_time_scale_ = 1; // Seconds multiplier, can be negative. + Scalar weight_ = 1; + bool playing_ = false; + bool loop_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(AnimationClip); + + friend AnimationPlayer; +}; + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation_player.cc b/impeller/scene/animation/animation_player.cc new file mode 100644 index 0000000000000..15d4dc9e7c57d --- /dev/null +++ b/impeller/scene/animation/animation_player.cc @@ -0,0 +1,61 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/animation_player.h" + +#include + +#include "flutter/fml/time/time_point.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +AnimationPlayer::AnimationPlayer() = default; +AnimationPlayer::~AnimationPlayer() = default; + +AnimationPlayer::AnimationPlayer(AnimationPlayer&&) = default; +AnimationPlayer& AnimationPlayer::operator=(AnimationPlayer&&) = default; + +AnimationClip& AnimationPlayer::AddAnimation( + std::shared_ptr animation, + Node* bind_target) { + AnimationClip clip(std::move(animation), bind_target); + + // Record all of the unique default transforms that this AnimationClip + // will mutate. + for (const auto& binding : clip.bindings_) { + default_target_transforms_.insert( + {binding.node, binding.node->GetLocalTransform()}); + } + + clips_.push_back(std::move(clip)); + return clips_.back(); +} + +void AnimationPlayer::Update() { + if (!previous_time_.has_value()) { + previous_time_ = fml::TimePoint::Now().ToEpochDelta(); + } + auto new_time = fml::TimePoint::Now().ToEpochDelta(); + Scalar delta_time = (new_time - previous_time_.value()).ToSecondsF(); + previous_time_ = new_time; + + Reset(); + + // Update and apply all clips. + for (auto& clip : clips_) { + clip.Advance(delta_time); + clip.ApplyToBindings(); + } +} + +void AnimationPlayer::Reset() { + for (auto& [node, transform] : default_target_transforms_) { + node->SetLocalTransform(transform); + } +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/animation_player.h b/impeller/scene/animation/animation_player.h new file mode 100644 index 0000000000000..808b76e57b910 --- /dev/null +++ b/impeller/scene/animation/animation_player.h @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "flutter/fml/time/time_delta.h" +#include "impeller/geometry/matrix.h" +#include "impeller/scene/animation/animation_clip.h" + +namespace impeller { +namespace scene { + +class Node; + +class AnimationPlayer final { + public: + AnimationPlayer(); + ~AnimationPlayer(); + + AnimationPlayer(AnimationPlayer&&); + AnimationPlayer& operator=(AnimationPlayer&&); + + AnimationClip& AddAnimation(std::shared_ptr animation, + Node* bind_target); + + /// @brief Advanced all clips and updates animated properties in the scene. + void Update(); + + /// @brief Reset all bound animation target transforms. + void Reset(); + + private: + std::unordered_map default_target_transforms_; + + std::vector clips_; + + std::optional previous_time_; + + FML_DISALLOW_COPY_AND_ASSIGN(AnimationPlayer); +}; + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/property_resolver.cc b/impeller/scene/animation/property_resolver.cc new file mode 100644 index 0000000000000..6407f1d8a60b4 --- /dev/null +++ b/impeller/scene/animation/property_resolver.cc @@ -0,0 +1,131 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/scene/animation/property_resolver.h" + +#include +#include +#include + +#include "impeller/geometry/point.h" +#include "impeller/scene/node.h" + +namespace impeller { +namespace scene { + +std::unique_ptr +PropertyResolver::MakeTranslationTimeline(std::vector times, + std::vector values) { + FML_DCHECK(times.size() == values.size()); + auto result = std::unique_ptr( + new TranslationTimelineResolver()); + result->times_ = std::move(times); + result->values_ = std::move(values); + return result; +} + +std::unique_ptr +PropertyResolver::MakeRotationTimeline(std::vector times, + std::vector values) { + FML_DCHECK(times.size() == values.size()); + auto result = + std::unique_ptr(new RotationTimelineResolver()); + result->times_ = std::move(times); + result->values_ = std::move(values); + return result; +} + +std::unique_ptr PropertyResolver::MakeScaleTimeline( + std::vector times, + std::vector values) { + FML_DCHECK(times.size() == values.size()); + auto result = + std::unique_ptr(new ScaleTimelineResolver()); + result->times_ = std::move(times); + result->values_ = std::move(values); + return result; +} + +PropertyResolver::~PropertyResolver() = default; + +TimelineResolver::~TimelineResolver() = default; + +Scalar TimelineResolver::GetEndTime() { + if (times_.empty()) { + return 0; + } + return times_.back(); +} + +TimelineResolver::TimelineKey TimelineResolver::GetTimelineKey(Scalar time) { + if (times_.size() <= 1 || time <= times_.front()) { + return {.index = 0, .lerp = 1}; + } + if (time >= times_.back()) { + return {.index = times_.size() - 1, .lerp = 1}; + } + auto it = std::lower_bound(times_.begin(), times_.end(), time); + size_t index = std::distance(times_.begin(), it); + + Scalar previous_time = *(it - 1); + Scalar next_time = *it; + return {.index = index, + .lerp = (time - previous_time) / (next_time - previous_time)}; +} + +TranslationTimelineResolver::TranslationTimelineResolver() = default; + +TranslationTimelineResolver::~TranslationTimelineResolver() = default; + +void TranslationTimelineResolver::Apply(Node& target, + Scalar time, + Scalar weight) { + if (values_.empty()) { + return; + } + auto key = GetTimelineKey(time); + auto value = values_[key.index]; + if (key.lerp < 1) { + value = values_[key.index - 1].Lerp(value, key.lerp); + } + target.SetLocalTransform(Matrix::MakeTranslation(value * weight) * + target.GetLocalTransform()); +} + +RotationTimelineResolver::RotationTimelineResolver() = default; + +RotationTimelineResolver::~RotationTimelineResolver() = default; + +void RotationTimelineResolver::Apply(Node& target, Scalar time, Scalar weight) { + if (values_.empty()) { + return; + } + auto key = GetTimelineKey(time); + auto value = values_[key.index]; + if (key.lerp < 1) { + value = values_[key.index - 1].Slerp(value, key.lerp); + } + target.SetLocalTransform(Matrix::MakeRotation(value * weight) * + target.GetLocalTransform()); +} + +ScaleTimelineResolver::ScaleTimelineResolver() = default; + +ScaleTimelineResolver::~ScaleTimelineResolver() = default; + +void ScaleTimelineResolver::Apply(Node& target, Scalar time, Scalar weight) { + if (values_.empty()) { + return; + } + auto key = GetTimelineKey(time); + auto value = values_[key.index]; + if (key.lerp < 1) { + value = values_[key.index - 1].Lerp(value, key.lerp); + } + target.SetLocalTransform(Matrix::MakeScale(value * weight) * + target.GetLocalTransform()); +} + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/property_resolver.h b/impeller/scene/animation/property_resolver.h new file mode 100644 index 0000000000000..3e5bde2bb15ac --- /dev/null +++ b/impeller/scene/animation/property_resolver.h @@ -0,0 +1,123 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/hash_combine.h" +#include "flutter/fml/macros.h" +#include "impeller/geometry/quaternion.h" +#include "impeller/geometry/scalar.h" +#include "impeller/geometry/vector.h" + +namespace impeller { +namespace scene { + +class Node; +class TranslationTimelineResolver; +class RotationTimelineResolver; +class ScaleTimelineResolver; + +class PropertyResolver { + public: + static std::unique_ptr MakeTranslationTimeline( + std::vector times, + std::vector values); + + static std::unique_ptr MakeRotationTimeline( + std::vector times, + std::vector values); + + static std::unique_ptr MakeScaleTimeline( + std::vector times, + std::vector values); + + virtual ~PropertyResolver(); + + virtual Scalar GetEndTime() = 0; + + /// @brief Resolve and apply the property value to a target node. This + /// operation is additive; a given node property may be amended by + /// many different PropertyResolvers prior to rendering. For example, + /// an AnimationPlayer may blend multiple Animations together by + /// applying several AnimationClips. + virtual void Apply(Node& target, Scalar time, Scalar weight) = 0; +}; + +class TimelineResolver : public PropertyResolver { + public: + virtual ~TimelineResolver(); + + // |Resolver| + Scalar GetEndTime(); + + protected: + struct TimelineKey { + /// The index of the closest previous keyframe. + size_t index = 0; + /// Used to interpolate between the resolved values for `timeline_index - 1` + /// and `timeline_index`. The range of this value should always be `0>N>=1`. + Scalar lerp = 1; + }; + TimelineKey GetTimelineKey(Scalar time); + + std::vector times_; +}; + +class TranslationTimelineResolver final : public TimelineResolver { + public: + ~TranslationTimelineResolver(); + + // |Resolver| + void Apply(Node& target, Scalar time, Scalar weight) override; + + private: + TranslationTimelineResolver(); + + std::vector values_; + + FML_DISALLOW_COPY_AND_ASSIGN(TranslationTimelineResolver); + + friend PropertyResolver; +}; + +class RotationTimelineResolver final : public TimelineResolver { + public: + ~RotationTimelineResolver(); + + // |Resolver| + void Apply(Node& target, Scalar time, Scalar weight) override; + + private: + RotationTimelineResolver(); + + std::vector values_; + + FML_DISALLOW_COPY_AND_ASSIGN(RotationTimelineResolver); + + friend PropertyResolver; +}; + +class ScaleTimelineResolver final : public TimelineResolver { + public: + ~ScaleTimelineResolver(); + + // |Resolver| + void Apply(Node& target, Scalar time, Scalar weight) override; + + private: + ScaleTimelineResolver(); + + std::vector values_; + + FML_DISALLOW_COPY_AND_ASSIGN(ScaleTimelineResolver); + + friend PropertyResolver; +}; + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/node.cc b/impeller/scene/node.cc index cc1a847356da3..72a551af71278 100644 --- a/impeller/scene/node.cc +++ b/impeller/scene/node.cc @@ -12,6 +12,7 @@ #include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/geometry/matrix.h" +#include "impeller/scene/animation/animation_player.h" #include "impeller/scene/importer/conversions.h" #include "impeller/scene/importer/scene_flatbuffers.h" #include "impeller/scene/mesh.h" @@ -144,7 +145,6 @@ std::shared_ptr Node::MakeFromFlatbuffer(const fb::Scene& scene, } result->AddChild(scene_nodes[child]); } - // TODO(bdero): Unpack animations. // Unpack each node. for (size_t node_i = 0; node_i < scene.nodes()->size(); node_i++) { @@ -152,6 +152,16 @@ std::shared_ptr Node::MakeFromFlatbuffer(const fb::Scene& scene, scene_nodes, textures, allocator); } + // Unpack animations. + if (scene.animations()) { + for (const auto animation : *scene.animations()) { + if (auto out_animation = + Animation::MakeFromFlatbuffer(*animation, scene_nodes)) { + result->animations_.push_back(out_animation); + } + } + } + return result; } @@ -202,10 +212,6 @@ Mesh::Mesh(Mesh&& mesh) = default; Mesh& Mesh::operator=(Mesh&& mesh) = default; -Node::Node(Node&& node) = default; - -Node& Node::operator=(Node&& node) = default; - const std::string& Node::GetName() const { return name_; } @@ -214,18 +220,40 @@ void Node::SetName(const std::string& new_name) { name_ = new_name; } -std::shared_ptr Node::FindNodeByName(const std::string& name) const { +std::shared_ptr Node::FindChildByName( + const std::string& name, + bool exclude_animation_players) const { for (auto& child : children_) { + if (exclude_animation_players && child->animation_player_.has_value()) { + continue; + } if (child->GetName() == name) { return child; } - if (auto found = child->FindNodeByName(name)) { + if (auto found = child->FindChildByName(name)) { return found; } } return nullptr; } +std::shared_ptr Node::FindAnimationByName( + const std::string& name) const { + for (const auto& animation : animations_) { + if (animation->GetName() == name) { + return animation; + } + } + return nullptr; +} + +AnimationClip& Node::AddAnimation(const std::shared_ptr& animation) { + if (!animation_player_.has_value()) { + animation_player_ = AnimationPlayer(); + } + return animation_player_->AddAnimation(animation, this); +} + void Node::SetLocalTransform(Matrix transform) { local_transform_ = transform; } @@ -274,8 +302,11 @@ Mesh& Node::GetMesh() { } bool Node::Render(SceneEncoder& encoder, const Matrix& parent_transform) const { - Matrix transform = parent_transform * local_transform_; + if (animation_player_.has_value()) { + animation_player_->Update(); + } + Matrix transform = parent_transform * local_transform_; mesh_.Render(encoder, transform); for (auto& child : children_) { diff --git a/impeller/scene/node.h b/impeller/scene/node.h index 56290e9d1678c..e573f9991b236 100644 --- a/impeller/scene/node.h +++ b/impeller/scene/node.h @@ -9,10 +9,12 @@ #include #include "flutter/fml/macros.h" - #include "impeller/geometry/matrix.h" #include "impeller/renderer/render_target.h" #include "impeller/renderer/texture.h" +#include "impeller/scene/animation/animation.h" +#include "impeller/scene/animation/animation_clip.h" +#include "impeller/scene/animation/animation_player.h" #include "impeller/scene/camera.h" #include "impeller/scene/mesh.h" #include "impeller/scene/scene_encoder.h" @@ -31,13 +33,15 @@ class Node final { Node(); ~Node(); - Node(Node&& node); - Node& operator=(Node&& node); - const std::string& GetName() const; void SetName(const std::string& new_name); - std::shared_ptr FindNodeByName(const std::string& name) const; + std::shared_ptr FindChildByName( + const std::string& name, + bool exclude_animation_players = false) const; + + std::shared_ptr FindAnimationByName(const std::string& name) const; + AnimationClip& AddAnimation(const std::shared_ptr& animation); void SetLocalTransform(Matrix transform); Matrix GetLocalTransform() const; @@ -53,9 +57,6 @@ class Node final { bool Render(SceneEncoder& encoder, const Matrix& parent_transform) const; - protected: - Matrix local_transform_; - private: void UnpackFromFlatbuffer( const fb::Node& node, @@ -63,12 +64,18 @@ class Node final { const std::vector>& textures, Allocator& allocator); + Matrix local_transform_; + std::string name_; bool is_root_ = false; Node* parent_ = nullptr; std::vector> children_; Mesh mesh_; + // For convenience purposes, deserialized nodes hang onto an animation library + std::vector> animations_; + mutable std::optional animation_player_; + FML_DISALLOW_COPY_AND_ASSIGN(Node); friend Scene; diff --git a/impeller/scene/scene_unittests.cc b/impeller/scene/scene_unittests.cc index 0cbd458fb9f54..5ce513ab165e2 100644 --- a/impeller/scene/scene_unittests.cc +++ b/impeller/scene/scene_unittests.cc @@ -17,6 +17,7 @@ #include "impeller/playground/playground.h" #include "impeller/playground/playground_test.h" #include "impeller/renderer/formats.h" +#include "impeller/scene/animation/animation_clip.h" #include "impeller/scene/camera.h" #include "impeller/scene/geometry.h" #include "impeller/scene/importer/scene_flatbuffers.h" @@ -122,6 +123,13 @@ TEST_P(SceneTest, TwoTriangles) { Node::MakeFromFlatbuffer(*mapping, *allocator); ASSERT_NE(gltf_scene, nullptr); + auto animation = gltf_scene->FindAnimationByName("Metronome"); + ASSERT_NE(animation, nullptr); + + AnimationClip& metronome_clip = gltf_scene->AddAnimation(animation); + metronome_clip.SetLoop(true); + metronome_clip.Play(); + auto scene_context = std::make_shared(GetContext()); auto scene = Scene(scene_context); scene.GetRoot().AddChild(std::move(gltf_scene));