Lagrange
skinning.h
1/*
2 * Copyright 2023 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <lagrange/Attribute.h>
15#include <lagrange/SurfaceMesh.h>
16#include <lagrange/attribute_names.h>
17#include <lagrange/utils/assert.h>
18#include <lagrange/views.h>
19
20#include <Eigen/Core>
21#include <numeric>
22#include <vector>
23
36namespace lagrange::internal {
37
48template <typename Scalar, typename Index>
51 const lagrange::Attribute<Scalar>& original_vertices,
52 const std::vector<Eigen::Transform<Scalar, 3, Eigen::TransformTraits::Affine>>& transforms,
53 const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& weights,
54 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& weight_complement = {})
55{
56 la_runtime_assert((size_t)weights.cols() == transforms.size());
57 la_runtime_assert(weights.rows() == mesh.get_num_vertices());
59 (weight_complement.rows() == 0) || (weight_complement.rows() == mesh.get_num_vertices()));
60
61 const Index num_vertices = mesh.get_num_vertices();
62 const size_t num_handles = transforms.size();
63
64 auto original_view = lagrange::matrix_view(original_vertices);
65 auto verts = lagrange::vertex_ref(mesh);
66 verts.setZero();
67
68 for (Index v = 0; v < num_vertices; ++v) {
69 const Eigen::Matrix<Scalar, 3, 1> orig = original_view.row(v);
70 Scalar weight_sum = 0;
71 for (size_t h = 0; h < num_handles; ++h) {
72 Scalar weight = weights(v, h);
73 if (weight > 0) {
74 verts.row(v) += weight * (transforms[h] * orig);
75 }
76 weight_sum += weight;
77 }
78 if (weight_complement.rows() > 0 && weight_complement(v) > 0) {
79 verts.row(v) += (weight_complement(v) * orig);
80 weight_sum += weight_complement(v);
81 }
82 if (weight_sum > 0) {
83 verts.row(v) /= weight_sum;
84 }
85 }
86}
87
96template <typename Scalar, typename Index>
99 const Attribute<Scalar>& original_vertices,
100 const std::vector<Eigen::Transform<Scalar, 3, Eigen::TransformTraits::Affine>>& transforms)
101{
102 auto weight_id = mesh.get_attribute_id(AttributeName::weight);
104
105 auto& weight_attr = mesh.template get_attribute<Scalar>(weight_id);
106
107 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> weights = matrix_view(weight_attr);
108 skinning_deform(mesh, original_vertices, transforms, weights);
109}
110
111
112template <typename Scalar, typename Index>
114{
119 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> weights;
120
125 Eigen::Matrix<Index, Eigen::Dynamic, Eigen::Dynamic> indices;
126};
127
138template <typename Scalar, typename Index>
140 const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& weights,
141 int n,
142 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& weight_complement =
143 Eigen::Matrix<Scalar, Eigen::Dynamic, 1>())
144{
145 const Index num_vertices = Index(weights.rows());
146 const Index num_handles = Index(weights.cols());
147 Index num_implicit_handle = 0;
148
149 la_runtime_assert(num_handles > 0);
150 if (weight_complement.rows() > 0 &&
151 weight_complement.maxCoeff() > std::numeric_limits<Scalar>::epsilon()) {
152 la_runtime_assert(weight_complement.rows() == num_vertices);
153 // the weights do not sum up to 1, so we assume there is an implicit fixed handle
154 num_implicit_handle = 1;
155 }
156
157 // Max number of handles
158 const Index num_handles_max = Index(n) - num_implicit_handle;
159 const Index num_handles_used = std::min(num_handles, num_handles_max);
160
161
163 result.weights.resize(num_vertices, n);
164 result.indices.resize(num_vertices, n);
165 if (num_handles < Index(n)) {
166 // only need to initialize in this case
167 result.weights.setZero();
168 result.indices.setZero();
169 }
170
171 if (num_handles <= num_handles_max) {
172 // we have more handle slots than handles, so just copy them over
173 la_runtime_assert(num_handles == num_handles_used);
174 for (Index i = 0; i < num_vertices; ++i) {
175 for (Index j = 0; j < num_handles; ++j) {
176 result.indices(i, j) = j;
177 result.weights(i, j) = weights(i, j);
178 }
179 }
180
181 } else {
182 // we only pick some of the handles, find and copy the n most important ones
183
184 for (Index i = 0; i < num_vertices; ++i) {
185 std::vector<Index> tmp(num_handles);
186 std::iota(tmp.begin(), tmp.end(), 0);
187 std::partial_sort(
188 tmp.begin(),
189 tmp.begin() + num_handles_used,
190 tmp.end(),
191 [&weights, i](const Index a, const Index b) -> bool {
192 return weights(i, a) > weights(i, b);
193 });
194
195 for (Index j = 0; j < num_handles_used; ++j) {
196 result.indices(i, j) = tmp[j];
197 result.weights(i, j) = weights(i, tmp[j]);
198 }
199 }
200 }
201
202 if (num_implicit_handle > 0) {
203 // add the implicit handle
204 for (Index i = 0; i < num_vertices; ++i) {
205 result.indices(i, num_handles_used) = num_handles_used;
206 result.weights(i, num_handles_used) = weight_complement(i);
207 }
208 }
209
210 // normalize if we changed the active handles
211 if (num_handles > num_handles_max) {
212 for (Index i = 0; i < num_vertices; ++i) {
213 Scalar s = result.weights.row(i).sum();
214 la_runtime_assert(s > 0);
215 result.weights.row(i) /= s;
216 }
217 }
218
219 return result;
220}
221
230template <typename Scalar, typename Index, typename WeightScalar = Scalar>
233 const Eigen::Matrix<WeightScalar, Eigen::Dynamic, Eigen::Dynamic>& weights)
234{
235 return mesh.template create_attribute<WeightScalar>(
239 weights.cols(),
240 {weights.data(), size_t(weights.size())});
241}
242
252template <
253 typename Scalar,
254 typename Index,
255 typename WeightScalar = Scalar,
256 typename WeightIndex = Index>
257std::pair<lagrange::AttributeId, lagrange::AttributeId> weights_to_indexed_mesh_attribute(
259 const Eigen::Matrix<WeightScalar, Eigen::Dynamic, Eigen::Dynamic>& weights,
260 int n)
261{
262 auto result = skinning_extract_n<WeightScalar, WeightIndex>(weights, n);
263 auto bone_id = mesh.template create_attribute<WeightIndex>(
267 result.indices.cols(),
268 {result.indices.data(), size_t(result.indices.size())});
269
270 auto weight_id = mesh.template create_attribute<WeightScalar>(
274 result.weights.cols(),
275 {result.weights.data(), size_t(result.weights.size())});
276 return {bone_id, weight_id};
277}
278
279
280} // namespace lagrange::internal
Derived attribute class that stores the actual information.
Definition: Attribute.h:153
uint32_t AttributeId
Identified to be used to access an attribute.
Definition: AttributeFwd.h:73
constexpr AttributeId invalid_attribute_id()
Invalid attribute id.
Definition: AttributeFwd.h:76
@ Vector
Mesh attribute can have any number of channels (including 1 channel).
@ Vertex
Per-vertex mesh attributes.
Definition: AttributeFwd.h:28
ConstRowMatrixView< ValueType > matrix_view(const Attribute< ValueType > &attribute)
Returns a read-only view of a given attribute in the form of an Eigen matrix.
Definition: views.cpp:35
RowMatrixView< Scalar > vertex_ref(SurfaceMesh< Scalar, Index > &mesh)
Returns a writable view of the mesh vertices in the form of an Eigen matrix.
Definition: views.cpp:150
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
nullptr_t, size_t, ptrdiff_t basic_ostream bad_weak_ptr extent, remove_extent, is_array,...
Definition: attribute_string_utils.h:21
lagrange::AttributeId weights_to_mesh_attribute(SurfaceMesh< Scalar, Index > &mesh, const Eigen::Matrix< WeightScalar, Eigen::Dynamic, Eigen::Dynamic > &weights)
Imports the weights matrix as weight attributes of the mesh.
Definition: skinning.h:231
std::pair< lagrange::AttributeId, lagrange::AttributeId > weights_to_indexed_mesh_attribute(SurfaceMesh< Scalar, Index > &mesh, const Eigen::Matrix< WeightScalar, Eigen::Dynamic, Eigen::Dynamic > &weights, int n)
Imports the weights matrix as indexed weight attributes of the mesh.
Definition: skinning.h:257
void skinning_deform(SurfaceMesh< Scalar, Index > &mesh, const lagrange::Attribute< Scalar > &original_vertices, const std::vector< Eigen::Transform< Scalar, 3, Eigen::TransformTraits::Affine > > &transforms, const Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &weights, const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &weight_complement={})
Performs linear blend skinning deformation on a mesh.
Definition: skinning.h:49
SkinningExtractNResult< Scalar, Index > skinning_extract_n(const Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > &weights, int n, const Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &weight_complement=Eigen::Matrix< Scalar, Eigen::Dynamic, 1 >())
From a weight matrix |V| x |H|, constructs a weight matrix |V| x n, where n is an arbitrary contraint...
Definition: skinning.h:139
static constexpr std::string_view indexed_weight
Indexed skinning weights, with a fixed number (typically 4) specified for each vertex.
Definition: attribute_names.h:69
static constexpr std::string_view weight
Skinning weights, with all joints specified for each vertex.
Definition: attribute_names.h:63
static constexpr std::string_view indexed_joint
Indexed skinning index, with a fixed number (typically 4) specified for each vertex.
Definition: attribute_names.h:75
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > weights
|V| x n weights (scalar).
Definition: skinning.h:119
Eigen::Matrix< Index, Eigen::Dynamic, Eigen::Dynamic > indices
|V| x n indices (index).
Definition: skinning.h:125