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

[Impeller Scene] Import animation data #38583

Merged
merged 5 commits into from
Jan 2, 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
Binary file modified impeller/fixtures/two_triangles.glb
Binary file not shown.
10 changes: 9 additions & 1 deletion impeller/scene/importer/conversions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ Color ToColor(const fb::Color& c) {
/// Impeller -> Flatbuffers
///

std::unique_ptr<fb::Matrix> ToFBMatrix(const Matrix& m) {
fb::Matrix ToFBMatrix(const Matrix& m) {
auto array = std::array<Scalar, 16>{m.m[0], m.m[1], m.m[2], m.m[3], //
m.m[4], m.m[5], m.m[6], m.m[7], //
m.m[8], m.m[9], m.m[10], m.m[11], //
m.m[12], m.m[13], m.m[14], m.m[15]};
return fb::Matrix(array);
}

std::unique_ptr<fb::Matrix> ToFBMatrixUniquePtr(const Matrix& m) {
auto array = std::array<Scalar, 16>{m.m[0], m.m[1], m.m[2], m.m[3], //
m.m[4], m.m[5], m.m[6], m.m[7], //
m.m[8], m.m[9], m.m[10], m.m[11], //
Expand Down
4 changes: 3 additions & 1 deletion impeller/scene/importer/conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Color ToColor(const fb::Color& c);
/// Impeller -> Flatbuffers
///

std::unique_ptr<fb::Matrix> ToFBMatrix(const Matrix& m);
fb::Matrix ToFBMatrix(const Matrix& m);

std::unique_ptr<fb::Matrix> ToFBMatrixUniquePtr(const Matrix& m);

fb::Vec2 ToFBVec2(const Vector2 v);

Expand Down
168 changes: 163 additions & 5 deletions impeller/scene/importer/importer_gltf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ static void ProcessNode(const tinygltf::Model& gltf,
if (in_node.translation.size() == 3) {
transform = transform * Matrix::MakeTranslation(
{static_cast<Scalar>(in_node.translation[0]),
static_cast<Scalar>(in_node.translation[0]),
static_cast<Scalar>(in_node.translation[0])});
static_cast<Scalar>(in_node.translation[1]),
static_cast<Scalar>(in_node.translation[2])});
}
if (in_node.rotation.size() == 4) {
transform = transform * Matrix::MakeRotation(Quaternion(
Expand All @@ -226,7 +226,7 @@ static void ProcessNode(const tinygltf::Model& gltf,
}
transform = ToMatrix(in_node.matrix);
}
out_node.transform = ToFBMatrix(transform);
out_node.transform = ToFBMatrixUniquePtr(transform);

//---------------------------------------------------------------------------
/// Static meshes.
Expand All @@ -242,13 +242,42 @@ static void ProcessNode(const tinygltf::Model& gltf,
out_node.mesh_primitives.push_back(std::move(mesh_primitive));
}
}

//---------------------------------------------------------------------------
/// Skin.
///

if (WithinRange(in_node.skin, gltf.skins.size())) {
auto& skin = gltf.skins[in_node.skin];

auto ipskin = std::make_unique<fb::SkinT>();
ipskin->joints = skin.joints;
{
std::vector<fb::Matrix> matrices;
auto& matrix_accessor = gltf.accessors[skin.inverseBindMatrices];
auto& matrix_view = gltf.bufferViews[matrix_accessor.bufferView];
auto& matrix_buffer = gltf.buffers[matrix_view.buffer];
for (size_t matrix_i = 0; matrix_i < matrix_accessor.count; matrix_i++) {
auto* s = reinterpret_cast<const float*>(
matrix_buffer.data.data() + matrix_view.byteOffset +
matrix_accessor.ByteStride(matrix_view) * matrix_i);
Matrix m(s[0], s[1], s[2], s[3], //
s[4], s[5], s[6], s[7], //
s[8], s[9], s[10], s[11], //
s[12], s[13], s[14], s[15]);
matrices.push_back(ToFBMatrix(m));
}
ipskin->inverse_bind_matrices = std::move(matrices);
}
ipskin->skeleton = skin.skeleton;
out_node.skin = std::move(ipskin);
}
}

static void ProcessTexture(const tinygltf::Model& gltf,
const tinygltf::Texture& in_texture,
fb::TextureT& out_texture) {
if (in_texture.source < 0 ||
in_texture.source >= static_cast<int>(gltf.images.size())) {
if (!WithinRange(in_texture.source, gltf.images.size())) {
return;
}
auto& image = gltf.images[in_texture.source];
Expand Down Expand Up @@ -283,6 +312,128 @@ static void ProcessTexture(const tinygltf::Model& gltf,
out_texture.uri = image.uri;
}

static void ProcessAnimation(const tinygltf::Model& gltf,
const tinygltf::Animation& in_animation,
fb::AnimationT& out_animation) {
out_animation.name = in_animation.name;

std::vector<std::unique_ptr<impeller::fb::ChannelT>> channels;
for (auto& in_channel : in_animation.channels) {
auto out_channel = std::make_unique<fb::ChannelT>();

out_channel->node = in_channel.target_node;
auto& sampler = in_animation.samplers[in_channel.sampler];

/// Keyframe times.
auto& times_accessor = gltf.accessors[sampler.input];
if (times_accessor.count <= 0) {
continue; // Nothing to record.
}
{
auto& times_bufferview = gltf.bufferViews[times_accessor.bufferView];
auto& times_buffer = gltf.buffers[times_bufferview.buffer];
if (times_accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) {
std::cerr << "Unexpected component type \""
<< times_accessor.componentType
<< "\" for animation channel times accessor. Skipping."
<< std::endl;
continue;
}
if (times_accessor.type != TINYGLTF_TYPE_SCALAR) {
std::cerr << "Unexpected type \"" << times_accessor.type
<< "\" for animation channel times accessor. Skipping."
<< std::endl;
continue;
}
for (size_t time_i = 0; time_i < times_accessor.count; time_i++) {
const float* time_p = reinterpret_cast<const float*>(
times_buffer.data.data() + times_bufferview.byteOffset +
times_accessor.ByteStride(times_bufferview) * time_i);
out_channel->timeline.push_back(*time_p);
}
}

/// Keyframe values.
auto& values_accessor = gltf.accessors[sampler.output];
if (values_accessor.count != times_accessor.count) {
std::cerr << "Mismatch between time and value accessors for animation "
"channel. Skipping."
<< std::endl;
continue;
}
{
auto& values_bufferview = gltf.bufferViews[values_accessor.bufferView];
auto& values_buffer = gltf.buffers[values_bufferview.buffer];
if (values_accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) {
std::cerr << "Unexpected component type \""
<< values_accessor.componentType
<< "\" for animation channel values accessor. Skipping."
<< std::endl;
continue;
}
if (in_channel.target_path == "translation") {
if (values_accessor.type != TINYGLTF_TYPE_VEC3) {
std::cerr << "Unexpected type \"" << values_accessor.type
<< "\" for animation channel \"translation\" accessor. "
"Skipping."
<< std::endl;
continue;
}
fb::TranslationKeyframesT keyframes;
for (size_t value_i = 0; value_i < values_accessor.count; value_i++) {
const float* value_p = reinterpret_cast<const float*>(
values_buffer.data.data() + values_bufferview.byteOffset +
values_accessor.ByteStride(values_bufferview) * value_i);
keyframes.values.push_back(
fb::Vec3(value_p[0], value_p[1], value_p[2]));
}
out_channel->keyframes.Set(std::move(keyframes));
} else if (in_channel.target_path == "rotation") {
if (values_accessor.type != TINYGLTF_TYPE_VEC4) {
std::cerr << "Unexpected type \"" << values_accessor.type
<< "\" for animation channel \"rotation\" accessor. "
"Skipping."
<< std::endl;
continue;
}
fb::RotationKeyframesT keyframes;
for (size_t value_i = 0; value_i < values_accessor.count; value_i++) {
const float* value_p = reinterpret_cast<const float*>(
values_buffer.data.data() + values_bufferview.byteOffset +
values_accessor.ByteStride(values_bufferview) * value_i);
keyframes.values.push_back(
fb::Vec4(value_p[0], value_p[1], value_p[2], value_p[3]));
}
out_channel->keyframes.Set(std::move(keyframes));
} else if (in_channel.target_path == "scale") {
if (values_accessor.type != TINYGLTF_TYPE_VEC3) {
std::cerr << "Unexpected type \"" << values_accessor.type
<< "\" for animation channel \"scale\" accessor. "
"Skipping."
<< std::endl;
continue;
}
fb::ScaleKeyframesT keyframes;
for (size_t value_i = 0; value_i < values_accessor.count; value_i++) {
const float* value_p = reinterpret_cast<const float*>(
values_buffer.data.data() + values_bufferview.byteOffset +
values_accessor.ByteStride(values_bufferview) * value_i);
keyframes.values.push_back(
fb::Vec3(value_p[0], value_p[1], value_p[2]));
}
out_channel->keyframes.Set(std::move(keyframes));
} else {
std::cerr << "Unsupported animation channel target path \""
<< in_channel.target_path << "\". Skipping." << std::endl;
continue;
}
}

channels.push_back(std::move(out_channel));
}
out_animation.channels = std::move(channels);
}

bool ParseGLTF(const fml::Mapping& source_mapping, fb::SceneT& out_scene) {
tinygltf::Model gltf;

Expand Down Expand Up @@ -319,6 +470,13 @@ bool ParseGLTF(const fml::Mapping& source_mapping, fb::SceneT& out_scene) {
out_scene.nodes.push_back(std::move(node));
}

for (size_t animation_i = 0; animation_i < gltf.animations.size();
animation_i++) {
auto animation = std::make_unique<fb::AnimationT>();
ProcessAnimation(gltf, gltf.animations[animation_i], *animation);
out_scene.animations.push_back(std::move(animation));
}

return true;
}

Expand Down
15 changes: 14 additions & 1 deletion impeller/scene/importer/importer_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ TEST(ImporterTest, CanParseSkinnedGLTF) {
ASSERT_VECTOR3_NEAR(normal, Vector3(0, 0, 1));

Vector4 tangent = ToVector4(vertex.vertex().tangent());
ASSERT_VECTOR4_NEAR(tangent, Vector4(0, 0, 0, 1));
ASSERT_VECTOR4_NEAR(tangent, Vector4(1, 0, 0, -1));

Vector2 texture_coords = ToVector2(vertex.vertex().texture_coords());
ASSERT_POINT_NEAR(texture_coords, Vector2(0, 1));
Expand All @@ -104,6 +104,19 @@ TEST(ImporterTest, CanParseSkinnedGLTF) {

Vector4 weights = ToVector4(vertex.weights());
ASSERT_COLOR_NEAR(weights, Vector4(1, 0, 0, 0));

ASSERT_EQ(scene.animations.size(), 2u);
ASSERT_EQ(scene.animations[0]->name, "Idle");
ASSERT_EQ(scene.animations[1]->name, "Metronome");
ASSERT_EQ(scene.animations[1]->channels.size(), 6u);
auto& channel = scene.animations[1]->channels[4];
ASSERT_EQ(channel->keyframes.type, fb::Keyframes::RotationKeyframes);
auto* keyframes = channel->keyframes.AsRotationKeyframes();
ASSERT_EQ(keyframes->values.size(), 40u);
ASSERT_VECTOR4_NEAR(ToVector4(keyframes->values[0]),
Vector4(0.653281, 0.270598, -0.270598, 0.653281));
ASSERT_VECTOR4_NEAR(ToVector4(keyframes->values[10]),
Vector4(0.425122, 0.565041, -0.565041, 0.425122));
}

} // namespace testing
Expand Down
6 changes: 3 additions & 3 deletions impeller/scene/importer/scene.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ table Animation {
}

table Skin {
joints: [int]; // Index into `Scene`->`nodes`.
joints: [int]; // Indices into `Scene`->`nodes`.
inverse_bind_matrices: [Matrix];
/// The root joint of the skeleton.
skeleton: int; // Index into `Scene`->`nodes`.
Expand All @@ -180,14 +180,14 @@ struct Matrix {

table Node {
name: string;
children: [int]; // Index into `Scene`->`nodes`.
children: [int]; // Indices into `Scene`->`nodes`.
transform: Matrix;
mesh_primitives: [MeshPrimitive];
skin: Skin;
}

table Scene {
children: [int]; // Index into `Scene`->`nodes`.
children: [int]; // Indices into `Scene`->`nodes`.
nodes: [Node];
textures: [Texture]; // Textures may be reused across different materials.
animations: [Animation];
Expand Down
30 changes: 27 additions & 3 deletions impeller/scene/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

#include "impeller/scene/node.h"

#include <inttypes.h>
#include <atomic>
#include <memory>

#include "flutter/fml/logging.h"
#include "impeller/base/strings.h"
#include "impeller/base/validation.h"
#include "impeller/geometry/matrix.h"
#include "impeller/scene/importer/conversions.h"
Expand All @@ -18,6 +21,8 @@
namespace impeller {
namespace scene {

static std::atomic_uint64_t kNextNodeID = 0;

std::shared_ptr<Node> Node::MakeFromFlatbuffer(
const fml::Mapping& ipscene_mapping,
Allocator& allocator) {
Expand Down Expand Up @@ -155,8 +160,7 @@ void Node::UnpackFromFlatbuffer(
const std::vector<std::shared_ptr<Node>>& scene_nodes,
const std::vector<std::shared_ptr<Texture>>& textures,
Allocator& allocator) {
/// Transform.

name_ = source_node.name()->str();
SetLocalTransform(importer::ToMatrix(*source_node.transform()));

/// Meshes.
Expand Down Expand Up @@ -190,7 +194,7 @@ void Node::UnpackFromFlatbuffer(
}
}

Node::Node() = default;
Node::Node() : name_(SPrintF("__node%" PRIu64, kNextNodeID++)){};

Node::~Node() = default;

Expand All @@ -202,6 +206,26 @@ Node::Node(Node&& node) = default;

Node& Node::operator=(Node&& node) = default;

const std::string& Node::GetName() const {
return name_;
}

void Node::SetName(const std::string& new_name) {
name_ = new_name;
}

std::shared_ptr<Node> Node::FindNodeByName(const std::string& name) const {
for (auto& child : children_) {
if (child->GetName() == name) {
return child;
}
if (auto found = child->FindNodeByName(name)) {
return found;
}
}
return nullptr;
}

void Node::SetLocalTransform(Matrix transform) {
local_transform_ = transform;
}
Expand Down
7 changes: 7 additions & 0 deletions impeller/scene/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <memory>
#include <optional>
#include <vector>

#include "flutter/fml/macros.h"
Expand Down Expand Up @@ -33,6 +34,11 @@ class Node final {
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see the usage of FindNodeByName and can't tell if it matters that the lookup is linear with a string search. Its probably fine though.

Copy link
Member Author

@bdero bdero Jan 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used for animation channel binding when a new AnimationClip is created in #38595 (renamed to FindChildByName).

I haven't finished thinking these through, but a few options for improvement down the road:

  • If we keep this name-only lookup, we could capture the common case by pre-generating binding state at deserialization time which will get used only if the user applies the animation to the deserialized node it came from (probably overwhelmingly common).
  • Another option (which I like better) would be to form channel lookup paths at deserialization time and store them in the Animation instead of flat node names. This makes Animation retargeting less forgiving, but also makes name collisions less likely and prevents us from having to walk the whole subscene for every channel. We could even add more advanced pathing features later like globbing or indexing.


void SetLocalTransform(Matrix transform);
Matrix GetLocalTransform() const;

Expand All @@ -57,6 +63,7 @@ class Node final {
const std::vector<std::shared_ptr<Texture>>& textures,
Allocator& allocator);

std::string name_;
bool is_root_ = false;
Node* parent_ = nullptr;
std::vector<std::shared_ptr<Node>> children_;
Expand Down