diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2a31b9f945b4b..6c8128d46e6a5 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1614,6 +1614,7 @@ ORIGIN: ../../../flutter/impeller/scene/animation/animation_clip.cc + ../../../f 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/animation_transforms.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 @@ -4094,6 +4095,7 @@ 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/animation_transforms.h FILE: ../../../flutter/impeller/scene/animation/property_resolver.cc FILE: ../../../flutter/impeller/scene/animation/property_resolver.h FILE: ../../../flutter/impeller/scene/camera.cc diff --git a/impeller/geometry/quaternion.h b/impeller/geometry/quaternion.h index 0270dab9dc814..0dacf0428fa43 100644 --- a/impeller/geometry/quaternion.h +++ b/impeller/geometry/quaternion.h @@ -45,6 +45,8 @@ struct Quaternion { return {x * m, y * m, z * m, w * m}; } + Quaternion Invert() const { return {-x, -y, -z, w}; } + Quaternion Slerp(const Quaternion& to, double time) const; Quaternion operator*(const Quaternion& o) const { diff --git a/impeller/scene/BUILD.gn b/impeller/scene/BUILD.gn index dbd531cf8e17d..d937166b1ff21 100644 --- a/impeller/scene/BUILD.gn +++ b/impeller/scene/BUILD.gn @@ -12,6 +12,7 @@ impeller_component("scene") { "animation/animation_clip.h", "animation/animation_player.cc", "animation/animation_player.h", + "animation/animation_transforms.h", "animation/property_resolver.cc", "animation/property_resolver.h", "camera.cc", diff --git a/impeller/scene/animation/animation_clip.cc b/impeller/scene/animation/animation_clip.cc index 10019e8f52bd1..4a03bf58ba5b5 100644 --- a/impeller/scene/animation/animation_clip.cc +++ b/impeller/scene/animation/animation_clip.cc @@ -67,7 +67,7 @@ Scalar AnimationClip::GetWeight() const { } void AnimationClip::SetWeight(Scalar weight) { - weight_ = weight; + weight_ = std::max(0.0f, weight); } SecondsF AnimationClip::GetPlaybackTime() const { @@ -110,9 +110,16 @@ void AnimationClip::Advance(SecondsF delta_time) { } } -void AnimationClip::ApplyToBindings() const { +void AnimationClip::ApplyToBindings( + std::unordered_map& transform_decomps, + Scalar weight_multiplier) const { for (auto& binding : bindings_) { - binding.channel.resolver->Apply(*binding.node, playback_time_, weight_); + auto transforms = transform_decomps.find(binding.node); + if (transforms == transform_decomps.end()) { + continue; + } + binding.channel.resolver->Apply(transforms->second, playback_time_, + weight_ * weight_multiplier); } } diff --git a/impeller/scene/animation/animation_clip.h b/impeller/scene/animation/animation_clip.h index cf584ae64620a..8fe150e2061ca 100644 --- a/impeller/scene/animation/animation_clip.h +++ b/impeller/scene/animation/animation_clip.h @@ -9,6 +9,7 @@ #include "flutter/fml/macros.h" #include "impeller/scene/animation/animation.h" +#include "impeller/scene/animation/animation_transforms.h" namespace impeller { namespace scene { @@ -60,7 +61,9 @@ class AnimationClip final { void Advance(SecondsF delta_time); /// @brief Applies the animation to all binded properties in the scene. - void ApplyToBindings() const; + void ApplyToBindings( + std::unordered_map& transform_decomps, + Scalar weight_multiplier) const; private: void BindToTarget(Node* node); diff --git a/impeller/scene/animation/animation_player.cc b/impeller/scene/animation/animation_player.cc index 01119fb521f61..9fcf53306879f 100644 --- a/impeller/scene/animation/animation_player.cc +++ b/impeller/scene/animation/animation_player.cc @@ -5,6 +5,7 @@ #include "impeller/scene/animation/animation_player.h" #include +#include #include "flutter/fml/time/time_point.h" #include "impeller/base/timing.h" @@ -19,20 +20,37 @@ AnimationPlayer::~AnimationPlayer() = default; AnimationPlayer::AnimationPlayer(AnimationPlayer&&) = default; AnimationPlayer& AnimationPlayer::operator=(AnimationPlayer&&) = default; -AnimationClip& AnimationPlayer::AddAnimation( - std::shared_ptr animation, +AnimationClip* AnimationPlayer::AddAnimation( + const std::shared_ptr& animation, Node* bind_target) { - AnimationClip clip(std::move(animation), bind_target); + if (!animation) { + VALIDATION_LOG << "Cannot add null animation."; + return nullptr; + } + + AnimationClip clip(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()}); + auto decomp = binding.node->GetLocalTransform().Decompose(); + if (!decomp.has_value()) { + continue; + } + target_transforms_.insert( + {binding.node, AnimationTransforms{.bind_pose = decomp.value()}}); } - clips_.push_back(std::move(clip)); - return clips_.back(); + auto result = clips_.insert({animation->GetName(), std::move(clip)}); + return &result.first->second; +} + +AnimationClip* AnimationPlayer::GetClip(const std::string& name) const { + auto result = clips_.find(name); + if (result == clips_.end()) { + return nullptr; + } + return const_cast(&result->second); } void AnimationPlayer::Update() { @@ -43,18 +61,27 @@ void AnimationPlayer::Update() { auto delta_time = new_time - previous_time_.value(); previous_time_ = new_time; - Reset(); + // Reset the animated pose state. + for (auto& [node, transforms] : target_transforms_) { + transforms.animated_pose = transforms.bind_pose; + } + + // Compute a weight multiplier for normalizing the animation. + Scalar total_weight = 0; + for (auto& [_, clip] : clips_) { + total_weight += clip.GetWeight(); + } + Scalar weight_multiplier = total_weight > 1 ? 1 / total_weight : 1; - // Update and apply all clips. - for (auto& clip : clips_) { + // Update and apply all clips to the animation pose state. + for (auto& [_, clip] : clips_) { clip.Advance(delta_time); - clip.ApplyToBindings(); + clip.ApplyToBindings(target_transforms_, weight_multiplier); } -} -void AnimationPlayer::Reset() { - for (auto& [node, transform] : default_target_transforms_) { - node->SetLocalTransform(Matrix()); + // Apply the animated pose to the bound joints. + for (auto& [node, transforms] : target_transforms_) { + node->SetLocalTransform(Matrix(transforms.animated_pose)); } } diff --git a/impeller/scene/animation/animation_player.h b/impeller/scene/animation/animation_player.h index 23c7336aed371..bcbb678d4c619 100644 --- a/impeller/scene/animation/animation_player.h +++ b/impeller/scene/animation/animation_player.h @@ -4,9 +4,9 @@ #pragma once +#include #include #include -#include #include #include "flutter/fml/hash_combine.h" @@ -14,6 +14,7 @@ #include "flutter/fml/time/time_delta.h" #include "impeller/base/timing.h" #include "impeller/geometry/matrix.h" +#include "impeller/geometry/matrix_decomposition.h" #include "impeller/scene/animation/animation_clip.h" namespace impeller { @@ -29,19 +30,18 @@ class AnimationPlayer final { AnimationPlayer(AnimationPlayer&&); AnimationPlayer& operator=(AnimationPlayer&&); - AnimationClip& AddAnimation(std::shared_ptr animation, + AnimationClip* AddAnimation(const std::shared_ptr& animation, Node* bind_target); + AnimationClip* GetClip(const std::string& name) const; + /// @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::unordered_map target_transforms_; - std::vector clips_; + std::map clips_; std::optional previous_time_; diff --git a/impeller/scene/animation/animation_transforms.h b/impeller/scene/animation/animation_transforms.h new file mode 100644 index 0000000000000..b15c9817781d4 --- /dev/null +++ b/impeller/scene/animation/animation_transforms.h @@ -0,0 +1,18 @@ +// 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 "impeller/geometry/matrix_decomposition.h" + +namespace impeller { +namespace scene { + +struct AnimationTransforms { + MatrixDecomposition bind_pose; + MatrixDecomposition animated_pose; +}; + +} // namespace scene +} // namespace impeller diff --git a/impeller/scene/animation/property_resolver.cc b/impeller/scene/animation/property_resolver.cc index d9d5db94e44e6..a43a308efdb75 100644 --- a/impeller/scene/animation/property_resolver.cc +++ b/impeller/scene/animation/property_resolver.cc @@ -8,6 +8,7 @@ #include #include +#include "impeller/geometry/matrix_decomposition.h" #include "impeller/geometry/point.h" #include "impeller/scene/node.h" @@ -78,7 +79,7 @@ TranslationTimelineResolver::TranslationTimelineResolver() = default; TranslationTimelineResolver::~TranslationTimelineResolver() = default; -void TranslationTimelineResolver::Apply(Node& target, +void TranslationTimelineResolver::Apply(AnimationTransforms& target, SecondsF time, Scalar weight) { if (values_.empty()) { @@ -89,15 +90,16 @@ void TranslationTimelineResolver::Apply(Node& target, if (key.lerp < 1) { value = values_[key.index - 1].Lerp(value, key.lerp); } - target.SetLocalTransform(target.GetLocalTransform() * - Matrix::MakeTranslation(value * weight)); + + target.animated_pose.translation += + (value - target.bind_pose.translation) * weight; } RotationTimelineResolver::RotationTimelineResolver() = default; RotationTimelineResolver::~RotationTimelineResolver() = default; -void RotationTimelineResolver::Apply(Node& target, +void RotationTimelineResolver::Apply(AnimationTransforms& target, SecondsF time, Scalar weight) { if (values_.empty()) { @@ -108,15 +110,19 @@ void RotationTimelineResolver::Apply(Node& target, if (key.lerp < 1) { value = values_[key.index - 1].Slerp(value, key.lerp); } - target.SetLocalTransform(target.GetLocalTransform() * - Matrix::MakeRotation(value * weight)); + + target.animated_pose.rotation = + target.animated_pose.rotation * + Quaternion().Slerp(target.bind_pose.rotation.Invert() * value, weight); } ScaleTimelineResolver::ScaleTimelineResolver() = default; ScaleTimelineResolver::~ScaleTimelineResolver() = default; -void ScaleTimelineResolver::Apply(Node& target, SecondsF time, Scalar weight) { +void ScaleTimelineResolver::Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) { if (values_.empty()) { return; } @@ -125,8 +131,9 @@ void ScaleTimelineResolver::Apply(Node& target, SecondsF time, Scalar weight) { if (key.lerp < 1) { value = values_[key.index - 1].Lerp(value, key.lerp); } - target.SetLocalTransform(target.GetLocalTransform() * - Matrix::MakeScale(value * weight)); + + target.animated_pose.scale *= + Vector3(1, 1, 1).Lerp(value / target.bind_pose.scale, weight); } } // namespace scene diff --git a/impeller/scene/animation/property_resolver.h b/impeller/scene/animation/property_resolver.h index e0cd1888a857a..f7b5ce2abfdff 100644 --- a/impeller/scene/animation/property_resolver.h +++ b/impeller/scene/animation/property_resolver.h @@ -11,9 +11,11 @@ #include "flutter/fml/hash_combine.h" #include "flutter/fml/macros.h" #include "impeller/base/timing.h" +#include "impeller/geometry/matrix_decomposition.h" #include "impeller/geometry/quaternion.h" #include "impeller/geometry/scalar.h" #include "impeller/geometry/vector.h" +#include "impeller/scene/animation/animation_transforms.h" namespace impeller { namespace scene { @@ -46,7 +48,9 @@ class PropertyResolver { /// many different PropertyResolvers prior to rendering. For example, /// an AnimationPlayer may blend multiple Animations together by /// applying several AnimationClips. - virtual void Apply(Node& target, SecondsF time, Scalar weight) = 0; + virtual void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) = 0; }; class TimelineResolver : public PropertyResolver { @@ -74,7 +78,9 @@ class TranslationTimelineResolver final : public TimelineResolver { ~TranslationTimelineResolver(); // |Resolver| - void Apply(Node& target, SecondsF time, Scalar weight) override; + void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) override; private: TranslationTimelineResolver(); @@ -91,7 +97,9 @@ class RotationTimelineResolver final : public TimelineResolver { ~RotationTimelineResolver(); // |Resolver| - void Apply(Node& target, SecondsF time, Scalar weight) override; + void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) override; private: RotationTimelineResolver(); @@ -108,7 +116,9 @@ class ScaleTimelineResolver final : public TimelineResolver { ~ScaleTimelineResolver(); // |Resolver| - void Apply(Node& target, SecondsF time, Scalar weight) override; + void Apply(AnimationTransforms& target, + SecondsF time, + Scalar weight) override; private: ScaleTimelineResolver(); diff --git a/impeller/scene/geometry.cc b/impeller/scene/geometry.cc index 099476d14202d..739d60923ba3b 100644 --- a/impeller/scene/geometry.cc +++ b/impeller/scene/geometry.cc @@ -95,7 +95,7 @@ std::shared_ptr Geometry::MakeFromFlatbuffer( } DeviceBufferDescriptor buffer_desc; - buffer_desc.size = vertices_bytes * indices_bytes; + buffer_desc.size = vertices_bytes + indices_bytes; buffer_desc.storage_mode = StorageMode::kHostVisible; auto buffer = allocator.CreateBuffer(buffer_desc); diff --git a/impeller/scene/material.cc b/impeller/scene/material.cc index 73ce339babe7f..87eb92e198013 100644 --- a/impeller/scene/material.cc +++ b/impeller/scene/material.cc @@ -81,13 +81,11 @@ std::unique_ptr UnlitMaterial::MakeFromFlatbuffer( if (material.base_color_factor()) { result->SetColor(importer::ToColor(*material.base_color_factor())); - result->SetVertexColorWeight(0); } if (material.base_color_texture() >= 0 && material.base_color_texture() < static_cast(textures.size())) { result->SetColorTexture(textures[material.base_color_texture()]); - result->SetVertexColorWeight(0); } return result; diff --git a/impeller/scene/node.cc b/impeller/scene/node.cc index 7343adb8fa98e..8f1da38f1e559 100644 --- a/impeller/scene/node.cc +++ b/impeller/scene/node.cc @@ -7,9 +7,11 @@ #include #include #include +#include #include "flutter/fml/logging.h" #include "impeller/base/strings.h" +#include "impeller/base/thread.h" #include "impeller/base/validation.h" #include "impeller/geometry/matrix.h" #include "impeller/scene/animation/animation_player.h" @@ -24,6 +26,24 @@ namespace scene { static std::atomic_uint64_t kNextNodeID = 0; +void Node::MutationLog::Append(const Entry& entry) { + WriterLock lock(write_mutex_); + dirty_ = true; + entries_.push_back(entry); +} + +std::optional> +Node::MutationLog::Flush() { + WriterLock lock(write_mutex_); + if (!dirty_) { + return std::nullopt; + } + dirty_ = false; + auto result = entries_; + entries_ = {}; + return result; +} + std::shared_ptr Node::MakeFromFlatbuffer( const fml::Mapping& ipscene_mapping, Allocator& allocator) { @@ -257,7 +277,7 @@ std::shared_ptr Node::FindAnimationByName( return nullptr; } -AnimationClip& Node::AddAnimation(const std::shared_ptr& animation) { +AnimationClip* Node::AddAnimation(const std::shared_ptr& animation) { if (!animation_player_.has_value()) { animation_player_ = AnimationPlayer(); } @@ -287,6 +307,11 @@ Matrix Node::GetGlobalTransform() const { } bool Node::AddChild(std::shared_ptr node) { + if (!node) { + VALIDATION_LOG << "Cannot add null child to node."; + return false; + } + // TODO(bdero): Figure out a better paradigm/rules for nodes with multiple // parents. We should probably disallow this, make deep // copying of nodes cheap and easy, add mesh instancing, etc. @@ -328,7 +353,55 @@ bool Node::IsJoint() const { bool Node::Render(SceneEncoder& encoder, Allocator& allocator, - const Matrix& parent_transform) const { + const Matrix& parent_transform) { + std::optional> log = mutation_log_.Flush(); + if (log.has_value()) { + for (const auto& entry : log.value()) { + if (auto e = std::get_if(&entry)) { + local_transform_ = e->transform; + } else if (auto e = + std::get_if(&entry)) { + AnimationClip* clip = + animation_player_.has_value() + ? animation_player_->GetClip(e->animation_name) + : nullptr; + if (!clip) { + auto animation = FindAnimationByName(e->animation_name); + if (!animation) { + continue; + } + clip = AddAnimation(animation); + if (!clip) { + continue; + } + } + + clip->SetPlaying(e->playing); + clip->SetLoop(e->loop); + clip->SetWeight(e->weight); + clip->SetPlaybackTimeScale(e->time_scale); + } else if (auto e = + std::get_if(&entry)) { + AnimationClip* clip = + animation_player_.has_value() + ? animation_player_->GetClip(e->animation_name) + : nullptr; + if (!clip) { + auto animation = FindAnimationByName(e->animation_name); + if (!animation) { + continue; + } + clip = AddAnimation(animation); + if (!clip) { + continue; + } + } + + clip->Seek(SecondsF(e->time)); + } + } + } + if (animation_player_.has_value()) { animation_player_->Update(); } @@ -345,5 +418,9 @@ bool Node::Render(SceneEncoder& encoder, return true; } +void Node::AddMutation(const MutationLog::Entry& entry) { + mutation_log_.Append(entry); +} + } // namespace scene } // namespace impeller diff --git a/impeller/scene/node.h b/impeller/scene/node.h index 8b79e5f90fd81..2f144397500fd 100644 --- a/impeller/scene/node.h +++ b/impeller/scene/node.h @@ -5,10 +5,13 @@ #pragma once #include +#include #include #include #include "flutter/fml/macros.h" +#include "impeller/base/thread.h" +#include "impeller/base/thread_safety.h" #include "impeller/geometry/matrix.h" #include "impeller/renderer/render_target.h" #include "impeller/renderer/texture.h" @@ -25,6 +28,40 @@ namespace scene { class Node final { public: + class MutationLog { + public: + struct SetTransformEntry { + Matrix transform; + }; + + struct SetAnimationStateEntry { + std::string animation_name; + bool playing = false; + bool loop = false; + Scalar weight = 0; + Scalar time_scale = 1; + }; + + struct SeekAnimationEntry { + std::string animation_name; + float time = 0; + }; + + using Entry = std:: + variant; + + void Append(const Entry& entry); + + private: + std::optional> Flush(); + + RWMutex write_mutex_; + bool dirty_ IPLR_GUARDED_BY(write_mutex_) = false; + std::vector entries_ IPLR_GUARDED_BY(write_mutex_); + + friend Node; + }; + static std::shared_ptr MakeFromFlatbuffer( const fml::Mapping& ipscene_mapping, Allocator& allocator); @@ -44,7 +81,7 @@ class Node final { bool exclude_animation_players = false) const; std::shared_ptr FindAnimationByName(const std::string& name) const; - AnimationClip& AddAnimation(const std::shared_ptr& animation); + AnimationClip* AddAnimation(const std::shared_ptr& animation); void SetLocalTransform(Matrix transform); Matrix GetLocalTransform() const; @@ -63,7 +100,9 @@ class Node final { bool Render(SceneEncoder& encoder, Allocator& allocator, - const Matrix& parent_transform) const; + const Matrix& parent_transform); + + void AddMutation(const MutationLog::Entry& entry); private: void UnpackFromFlatbuffer( @@ -72,6 +111,8 @@ class Node final { const std::vector>& textures, Allocator& allocator); + mutable MutationLog mutation_log_; + Matrix local_transform_; std::string name_; diff --git a/impeller/scene/scene.cc b/impeller/scene/scene.cc index d1c8cae5002b2..ca7202afb04b4 100644 --- a/impeller/scene/scene.cc +++ b/impeller/scene/scene.cc @@ -31,7 +31,7 @@ Node& Scene::GetRoot() { } bool Scene::Render(const RenderTarget& render_target, - const Matrix& camera_transform) const { + const Matrix& camera_transform) { // Collect the render commands from the scene. SceneEncoder encoder; if (!root_.Render(encoder, @@ -57,8 +57,7 @@ bool Scene::Render(const RenderTarget& render_target, return true; } -bool Scene::Render(const RenderTarget& render_target, - const Camera& camera) const { +bool Scene::Render(const RenderTarget& render_target, const Camera& camera) { return Render(render_target, camera.GetTransform(render_target.GetRenderTargetSize())); } diff --git a/impeller/scene/scene.h b/impeller/scene/scene.h index 9ff0745befaa0..210cbd5b850c0 100644 --- a/impeller/scene/scene.h +++ b/impeller/scene/scene.h @@ -28,9 +28,9 @@ class Scene { Node& GetRoot(); bool Render(const RenderTarget& render_target, - const Matrix& camera_transform) const; + const Matrix& camera_transform); - bool Render(const RenderTarget& render_target, const Camera& camera) const; + bool Render(const RenderTarget& render_target, const Camera& camera); private: std::shared_ptr scene_context_; diff --git a/impeller/scene/scene_context.cc b/impeller/scene/scene_context.cc index 284d6da6b3ac1..1ee1a7a5b3733 100644 --- a/impeller/scene/scene_context.cc +++ b/impeller/scene/scene_context.cc @@ -28,6 +28,9 @@ void SceneContextOptions::ApplyToPipelineDescriptor( desc.SetSampleCount(sample_count); desc.SetPrimitiveType(primitive_type); + + desc.SetWindingOrder(WindingOrder::kCounterClockwise); + desc.SetCullMode(CullMode::kBackFace); } SceneContext::SceneContext(std::shared_ptr context) diff --git a/impeller/scene/scene_unittests.cc b/impeller/scene/scene_unittests.cc index f2a048f8a7f4f..d822c1497a8ca 100644 --- a/impeller/scene/scene_unittests.cc +++ b/impeller/scene/scene_unittests.cc @@ -27,8 +27,6 @@ #include "third_party/flatbuffers/include/flatbuffers/verifier.h" #include "third_party/imgui/imgui.h" -// #include "third_party/tinygltf/tiny_gltf.h" - namespace impeller { namespace scene { namespace testing { @@ -126,9 +124,10 @@ TEST_P(SceneTest, TwoTriangles) { auto animation = gltf_scene->FindAnimationByName("Metronome"); ASSERT_NE(animation, nullptr); - AnimationClip& metronome_clip = gltf_scene->AddAnimation(animation); - metronome_clip.SetLoop(true); - metronome_clip.Play(); + AnimationClip* metronome_clip = gltf_scene->AddAnimation(animation); + ASSERT_NE(metronome_clip, nullptr); + metronome_clip->SetLoop(true); + metronome_clip->Play(); auto scene_context = std::make_shared(GetContext()); auto scene = Scene(scene_context); @@ -145,18 +144,123 @@ TEST_P(SceneTest, TwoTriangles) { ImGui::SliderFloat("Weight", &weight, -2, 2); ImGui::Checkbox("Loop", &loop); if (ImGui::Button("Play")) { - metronome_clip.Play(); + metronome_clip->Play(); } if (ImGui::Button("Pause")) { - metronome_clip.Pause(); + metronome_clip->Pause(); } if (ImGui::Button("Stop")) { - metronome_clip.Stop(); + metronome_clip->Stop(); } - metronome_clip.SetPlaybackTimeScale(playback_time_scale); - metronome_clip.SetWeight(weight); - metronome_clip.SetLoop(loop); + metronome_clip->SetPlaybackTimeScale(playback_time_scale); + metronome_clip->SetWeight(weight); + metronome_clip->SetLoop(loop); + } + + ImGui::End(); + Node& node = *scene.GetRoot().GetChildren()[0]; + node.SetLocalTransform(node.GetLocalTransform() * + Matrix::MakeRotation(0.02, {0, 1, 0, 0})); + + static ImVec2 mouse_pos_prev = ImGui::GetMousePos(); + ImVec2 mouse_pos = ImGui::GetMousePos(); + Vector2 mouse_diff = + Vector2(mouse_pos.x - mouse_pos_prev.x, mouse_pos.y - mouse_pos_prev.y); + + static Vector3 position(0, 1, -5); + static Vector3 cam_position = position; + auto strafe = + Vector3(ImGui::IsKeyDown(ImGuiKey_D) - ImGui::IsKeyDown(ImGuiKey_A), + ImGui::IsKeyDown(ImGuiKey_E) - ImGui::IsKeyDown(ImGuiKey_Q), + ImGui::IsKeyDown(ImGuiKey_W) - ImGui::IsKeyDown(ImGuiKey_S)); + position += strafe * 0.5; + cam_position = cam_position.Lerp(position, 0.02); + + // Face towards the +Z direction (+X right, +Y up). + auto camera = Camera::MakePerspective( + /* fov */ Degrees(60), + /* position */ cam_position) + .LookAt( + /* target */ cam_position + Vector3(0, 0, 1), + /* up */ {0, 1, 0}); + + scene.Render(render_target, camera); + return true; + }; + + OpenPlaygroundHere(callback); +} + +TEST_P(SceneTest, Dash) { + auto allocator = GetContext()->GetResourceAllocator(); + + auto mapping = flutter::testing::OpenFixtureAsMapping("dash.glb.ipscene"); + if (!mapping) { + // TODO(bdero): Just skip this playground is the dash asset isn't found. I + // haven't checked it in because it's way too big right now, + // but this is still useful to keep around for debugging + // purposes. + return; + } + ASSERT_NE(mapping, nullptr); + + std::shared_ptr gltf_scene = + Node::MakeFromFlatbuffer(*mapping, *allocator); + ASSERT_NE(gltf_scene, nullptr); + + auto walk_anim = gltf_scene->FindAnimationByName("Walk"); + ASSERT_NE(walk_anim, nullptr); + + AnimationClip* walk_clip = gltf_scene->AddAnimation(walk_anim); + ASSERT_NE(walk_clip, nullptr); + walk_clip->SetLoop(true); + walk_clip->Play(); + + auto run_anim = gltf_scene->FindAnimationByName("Run"); + ASSERT_NE(walk_anim, nullptr); + + AnimationClip* run_clip = gltf_scene->AddAnimation(run_anim); + ASSERT_NE(run_clip, nullptr); + run_clip->SetLoop(true); + run_clip->Play(); + + auto scene_context = std::make_shared(GetContext()); + auto scene = Scene(scene_context); + scene.GetRoot().AddChild(std::move(gltf_scene)); + + Renderer::RenderCallback callback = [&](RenderTarget& render_target) { + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + static Scalar playback_time_scale = 1; + static Scalar walk = 0.5; + static Scalar run = 0.5; + static bool loop = true; + + ImGui::SliderFloat("Playback time scale", &playback_time_scale, -5, 5); + ImGui::SliderFloat("Walk weight", &walk, 0, 1); + ImGui::SliderFloat("Run weight", &run, 0, 1); + ImGui::Checkbox("Loop", &loop); + if (ImGui::Button("Play")) { + walk_clip->Play(); + run_clip->Play(); + } + if (ImGui::Button("Pause")) { + walk_clip->Pause(); + run_clip->Pause(); + } + if (ImGui::Button("Stop")) { + walk_clip->Stop(); + run_clip->Stop(); + } + + walk_clip->SetPlaybackTimeScale(playback_time_scale); + walk_clip->SetWeight(walk); + walk_clip->SetLoop(loop); + + run_clip->SetPlaybackTimeScale(playback_time_scale); + run_clip->SetWeight(run); + run_clip->SetLoop(loop); } ImGui::End(); diff --git a/impeller/scene/skin.cc b/impeller/scene/skin.cc index 4a4c614195289..4407eeb5bf76f 100644 --- a/impeller/scene/skin.cc +++ b/impeller/scene/skin.cc @@ -41,12 +41,14 @@ std::unique_ptr Skin::MakeFromFlatbuffer( } result.inverse_bind_matrices_.reserve(skin.inverse_bind_matrices()->size()); - for (auto matrix : *skin.inverse_bind_matrices()) { - if (!matrix) { - result.inverse_bind_matrices_.push_back(Matrix()); - continue; - } - result.inverse_bind_matrices_.push_back(importer::ToMatrix(*matrix)); + for (size_t matrix_i = 0; matrix_i < skin.inverse_bind_matrices()->size(); + matrix_i++) { + const auto* ip_matrix = skin.inverse_bind_matrices()->Get(matrix_i); + Matrix matrix = ip_matrix ? importer::ToMatrix(*ip_matrix) : Matrix(); + + result.inverse_bind_matrices_.push_back(matrix); + // Overwrite the joint transforms with the inverse bind pose. + result.joints_[matrix_i]->SetGlobalTransform(matrix.Invert()); } return std::make_unique(std::move(result)); diff --git a/lib/ui/experiments/scene.dart b/lib/ui/experiments/scene.dart index 2b2e477a4c41e..abe610caccff7 100644 --- a/lib/ui/experiments/scene.dart +++ b/lib/ui/experiments/scene.dart @@ -22,16 +22,17 @@ class SceneNode extends NativeFieldWrapperClass1 { // encoded paths (replacing ' ' with '%20', for example). We perform // the same encoding here so that users can load assets with the same // key they have written in the pubspec. - final String encodedKey = Uri(path: Uri.encodeFull(assetKey)).path; + final String encodedKey = Uri(path: Uri.encodeFull(assetKey)).path; { - final SceneNode? sceneNode = _ipsceneRegistry[encodedKey]?.target; - if (sceneNode != null) { - return Future.value(sceneNode); + final Future? futureSceneNode = _ipsceneRegistry[encodedKey]?.target; + if (futureSceneNode != null) { + return futureSceneNode; } } final SceneNode sceneNode = SceneNode._create(); - return _futurize((_Callback callback) { + + final Future futureSceneNode = _futurize((_Callback callback) { final String error = sceneNode._initFromAsset(assetKey, callback); if (error.isNotEmpty) { return error; @@ -41,10 +42,11 @@ class SceneNode extends NativeFieldWrapperClass1 { return true; }()); - _ipsceneRegistry[encodedKey] = WeakReference(sceneNode); - return null; }).then((_) => sceneNode); + + _ipsceneRegistry[encodedKey] = WeakReference>(futureSceneNode); + return futureSceneNode; } static SceneNode fromTransform(Float64List matrix4) { @@ -61,8 +63,8 @@ class SceneNode extends NativeFieldWrapperClass1 { _setTransform(matrix4); } - void setAnimationState(String animationName, bool playing, double weight, double timeScale) { - _setAnimationState(animationName, playing, weight, timeScale); + void setAnimationState(String animationName, bool playing, bool loop, double weight, double timeScale) { + _setAnimationState(animationName, playing, loop, weight, timeScale); } void seekAnimation(String animationName, double time) { @@ -73,11 +75,11 @@ class SceneNode extends NativeFieldWrapperClass1 { // SceneNode.fromAsset. It holds weak references to the SceneNodes so that the // case where an in-use ipscene is requested again can be fast, but scenes // that are no longer referenced are not retained because of the cache. - static final Map> _ipsceneRegistry = - >{}; + static final Map>> _ipsceneRegistry = + >>{}; static Future _reinitializeScene(String assetKey) async { - final WeakReference? sceneRef = _ipsceneRegistry == null + final WeakReference>? sceneRef = _ipsceneRegistry == null ? null : _ipsceneRegistry[assetKey]; @@ -87,10 +89,11 @@ class SceneNode extends NativeFieldWrapperClass1 { return; } - final SceneNode? sceneNode = sceneRef.target; - if (sceneNode == null) { + final Future? sceneNodeFuture = sceneRef.target; + if (sceneNodeFuture == null) { return; } + final SceneNode sceneNode = await sceneNodeFuture; await _futurize((_Callback callback) { final String error = sceneNode._initFromAsset(assetKey, callback); @@ -116,10 +119,10 @@ class SceneNode extends NativeFieldWrapperClass1 { @FfiNative, Handle)>('SceneNode::SetTransform') external void _setTransform(Float64List matrix4); - @FfiNative, Handle, Handle, Handle, Handle)>('SceneScene::SetAnimationState') - external void _setAnimationState(String animationName, bool playing, double weight, double timeScale); + @FfiNative, Handle, Bool, Bool, Double, Double)>('SceneNode::SetAnimationState') + external void _setAnimationState(String animationName, bool playing, bool loop, double weight, double timeScale); - @FfiNative, Handle, Handle)>('SceneNode::SeekAnimation') + @FfiNative, Handle, Double)>('SceneNode::SeekAnimation') external void _seekAnimation(String animationName, double time); /// Returns a fresh instance of [SceneShader]. diff --git a/lib/ui/painting/scene/scene_node.cc b/lib/ui/painting/scene/scene_node.cc index cc0b2bae1f04b..914dd2c94a5f6 100644 --- a/lib/ui/painting/scene/scene_node.cc +++ b/lib/ui/painting/scene/scene_node.cc @@ -21,6 +21,7 @@ #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" #include "third_party/tonic/dart_library_natives.h" +#include "third_party/tonic/typed_data/typed_list.h" namespace flutter { @@ -98,13 +99,16 @@ std::string SceneNode::initFromAsset(const std::string& asset_name, return ""; } +static impeller::Matrix ToMatrix(const tonic::Float64List& matrix4) { + return impeller::Matrix(matrix4[0], matrix4[1], matrix4[2], matrix4[3], // + matrix4[4], matrix4[5], matrix4[6], matrix4[7], // + matrix4[8], matrix4[9], matrix4[10], matrix4[11], // + matrix4[12], matrix4[13], matrix4[14], matrix4[15]); +} + void SceneNode::initFromTransform(const tonic::Float64List& matrix4) { node_ = std::make_shared(); - node_->SetLocalTransform( - impeller::Matrix(matrix4[0], matrix4[1], matrix4[2], matrix4[3], // - matrix4[4], matrix4[5], matrix4[6], matrix4[7], // - matrix4[8], matrix4[9], matrix4[10], matrix4[11], // - matrix4[12], matrix4[13], matrix4[14], matrix4[15])); + node_->SetLocalTransform(ToMatrix(matrix4)); } void SceneNode::AddChild(Dart_Handle scene_node_handle) { @@ -121,18 +125,32 @@ void SceneNode::AddChild(Dart_Handle scene_node_handle) { } void SceneNode::SetTransform(const tonic::Float64List& matrix4) { - // TODO(bdero): Implement mutation log. + impeller::scene::Node::MutationLog::SetTransformEntry entry = { + ToMatrix(matrix4)}; + node_->AddMutation(entry); } void SceneNode::SetAnimationState(const std::string& animation_name, bool playing, + bool loop, double weight, double time_scale) { - // TODO(bdero): Implement mutation log. + impeller::scene::Node::MutationLog::SetAnimationStateEntry entry = { + .animation_name = animation_name, + .playing = playing, + .loop = loop, + .weight = static_cast(weight), + .time_scale = static_cast(time_scale), + }; + node_->AddMutation(entry); } void SceneNode::SeekAnimation(const std::string& animation_name, double time) { - // TODO(bdero): Implement mutation log. + impeller::scene::Node::MutationLog::SeekAnimationEntry entry = { + .animation_name = animation_name, + .time = static_cast(time), + }; + node_->AddMutation(entry); } SceneNode::SceneNode() = default; diff --git a/lib/ui/painting/scene/scene_node.h b/lib/ui/painting/scene/scene_node.h index 563b0f9232c55..ee44e801f7de6 100644 --- a/lib/ui/painting/scene/scene_node.h +++ b/lib/ui/painting/scene/scene_node.h @@ -48,6 +48,7 @@ class SceneNode : public RefCountedDartWrappable { void SetAnimationState(const std::string& animation_name, bool playing, + bool loop, double weight, double time_scale);