Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[Impeller Scene] Animation binding and playback #38595

Merged
merged 2 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions impeller/scene/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
125 changes: 125 additions & 0 deletions impeller/scene/animation/animation.cc
Original file line number Diff line number Diff line change
@@ -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 changes: 76 additions & 0 deletions impeller/scene/animation/animation.h
Original file line number Diff line number Diff line change
@@ -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 changes: 136 additions & 0 deletions impeller/scene/animation/animation_clip.cc
Original file line number Diff line number Diff line change
@@ -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
Loading