blob: b3ea96993a6e7514c4b932f68e9b1cbc313a79a4 [file] [log] [blame]
// 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/skin.h"
#include <cmath>
#include <memory>
#include <optional>
#include <vector>
#include "flutter/fml/logging.h"
#include "impeller/core/allocator.h"
#include "impeller/scene/importer/conversions.h"
namespace impeller {
namespace scene {
std::unique_ptr<Skin> Skin::MakeFromFlatbuffer(
const fb::Skin& skin,
const std::vector<std::shared_ptr<Node>>& scene_nodes) {
if (!skin.joints() || !skin.inverse_bind_matrices() ||
skin.joints()->size() != skin.inverse_bind_matrices()->size()) {
VALIDATION_LOG << "Skin data is missing joints or bind matrices.";
return nullptr;
}
Skin result;
result.joints_.reserve(skin.joints()->size());
for (auto joint : *skin.joints()) {
if (joint < 0 || static_cast<size_t>(joint) > scene_nodes.size()) {
VALIDATION_LOG << "Skin joint index out of range.";
result.joints_.push_back(nullptr);
continue;
}
if (scene_nodes[joint]) {
scene_nodes[joint]->SetIsJoint(true);
}
result.joints_.push_back(scene_nodes[joint]);
}
result.inverse_bind_matrices_.reserve(skin.inverse_bind_matrices()->size());
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<Skin>(std::move(result));
}
Skin::Skin() = default;
Skin::~Skin() = default;
Skin::Skin(Skin&&) = default;
Skin& Skin::operator=(Skin&&) = default;
std::shared_ptr<Texture> Skin::GetJointsTexture(Allocator& allocator) {
// Each joint has a matrix. 1 matrix = 16 floats. 1 pixel = 4 floats.
// Therefore, each joint needs 4 pixels.
auto required_pixels = joints_.size() * 4;
auto dimension_size = std::max(
2u,
Allocation::NextPowerOfTwoSize(std::ceil(std::sqrt(required_pixels))));
impeller::TextureDescriptor texture_descriptor;
texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
texture_descriptor.format = PixelFormat::kR32G32B32A32Float;
texture_descriptor.size = {dimension_size, dimension_size};
texture_descriptor.mip_count = 1u;
auto result = allocator.CreateTexture(texture_descriptor);
result->SetLabel("Joints Texture");
if (!result) {
FML_LOG(ERROR) << "Could not create joint texture.";
return nullptr;
}
std::vector<Matrix> joints;
joints.resize(result->GetSize().Area() / 4, Matrix());
FML_DCHECK(joints.size() >= joints_.size());
for (size_t joint_i = 0; joint_i < joints_.size(); joint_i++) {
const Node* joint = joints_[joint_i].get();
if (!joint) {
// When a joint is missing, just let it remain as an identity matrix.
continue;
}
// Compute a model space matrix for the joint by walking up the bones to the
// skeleton root.
while (joint && joint->IsJoint()) {
joints[joint_i] = joint->GetLocalTransform() * joints[joint_i];
joint = joint->GetParent();
}
// Get the joint transform relative to the default pose of the bone by
// incorporating the joint's inverse bind matrix. The inverse bind matrix
// transforms from model space to the default pose space of the joint. The
// result is a model space matrix that only captures the difference between
// the joint's default pose and the joint's current pose in the scene. This
// is necessary because the skinned model's vertex positions (which _define_
// the default pose) are all in model space.
joints[joint_i] = joints[joint_i] * inverse_bind_matrices_[joint_i];
}
if (!result->SetContents(reinterpret_cast<uint8_t*>(joints.data()),
joints.size() * sizeof(Matrix))) {
FML_LOG(ERROR) << "Could not set contents of joint texture.";
return nullptr;
}
return result;
}
} // namespace scene
} // namespace impeller