Lagrange
Loading...
Searching...
No Matches
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
35
36namespace lagrange::internal {
37
49template <typename Scalar, typename Index>
52 const lagrange::Attribute<Scalar>& original_vertices,
53 const std::vector<Eigen::Transform<Scalar, 3, Eigen::TransformTraits::Affine>>& transforms,
54 const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& weights,
55 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& weight_complement = {})
56{
57 la_runtime_assert((size_t)weights.cols() == transforms.size());
58 la_runtime_assert(weights.rows() == mesh.get_num_vertices());
60 (weight_complement.rows() == 0) || (weight_complement.rows() == mesh.get_num_vertices()));
61
62 const Index num_vertices = mesh.get_num_vertices();
63 const size_t num_handles = transforms.size();
64
65 auto original_view = lagrange::matrix_view(original_vertices);
66 auto verts = lagrange::vertex_ref(mesh);
67 verts.setZero();
68
69 for (Index v = 0; v < num_vertices; ++v) {
70 const Eigen::Matrix<Scalar, 3, 1> orig = original_view.row(v);
71 Scalar weight_sum = 0;
72 for (size_t h = 0; h < num_handles; ++h) {
73 Scalar weight = weights(v, h);
74 if (weight > 0) {
75 verts.row(v) += weight * (transforms[h] * orig);
76 }
77 weight_sum += weight;
78 }
79 if (weight_complement.rows() > 0 && weight_complement(v) > 0) {
80 verts.row(v) += (weight_complement(v) * orig);
81 weight_sum += weight_complement(v);
82 }
83 if (weight_sum > 0) {
84 verts.row(v) /= weight_sum;
85 }
86 }
87}
88
97template <typename Scalar, typename Index>
100 const Attribute<Scalar>& original_vertices,
101 const std::vector<Eigen::Transform<Scalar, 3, Eigen::TransformTraits::Affine>>& transforms)
102{
103 auto weight_id = mesh.get_attribute_id(AttributeName::weight);
105
106 auto& weight_attr = mesh.template get_attribute<Scalar>(weight_id);
107
108 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> weights = matrix_view(weight_attr);
109 skinning_deform(mesh, original_vertices, transforms, weights);
110}
111
112
113template <typename Scalar, typename Index>
115{
120 Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> weights;
121
126 Eigen::Matrix<Index, Eigen::Dynamic, Eigen::Dynamic> indices;
127};
128
140template <typename Scalar, typename Index>
142 const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& weights,
143 int n,
144 const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& weight_complement =
145 Eigen::Matrix<Scalar, Eigen::Dynamic, 1>())
146{
147 const Index num_vertices = Index(weights.rows());
148 const Index num_handles = Index(weights.cols());
149 Index num_implicit_handle = 0;
150
151 la_runtime_assert(num_handles > 0);
152 if (weight_complement.rows() > 0 &&
153 weight_complement.maxCoeff() > std::numeric_limits<Scalar>::epsilon()) {
154 la_runtime_assert(weight_complement.rows() == num_vertices);
155 // the weights do not sum up to 1, so we assume there is an implicit fixed handle
156 num_implicit_handle = 1;
157 }
158
159 // Max number of handles
160 const Index num_handles_max = Index(n) - num_implicit_handle;
161 const Index num_handles_used = std::min(num_handles, num_handles_max);
162
163
165 result.weights.resize(num_vertices, n);
166 result.indices.resize(num_vertices, n);
167 if (num_handles < Index(n)) {
168 // only need to initialize in this case
169 result.weights.setZero();
170 result.indices.setZero();
171 }
172
173 if (num_handles <= num_handles_max) {
174 // we have more handle slots than handles, so just copy them over
175 la_runtime_assert(num_handles == num_handles_used);
176 for (Index i = 0; i < num_vertices; ++i) {
177 for (Index j = 0; j < num_handles; ++j) {
178 result.indices(i, j) = j;
179 result.weights(i, j) = weights(i, j);
180 }
181 }
182
183 } else {
184 // we only pick some of the handles, find and copy the n most important ones
185
186 for (Index i = 0; i < num_vertices; ++i) {
187 std::vector<Index> tmp(num_handles);
188 std::iota(tmp.begin(), tmp.end(), 0);
189 std::partial_sort(
190 tmp.begin(),
191 tmp.begin() + num_handles_used,
192 tmp.end(),
193 [&weights, i](const Index a, const Index b) -> bool {
194 return weights(i, a) > weights(i, b);
195 });
196
197 for (Index j = 0; j < num_handles_used; ++j) {
198 result.indices(i, j) = tmp[j];
199 result.weights(i, j) = weights(i, tmp[j]);
200 }
201 }
202 }
203
204 if (num_implicit_handle > 0) {
205 // add the implicit handle
206 for (Index i = 0; i < num_vertices; ++i) {
207 result.indices(i, num_handles_used) = num_handles_used;
208 result.weights(i, num_handles_used) = weight_complement(i);
209 }
210 }
211
212 // normalize if we changed the active handles
213 if (num_handles > num_handles_max) {
214 for (Index i = 0; i < num_vertices; ++i) {
215 Scalar s = result.weights.row(i).sum();
216 la_runtime_assert(s > 0);
217 result.weights.row(i) /= s;
218 }
219 }
220
221 return result;
222}
223
232template <typename Scalar, typename Index, typename WeightScalar = Scalar>
235 const Eigen::Matrix<WeightScalar, Eigen::Dynamic, Eigen::Dynamic>& weights)
236{
237 return mesh.template create_attribute<WeightScalar>(
241 weights.cols(),
242 {weights.data(), size_t(weights.size())});
243}
244
254template <
255 typename Scalar,
256 typename Index,
257 typename WeightScalar = Scalar,
258 typename WeightIndex = Index>
259std::pair<lagrange::AttributeId, lagrange::AttributeId> weights_to_indexed_mesh_attribute(
261 const Eigen::Matrix<WeightScalar, Eigen::Dynamic, Eigen::Dynamic>& weights,
262 int n)
263{
264 auto result = skinning_extract_n<WeightScalar, WeightIndex>(weights, n);
265 auto bone_id = mesh.template create_attribute<WeightIndex>(
269 result.indices.cols(),
270 {result.indices.data(), size_t(result.indices.size())});
271
272 auto weight_id = mesh.template create_attribute<WeightScalar>(
276 result.weights.cols(),
277 {result.weights.data(), size_t(result.weights.size())});
278 return {bone_id, weight_id};
279}
280
281
282} // namespace lagrange::internal
Derived attribute class that stores the actual information.
Definition Attribute.h:153
A general purpose polygonal mesh class.
Definition SurfaceMesh.h:66
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).
Definition AttributeFwd.h:55
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
@ 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:174
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:233
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:259
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:50
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:141
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:120
Eigen::Matrix< Index, Eigen::Dynamic, Eigen::Dynamic > indices
|V| x n indices (index).
Definition skinning.h:126