mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller Scene] Animation binding and playback (flutter/engine#38595)
This commit is contained in:
parent
25975b721f
commit
ef04e885ab
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
125
engine/src/flutter/impeller/scene/animation/animation.cc
Normal file
125
engine/src/flutter/impeller/scene/animation/animation.cc
Normal file
@ -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 <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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> Animation::MakeFromFlatbuffer(
|
||||
const fb::Animation& animation,
|
||||
const std::vector<std::shared_ptr<Node>>& scene_nodes) {
|
||||
auto result = std::shared_ptr<Animation>(new Animation());
|
||||
|
||||
result->name_ = animation.name()->str();
|
||||
for (auto channel : *animation.channels()) {
|
||||
if (channel->node() < 0 ||
|
||||
static_cast<size_t>(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<Scalar> 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<Vector3> 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<Quaternion> 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<Vector3> 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::Channel>& Animation::GetChannels() const {
|
||||
return channels_;
|
||||
}
|
||||
|
||||
Scalar Animation::GetEndTime() const {
|
||||
return end_time_;
|
||||
}
|
||||
|
||||
} // namespace scene
|
||||
} // namespace impeller
|
||||
76
engine/src/flutter/impeller/scene/animation/animation.h
Normal file
76
engine/src/flutter/impeller/scene/animation/animation.h
Normal file
@ -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 <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<Animation> MakeFromFlatbuffer(
|
||||
const fb::Animation& animation,
|
||||
const std::vector<std::shared_ptr<Node>>& 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<PropertyResolver> resolver;
|
||||
};
|
||||
~Animation();
|
||||
|
||||
const std::string& GetName() const;
|
||||
|
||||
const std::vector<Channel>& GetChannels() const;
|
||||
|
||||
Scalar GetEndTime() const;
|
||||
|
||||
private:
|
||||
Animation();
|
||||
|
||||
std::string name_;
|
||||
std::vector<Channel> channels_;
|
||||
Scalar end_time_ = 0;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Animation);
|
||||
};
|
||||
|
||||
} // namespace scene
|
||||
} // namespace impeller
|
||||
136
engine/src/flutter/impeller/scene/animation/animation_clip.cc
Normal file
136
engine/src/flutter/impeller/scene/animation/animation_clip.cc
Normal file
@ -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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <valarray>
|
||||
|
||||
#include "impeller/scene/node.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace scene {
|
||||
|
||||
AnimationClip::AnimationClip(std::shared_ptr<Animation> 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
|
||||
88
engine/src/flutter/impeller/scene/animation/animation_clip.h
Normal file
88
engine/src/flutter/impeller/scene/animation/animation_clip.h
Normal file
@ -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 <memory>
|
||||
#include <vector>
|
||||
|
||||
#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> 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> animation_;
|
||||
std::vector<ChannelBinding> 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
|
||||
@ -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 <memory>
|
||||
|
||||
#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> 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
|
||||
@ -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 <memory>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#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> 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<Node*, Matrix> default_target_transforms_;
|
||||
|
||||
std::vector<AnimationClip> clips_;
|
||||
|
||||
std::optional<fml::TimeDelta> previous_time_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(AnimationPlayer);
|
||||
};
|
||||
|
||||
} // namespace scene
|
||||
} // namespace impeller
|
||||
131
engine/src/flutter/impeller/scene/animation/property_resolver.cc
Normal file
131
engine/src/flutter/impeller/scene/animation/property_resolver.cc
Normal file
@ -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 <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
||||
#include "impeller/geometry/point.h"
|
||||
#include "impeller/scene/node.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace scene {
|
||||
|
||||
std::unique_ptr<TranslationTimelineResolver>
|
||||
PropertyResolver::MakeTranslationTimeline(std::vector<Scalar> times,
|
||||
std::vector<Vector3> values) {
|
||||
FML_DCHECK(times.size() == values.size());
|
||||
auto result = std::unique_ptr<TranslationTimelineResolver>(
|
||||
new TranslationTimelineResolver());
|
||||
result->times_ = std::move(times);
|
||||
result->values_ = std::move(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<RotationTimelineResolver>
|
||||
PropertyResolver::MakeRotationTimeline(std::vector<Scalar> times,
|
||||
std::vector<Quaternion> values) {
|
||||
FML_DCHECK(times.size() == values.size());
|
||||
auto result =
|
||||
std::unique_ptr<RotationTimelineResolver>(new RotationTimelineResolver());
|
||||
result->times_ = std::move(times);
|
||||
result->values_ = std::move(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<ScaleTimelineResolver> PropertyResolver::MakeScaleTimeline(
|
||||
std::vector<Scalar> times,
|
||||
std::vector<Vector3> values) {
|
||||
FML_DCHECK(times.size() == values.size());
|
||||
auto result =
|
||||
std::unique_ptr<ScaleTimelineResolver>(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
|
||||
123
engine/src/flutter/impeller/scene/animation/property_resolver.h
Normal file
123
engine/src/flutter/impeller/scene/animation/property_resolver.h
Normal file
@ -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 <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<TranslationTimelineResolver> MakeTranslationTimeline(
|
||||
std::vector<Scalar> times,
|
||||
std::vector<Vector3> values);
|
||||
|
||||
static std::unique_ptr<RotationTimelineResolver> MakeRotationTimeline(
|
||||
std::vector<Scalar> times,
|
||||
std::vector<Quaternion> values);
|
||||
|
||||
static std::unique_ptr<ScaleTimelineResolver> MakeScaleTimeline(
|
||||
std::vector<Scalar> times,
|
||||
std::vector<Vector3> 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<Scalar> times_;
|
||||
};
|
||||
|
||||
class TranslationTimelineResolver final : public TimelineResolver {
|
||||
public:
|
||||
~TranslationTimelineResolver();
|
||||
|
||||
// |Resolver|
|
||||
void Apply(Node& target, Scalar time, Scalar weight) override;
|
||||
|
||||
private:
|
||||
TranslationTimelineResolver();
|
||||
|
||||
std::vector<Vector3> 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<Quaternion> 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<Vector3> values_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(ScaleTimelineResolver);
|
||||
|
||||
friend PropertyResolver;
|
||||
};
|
||||
|
||||
} // namespace scene
|
||||
} // namespace impeller
|
||||
@ -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> 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> 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> Node::FindNodeByName(const std::string& name) const {
|
||||
std::shared_ptr<Node> 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<Animation> 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>& 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_) {
|
||||
|
||||
@ -9,10 +9,12 @@
|
||||
#include <vector>
|
||||
|
||||
#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<Node> FindNodeByName(const std::string& name) const;
|
||||
std::shared_ptr<Node> FindChildByName(
|
||||
const std::string& name,
|
||||
bool exclude_animation_players = false) const;
|
||||
|
||||
std::shared_ptr<Animation> FindAnimationByName(const std::string& name) const;
|
||||
AnimationClip& AddAnimation(const std::shared_ptr<Animation>& 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<std::shared_ptr<Texture>>& textures,
|
||||
Allocator& allocator);
|
||||
|
||||
Matrix local_transform_;
|
||||
|
||||
std::string name_;
|
||||
bool is_root_ = false;
|
||||
Node* parent_ = nullptr;
|
||||
std::vector<std::shared_ptr<Node>> children_;
|
||||
Mesh mesh_;
|
||||
|
||||
// For convenience purposes, deserialized nodes hang onto an animation library
|
||||
std::vector<std::shared_ptr<Animation>> animations_;
|
||||
mutable std::optional<AnimationPlayer> animation_player_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Node);
|
||||
|
||||
friend Scene;
|
||||
|
||||
@ -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<SceneContext>(GetContext());
|
||||
auto scene = Scene(scene_context);
|
||||
scene.GetRoot().AddChild(std::move(gltf_scene));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user