Add Chromium-only Blender WebEngine parity work
This commit is contained in:
42
blender-5.2.0/intern/slim/CMakeLists.txt
Normal file
42
blender-5.2.0/intern/slim/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
# SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Derived from `libigl`, a simple C++ geometry processing library.
|
||||
|
||||
set(INC
|
||||
.
|
||||
../../source/blender/blenlib
|
||||
../guardedalloc
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
slim_matrix_transfer.h
|
||||
|
||||
intern/area_compensation.cpp
|
||||
intern/area_compensation.h
|
||||
intern/cotmatrix.h
|
||||
intern/doublearea.h
|
||||
intern/edge_lengths.h
|
||||
intern/flip_avoiding_line_search.h
|
||||
intern/geometry_data_retrieval.cpp
|
||||
intern/geometry_data_retrieval.h
|
||||
intern/least_squares_relocator.cpp
|
||||
intern/least_squares_relocator.h
|
||||
intern/slim.cpp
|
||||
intern/slim.h
|
||||
intern/slim_matrix_transfer.cpp
|
||||
intern/slim_parametrizer.cpp
|
||||
intern/uv_initializer.cpp
|
||||
intern/uv_initializer.h
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::dependencies::eigen
|
||||
)
|
||||
|
||||
blender_add_lib(bf_intern_slim "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_library(bf::intern::slim ALIAS bf_intern_slim)
|
||||
94
blender-5.2.0/intern/slim/intern/area_compensation.cpp
Normal file
94
blender-5.2.0/intern/slim/intern/area_compensation.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
#include "area_compensation.h"
|
||||
#include "doublearea.h"
|
||||
#include "slim.h"
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace slim {
|
||||
|
||||
static void correct_geometry_size(double surface_area_to_map_area_ratio,
|
||||
MatrixXd &vertex_positions,
|
||||
double desired_surface_area_to_map_ration)
|
||||
{
|
||||
BLI_assert(surface_area_to_map_area_ratio > 0);
|
||||
double sqrt_of_ratio = sqrt(surface_area_to_map_area_ratio / desired_surface_area_to_map_ration);
|
||||
vertex_positions = vertex_positions / sqrt_of_ratio;
|
||||
}
|
||||
|
||||
template<typename VertexPositionType, typename FaceIndicesType>
|
||||
static double compute_surface_area(const VertexPositionType v, const FaceIndicesType f)
|
||||
{
|
||||
Eigen::VectorXd doubled_area_of_triangles;
|
||||
doublearea(v, f, doubled_area_of_triangles);
|
||||
double area_of_map = doubled_area_of_triangles.sum() / 2;
|
||||
return area_of_map;
|
||||
}
|
||||
|
||||
void correct_map_surface_area_if_necessary(SLIMData &slim_data)
|
||||
{
|
||||
if (!slim_data.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool mesh_surface_area_was_corrected = (slim_data.expectedSurfaceAreaOfResultingMap != 0);
|
||||
int number_of_pinned_vertices = slim_data.b.rows();
|
||||
bool no_pinned_vertices_exist = number_of_pinned_vertices == 0;
|
||||
|
||||
bool needs_area_correction = mesh_surface_area_was_corrected && no_pinned_vertices_exist;
|
||||
if (!needs_area_correction) {
|
||||
return;
|
||||
}
|
||||
|
||||
double area_ofresulting_map = compute_surface_area(slim_data.V_o, slim_data.F);
|
||||
if (!area_ofresulting_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
double resulting_area_to_expected_area_ratio = area_ofresulting_map /
|
||||
slim_data.expectedSurfaceAreaOfResultingMap;
|
||||
double desired_ratio = 1.0;
|
||||
correct_geometry_size(resulting_area_to_expected_area_ratio, slim_data.V_o, desired_ratio);
|
||||
}
|
||||
|
||||
void correct_mesh_surface_area_if_necessary(SLIMData &slim_data)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
int number_of_pinned_vertices = slim_data.b.rows();
|
||||
bool pinned_vertices_exist = number_of_pinned_vertices > 0;
|
||||
bool needs_area_correction = slim_data.skipInitialization || pinned_vertices_exist;
|
||||
|
||||
if (!needs_area_correction) {
|
||||
return;
|
||||
}
|
||||
|
||||
double area_of_preinitialized_map = compute_surface_area(slim_data.V_o, slim_data.F);
|
||||
if (!area_of_preinitialized_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (area_of_preinitialized_map < 0) {
|
||||
area_of_preinitialized_map *= -1;
|
||||
}
|
||||
|
||||
slim_data.expectedSurfaceAreaOfResultingMap = area_of_preinitialized_map;
|
||||
double surface_area_of3d_mesh = compute_surface_area(slim_data.V, slim_data.F);
|
||||
double surface_area_to_map_area_ratio = surface_area_of3d_mesh / area_of_preinitialized_map;
|
||||
|
||||
double desired_ratio = 1.0;
|
||||
correct_geometry_size(surface_area_to_map_area_ratio, slim_data.V, desired_ratio);
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
22
blender-5.2.0/intern/slim/intern/area_compensation.h
Normal file
22
blender-5.2.0/intern/slim/intern/area_compensation.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "slim.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace slim {
|
||||
|
||||
void correct_map_surface_area_if_necessary(SLIMData &slim_data);
|
||||
void correct_mesh_surface_area_if_necessary(SLIMData &slim_data);
|
||||
|
||||
} // namespace slim
|
||||
97
blender-5.2.0/intern/slim/intern/cotmatrix.cpp
Normal file
97
blender-5.2.0/intern/slim/intern/cotmatrix.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Alec Jacobson
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "cotmatrix.h"
|
||||
#include "edge_lengths.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* Inputs:
|
||||
* V #V by dim list of rest domain positions
|
||||
* F #F by 3 list of triangle indices into V
|
||||
* Outputs:
|
||||
* C #F by 3 list of 1/2*cotangents corresponding angles
|
||||
* for triangles, columns correspond to edges [1,2],[2,0],[0,1]
|
||||
*/
|
||||
template<typename DerivedV, typename DerivedF, typename DerivedC>
|
||||
static inline void cotmatrix_entries(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DerivedC> &C)
|
||||
{
|
||||
using namespace std;
|
||||
using namespace Eigen;
|
||||
/* Number of elements. */
|
||||
int m = F.rows();
|
||||
|
||||
assert(F.cols() == 3);
|
||||
|
||||
/* Law of cosines + law of sines. */
|
||||
/* Compute Squared Edge lenghts. */
|
||||
Matrix<typename DerivedC::Scalar, Dynamic, 3> l2;
|
||||
squared_edge_lengths(V, F, l2);
|
||||
/* Compute Edge lenghts. */
|
||||
Matrix<typename DerivedC::Scalar, Dynamic, 3> l;
|
||||
l = l2.array().sqrt();
|
||||
|
||||
/* Double area. */
|
||||
Matrix<typename DerivedC::Scalar, Dynamic, 1> dblA;
|
||||
doublearea(l, dblA);
|
||||
/* Cotangents and diagonal entries for element matrices.
|
||||
* correctly divided by 4. */
|
||||
C.resize(m, 3);
|
||||
for (int i = 0; i < m; i++) {
|
||||
C(i, 0) = (l2(i, 1) + l2(i, 2) - l2(i, 0)) / dblA(i) / 4.0;
|
||||
C(i, 1) = (l2(i, 2) + l2(i, 0) - l2(i, 1)) / dblA(i) / 4.0;
|
||||
C(i, 2) = (l2(i, 0) + l2(i, 1) - l2(i, 2)) / dblA(i) / 4.0;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename DerivedV, typename DerivedF, typename Scalar>
|
||||
inline void cotmatrix(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::SparseMatrix<Scalar> &L)
|
||||
{
|
||||
using namespace Eigen;
|
||||
using namespace std;
|
||||
|
||||
L.resize(V.rows(), V.rows());
|
||||
Matrix<int, Dynamic, 2> edges;
|
||||
/* 3 for triangles. */
|
||||
assert(F.cols() == 3);
|
||||
|
||||
/* This is important! it could decrease the comptuation time by a factor of 2
|
||||
* Laplacian for a closed 2d manifold mesh will have on average 7 entries per
|
||||
* row. */
|
||||
L.reserve(10 * V.rows());
|
||||
edges.resize(3, 2);
|
||||
edges << 1, 2, 2, 0, 0, 1;
|
||||
|
||||
/* Gather cotangents. */
|
||||
Matrix<Scalar, Dynamic, Dynamic> C;
|
||||
cotmatrix_entries(V, F, C);
|
||||
|
||||
vector<Triplet<Scalar>> IJV;
|
||||
IJV.reserve(F.rows() * edges.rows() * 4);
|
||||
/* Loop over triangles. */
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
/* Loop over edges of element. */
|
||||
for (int e = 0; e < edges.rows(); e++) {
|
||||
int source = F(i, edges(e, 0));
|
||||
int dest = F(i, edges(e, 1));
|
||||
IJV.push_back(Triplet<Scalar>(source, dest, C(i, e)));
|
||||
IJV.push_back(Triplet<Scalar>(dest, source, C(i, e)));
|
||||
IJV.push_back(Triplet<Scalar>(source, source, -C(i, e)));
|
||||
IJV.push_back(Triplet<Scalar>(dest, dest, -C(i, e)));
|
||||
}
|
||||
}
|
||||
L.setFromTriplets(IJV.begin(), IJV.end());
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
44
blender-5.2.0/intern/slim/intern/cotmatrix.h
Normal file
44
blender-5.2.0/intern/slim/intern/cotmatrix.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* SPDX-FileCopyrightText: 2014 Alec Jacobson
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <Eigen/Sparse>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* Constructs the cotangent stiffness matrix (discrete laplacian) for a given
|
||||
* mesh (V,F).
|
||||
*
|
||||
* Templates:
|
||||
* DerivedV derived type of eigen matrix for V (e.g. derived from
|
||||
* MatrixXd)
|
||||
* DerivedF derived type of eigen matrix for F (e.g. derived from
|
||||
* MatrixXi)
|
||||
* Scalar scalar type for eigen sparse matrix (e.g. double)
|
||||
* Inputs:
|
||||
* V #V by dim list of mesh vertex positions
|
||||
* F #F by simplex_size list of mesh faces (must be triangles)
|
||||
* Outputs:
|
||||
* L #V by #V cotangent matrix, each row i corresponding to V(i,:)
|
||||
*
|
||||
* Note: This Laplacian uses the convention that diagonal entries are
|
||||
* **minus** the sum of off-diagonal entries. The diagonal entries are
|
||||
* therefore in general negative and the matrix is **negative** semi-definite
|
||||
* (immediately, -L is **positive** semi-definite)
|
||||
*/
|
||||
template<typename DerivedV, typename DerivedF, typename Scalar>
|
||||
inline void cotmatrix(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::SparseMatrix<Scalar> &L);
|
||||
|
||||
} // namespace slim
|
||||
|
||||
#include "cotmatrix.cpp"
|
||||
199
blender-5.2.0/intern/slim/intern/doublearea.cpp
Normal file
199
blender-5.2.0/intern/slim/intern/doublearea.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Alec Jacobson
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "doublearea.h"
|
||||
#include "edge_lengths.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <BLI_task.hh>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* Sort the elements of a matrix X along a given dimension like matlabs sort
|
||||
* function, assuming X.cols() == 3.
|
||||
*
|
||||
* Templates:
|
||||
* DerivedX derived scalar type, e.g. MatrixXi or MatrixXd
|
||||
* DerivedIX derived integer type, e.g. MatrixXi
|
||||
* Inputs:
|
||||
* X m by n matrix whose entries are to be sorted
|
||||
* dim dimensional along which to sort:
|
||||
* 1 sort each column (matlab default)
|
||||
* 2 sort each row
|
||||
* ascending sort ascending (true, matlab default) or descending (false)
|
||||
* Outputs:
|
||||
* Y m by n matrix whose entries are sorted
|
||||
* IX m by n matrix of indices so that if dim = 1, then in matlab notation
|
||||
* for j = 1:n, Y(:,j) = X(I(:,j),j); end
|
||||
*/
|
||||
template<typename DerivedX, typename DerivedY, typename DerivedIX>
|
||||
static inline void doublearea_sort3(const Eigen::PlainObjectBase<DerivedX> &X,
|
||||
const int dim,
|
||||
const bool ascending,
|
||||
Eigen::PlainObjectBase<DerivedY> &Y,
|
||||
Eigen::PlainObjectBase<DerivedIX> &IX)
|
||||
{
|
||||
using namespace Eigen;
|
||||
using namespace std;
|
||||
using YScalar = typename Eigen::PlainObjectBase<DerivedY>::Scalar;
|
||||
Y = X.template cast<YScalar>();
|
||||
/* Get number of columns (or rows). */
|
||||
int num_outer = (dim == 1 ? X.cols() : X.rows());
|
||||
/* Get number of rows (or columns). */
|
||||
int num_inner = (dim == 1 ? X.rows() : X.cols());
|
||||
assert(num_inner == 3);
|
||||
(void)num_inner;
|
||||
using Index = typename Eigen::PlainObjectBase<DerivedIX>::Scalar;
|
||||
IX.resize(X.rows(), X.cols());
|
||||
if (dim == 1) {
|
||||
IX.row(0).setConstant(0); /* = Eigen::PlainObjectBase<DerivedIX>::Zero(1,IX.cols());. */
|
||||
IX.row(1).setConstant(1); /* = Eigen::PlainObjectBase<DerivedIX>::Ones (1,IX.cols());. */
|
||||
IX.row(2).setConstant(2); /* = Eigen::PlainObjectBase<DerivedIX>::Ones (1,IX.cols());. */
|
||||
}
|
||||
else {
|
||||
IX.col(0).setConstant(0); /* = Eigen::PlainObjectBase<DerivedIX>::Zero(IX.rows(),1);. */
|
||||
IX.col(1).setConstant(1); /* = Eigen::PlainObjectBase<DerivedIX>::Ones (IX.rows(),1);. */
|
||||
IX.col(2).setConstant(2); /* = Eigen::PlainObjectBase<DerivedIX>::Ones (IX.rows(),1);. */
|
||||
}
|
||||
|
||||
using namespace blender;
|
||||
threading::parallel_for(
|
||||
IndexRange(num_outer), 16000, [&IX, &Y, &dim, &ascending](const IndexRange range) {
|
||||
for (const Index i : range) {
|
||||
YScalar &a = (dim == 1 ? Y(0, i) : Y(i, 0));
|
||||
YScalar &b = (dim == 1 ? Y(1, i) : Y(i, 1));
|
||||
YScalar &c = (dim == 1 ? Y(2, i) : Y(i, 2));
|
||||
Index &ai = (dim == 1 ? IX(0, i) : IX(i, 0));
|
||||
Index &bi = (dim == 1 ? IX(1, i) : IX(i, 1));
|
||||
Index &ci = (dim == 1 ? IX(2, i) : IX(i, 2));
|
||||
if (ascending) {
|
||||
/* 123 132 213 231 312 321. */
|
||||
if (a > b) {
|
||||
std::swap(a, b);
|
||||
std::swap(ai, bi);
|
||||
}
|
||||
/* 123 132 123 231 132 231. */
|
||||
if (b > c) {
|
||||
std::swap(b, c);
|
||||
std::swap(bi, ci);
|
||||
/* 123 123 123 213 123 213. */
|
||||
if (a > b) {
|
||||
std::swap(a, b);
|
||||
std::swap(ai, bi);
|
||||
}
|
||||
/* 123 123 123 123 123 123. */
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* 123 132 213 231 312 321. */
|
||||
if (a < b) {
|
||||
std::swap(a, b);
|
||||
std::swap(ai, bi);
|
||||
}
|
||||
/* 213 312 213 321 312 321. */
|
||||
if (b < c) {
|
||||
std::swap(b, c);
|
||||
std::swap(bi, ci);
|
||||
/* 231 321 231 321 321 321. */
|
||||
if (a < b) {
|
||||
std::swap(a, b);
|
||||
std::swap(ai, bi);
|
||||
}
|
||||
/* 321 321 321 321 321 321. */
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename DerivedV, typename DerivedF, typename DeriveddblA>
|
||||
inline void doublearea(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DeriveddblA> &dblA)
|
||||
{
|
||||
const int dim = V.cols();
|
||||
/* Only support triangles. */
|
||||
assert(F.cols() == 3);
|
||||
const size_t m = F.rows();
|
||||
/* Compute edge lengths. */
|
||||
Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, 3> l;
|
||||
|
||||
/* Projected area helper. */
|
||||
const auto &proj_doublearea = [&V, &F](const int x, const int y, const int f) -> double {
|
||||
auto rx = V(F(f, 0), x) - V(F(f, 2), x);
|
||||
auto sx = V(F(f, 1), x) - V(F(f, 2), x);
|
||||
auto ry = V(F(f, 0), y) - V(F(f, 2), y);
|
||||
auto sy = V(F(f, 1), y) - V(F(f, 2), y);
|
||||
return rx * sy - ry * sx;
|
||||
};
|
||||
|
||||
switch (dim) {
|
||||
case 3: {
|
||||
dblA = Eigen::PlainObjectBase<DeriveddblA>::Zero(m, 1);
|
||||
for (size_t f = 0; f < m; f++) {
|
||||
for (int d = 0; d < 3; d++) {
|
||||
double dblAd = proj_doublearea(d, (d + 1) % 3, f);
|
||||
dblA(f) += dblAd * dblAd;
|
||||
}
|
||||
}
|
||||
dblA = dblA.array().sqrt().eval();
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
dblA.resize(m, 1);
|
||||
for (size_t f = 0; f < m; f++) {
|
||||
dblA(f) = proj_doublearea(0, 1, f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
edge_lengths(V, F, l);
|
||||
return doublearea(l, dblA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Derivedl, typename DeriveddblA>
|
||||
inline void doublearea(const Eigen::PlainObjectBase<Derivedl> &ul,
|
||||
Eigen::PlainObjectBase<DeriveddblA> &dblA)
|
||||
{
|
||||
using namespace Eigen;
|
||||
using namespace std;
|
||||
using Index = typename Derivedl::Index;
|
||||
/* Only support triangles. */
|
||||
assert(ul.cols() == 3);
|
||||
/* Number of triangles. */
|
||||
const Index m = ul.rows();
|
||||
Eigen::Matrix<typename Derivedl::Scalar, Eigen::Dynamic, 3> l;
|
||||
MatrixXi _;
|
||||
/* "Lecture Notes on Geometric Robustness" Shewchuck 09, Section 3.1
|
||||
* http://www.cs.berkeley.edu/~jrs/meshpapers/robnotes.pdf
|
||||
*
|
||||
* "Miscalculating Area and Angles of a Needle-like Triangle"
|
||||
* https://people.eecs.berkeley.edu/~wkahan/Triangle.pdf
|
||||
*/
|
||||
doublearea_sort3(ul, 2, false, l, _);
|
||||
dblA.resize(l.rows(), 1);
|
||||
|
||||
using namespace blender;
|
||||
threading::parallel_for(IndexRange(m), 1000, [&l, &dblA](const IndexRange range) {
|
||||
for (const Index i : range) {
|
||||
/* Kahan's Heron's formula. */
|
||||
const typename Derivedl::Scalar arg = (l(i, 0) + (l(i, 1) + l(i, 2))) *
|
||||
(l(i, 2) - (l(i, 0) - l(i, 1))) *
|
||||
(l(i, 2) + (l(i, 0) - l(i, 1))) *
|
||||
(l(i, 0) + (l(i, 1) - l(i, 2)));
|
||||
dblA(i) = 2.0 * 0.25 * sqrt(arg);
|
||||
assert(l(i, 2) - (l(i, 0) - l(i, 1)) && "FAILED KAHAN'S ASSERTION");
|
||||
assert(dblA(i) == dblA(i) && "DOUBLEAREA() PRODUCED NaN");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
52
blender-5.2.0/intern/slim/intern/doublearea.h
Normal file
52
blender-5.2.0/intern/slim/intern/doublearea.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Alec Jacobson
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* DOUBLEAREA computes twice the area for each input triangle
|
||||
*
|
||||
* Templates:
|
||||
* DerivedV derived type of eigen matrix for V (e.g. derived from
|
||||
* MatrixXd)
|
||||
* DerivedF derived type of eigen matrix for F (e.g. derived from
|
||||
* MatrixXi)
|
||||
* DeriveddblA derived type of eigen matrix for dblA (e.g. derived from
|
||||
* MatrixXd)
|
||||
* Inputs:
|
||||
* V #V by dim list of mesh vertex positions
|
||||
* F #F by simplex_size list of mesh faces (must be triangles)
|
||||
* Outputs:
|
||||
* dblA #F list of triangle double areas (SIGNED only for 2D input)
|
||||
*
|
||||
* Known bug: For dim==3 complexity is O(#V + #F)!! Not just O(#F). This is a big deal
|
||||
* if you have 1million unreferenced vertices and 1 face
|
||||
*/
|
||||
template<typename DerivedV, typename DerivedF, typename DeriveddblA>
|
||||
inline void doublearea(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DeriveddblA> &dblA);
|
||||
|
||||
/* Same as above but use instrinsic edge lengths rather than (V,F) mesh
|
||||
* Inputs:
|
||||
* l #F by dim list of edge lengths using
|
||||
* for triangles, columns correspond to edges 23,31,12
|
||||
* Outputs:
|
||||
* dblA #F list of triangle double areas
|
||||
*/
|
||||
template<typename Derivedl, typename DeriveddblA>
|
||||
inline void doublearea(const Eigen::PlainObjectBase<Derivedl> &l,
|
||||
Eigen::PlainObjectBase<DeriveddblA> &dblA);
|
||||
|
||||
} // namespace slim
|
||||
|
||||
#include "doublearea.cpp"
|
||||
49
blender-5.2.0/intern/slim/intern/edge_lengths.cpp
Normal file
49
blender-5.2.0/intern/slim/intern/edge_lengths.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Alec Jacobson
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "edge_lengths.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace slim {
|
||||
|
||||
template<typename DerivedV, typename DerivedF, typename DerivedL>
|
||||
inline void edge_lengths(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DerivedL> &L)
|
||||
{
|
||||
squared_edge_lengths(V, F, L);
|
||||
L = L.array().sqrt().eval();
|
||||
}
|
||||
|
||||
template<typename DerivedV, typename DerivedF, typename DerivedL>
|
||||
inline void squared_edge_lengths(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DerivedL> &L)
|
||||
{
|
||||
using namespace std;
|
||||
const int m = F.rows();
|
||||
assert(F.cols() == 3);
|
||||
|
||||
L.resize(m, 3);
|
||||
|
||||
/* Loop over faces. */
|
||||
using namespace blender;
|
||||
threading::parallel_for(IndexRange(m), 1000, [&V, &F, &L](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
L(i, 0) = (V.row(F(i, 1)) - V.row(F(i, 2))).squaredNorm();
|
||||
L(i, 1) = (V.row(F(i, 2)) - V.row(F(i, 0))).squaredNorm();
|
||||
L(i, 2) = (V.row(F(i, 0)) - V.row(F(i, 1))).squaredNorm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
62
blender-5.2.0/intern/slim/intern/edge_lengths.h
Normal file
62
blender-5.2.0/intern/slim/intern/edge_lengths.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Alec Jacobson
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace slim {
|
||||
/* Constructs a list of lengths of edges opposite each index in a face
|
||||
* (triangle) list
|
||||
*
|
||||
* Templates:
|
||||
* DerivedV derived from vertex positions matrix type: i.e. MatrixXd
|
||||
* DerivedF derived from face indices matrix type: i.e. MatrixXi
|
||||
* DerivedL derived from edge lengths matrix type: i.e. MatrixXd
|
||||
* Inputs:
|
||||
* V eigen matrix #V by 3
|
||||
* F #F by 2 list of mesh edges
|
||||
* or
|
||||
* F #F by 3 list of mesh faces (must be triangles)
|
||||
* or
|
||||
* T #T by 4 list of mesh elements (must be tets)
|
||||
* Outputs:
|
||||
* L #F by {1|3|6} list of edge lengths
|
||||
* for edges, column of lengths
|
||||
* for triangles, columns correspond to edges [1,2],[2,0],[0,1]
|
||||
*/
|
||||
template<typename DerivedV, typename DerivedF, typename DerivedL>
|
||||
inline void edge_lengths(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DerivedL> &L);
|
||||
|
||||
/* Constructs a list of squared lengths of edges opposite each index in a face
|
||||
* (triangle) list
|
||||
*
|
||||
* Templates:
|
||||
* DerivedV derived from vertex positions matrix type: i.e. MatrixXd
|
||||
* DerivedF derived from face indices matrix type: i.e. MatrixXi
|
||||
* DerivedL derived from edge lengths matrix type: i.e. MatrixXd
|
||||
* Inputs:
|
||||
* V eigen matrix #V by 3
|
||||
* F #F by 2 list of mesh edges
|
||||
* or
|
||||
* F #F by 3 list of mesh faces (must be triangles)
|
||||
* Outputs:
|
||||
* L #F by {1|3|6} list of edge lengths squared
|
||||
* for edges, column of lengths
|
||||
* for triangles, columns correspond to edges [1,2],[2,0],[0,1]
|
||||
*/
|
||||
template<typename DerivedV, typename DerivedF, typename DerivedL>
|
||||
inline void squared_edge_lengths(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DerivedL> &L);
|
||||
} // namespace slim
|
||||
|
||||
#include "edge_lengths.cpp"
|
||||
164
blender-5.2.0/intern/slim/intern/flip_avoiding_line_search.cpp
Normal file
164
blender-5.2.0/intern/slim/intern/flip_avoiding_line_search.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Michael Rabinovich
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "flip_avoiding_line_search.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* Implement a bisection linesearch to minimize a mesh-based energy on vertices given at 'x' at a
|
||||
* search direction 'd', with initial step size. Stops when a point with lower energy is found, or
|
||||
* after maximal iterations have been reached.
|
||||
*
|
||||
* Inputs:
|
||||
* x #X by dim list of variables
|
||||
* d #X by dim list of a given search direction
|
||||
* step_size initial step size
|
||||
* energy A function to compute the mesh-based energy (return an energy that is
|
||||
* bigger than 0) cur_energy(OPTIONAL) The energy at the given point. Helps save redundant
|
||||
* computations.
|
||||
* This is optional. If not specified, the function will compute it.
|
||||
* Outputs:
|
||||
* x #X by dim list of variables at the new location
|
||||
* Returns the energy at the new point 'x'.
|
||||
*/
|
||||
static inline double line_search(Eigen::MatrixXd &x,
|
||||
const Eigen::MatrixXd &d,
|
||||
double step_size,
|
||||
std::function<double(Eigen::MatrixXd &)> energy,
|
||||
double cur_energy = -1)
|
||||
{
|
||||
double old_energy;
|
||||
if (cur_energy > 0) {
|
||||
old_energy = cur_energy;
|
||||
}
|
||||
else {
|
||||
old_energy = energy(x); /* No energy was given -> need to compute the current energy. */
|
||||
}
|
||||
double new_energy = old_energy;
|
||||
int cur_iter = 0;
|
||||
int MAX_STEP_SIZE_ITER = 12;
|
||||
|
||||
while (new_energy >= old_energy && cur_iter < MAX_STEP_SIZE_ITER) {
|
||||
Eigen::MatrixXd new_x = x + step_size * d;
|
||||
|
||||
double cur_e = energy(new_x);
|
||||
if (cur_e >= old_energy) {
|
||||
step_size /= 2;
|
||||
}
|
||||
else {
|
||||
x = new_x;
|
||||
new_energy = cur_e;
|
||||
}
|
||||
cur_iter++;
|
||||
}
|
||||
return new_energy;
|
||||
}
|
||||
|
||||
static inline double get_smallest_pos_quad_zero(double a, double b, double c)
|
||||
{
|
||||
using namespace std;
|
||||
double t1, t2;
|
||||
if (a != 0) {
|
||||
double delta_in = pow(b, 2) - 4 * a * c;
|
||||
if (delta_in < 0) {
|
||||
return INFINITY;
|
||||
}
|
||||
double delta = sqrt(delta_in);
|
||||
t1 = (-b + delta) / (2 * a);
|
||||
t2 = (-b - delta) / (2 * a);
|
||||
}
|
||||
else {
|
||||
t1 = t2 = -b / c;
|
||||
}
|
||||
|
||||
if (!std::isfinite(t1) || !std::isfinite(t2)) {
|
||||
throw SlimFailedException();
|
||||
}
|
||||
|
||||
double tmp_n = min(t1, t2);
|
||||
t1 = max(t1, t2);
|
||||
t2 = tmp_n;
|
||||
if (t1 == t2) {
|
||||
return INFINITY; /* Means the orientation flips twice = doesn't flip. */
|
||||
}
|
||||
/* Return the smallest negative root if it exists, otherwise return infinity. */
|
||||
if (t1 > 0) {
|
||||
if (t2 > 0) {
|
||||
return t2;
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
return INFINITY;
|
||||
}
|
||||
|
||||
static inline double get_min_pos_root_2D(const Eigen::MatrixXd &uv,
|
||||
const Eigen::MatrixXi &F,
|
||||
Eigen::MatrixXd &d,
|
||||
int f)
|
||||
{
|
||||
using namespace std;
|
||||
/* Finding the smallest timestep t s.t a triangle get degenerated (<=> det = 0). */
|
||||
int v1 = F(f, 0);
|
||||
int v2 = F(f, 1);
|
||||
int v3 = F(f, 2);
|
||||
/* Get quadratic coefficients (ax^2 + b^x + c). */
|
||||
const double &U11 = uv(v1, 0);
|
||||
const double &U12 = uv(v1, 1);
|
||||
const double &U21 = uv(v2, 0);
|
||||
const double &U22 = uv(v2, 1);
|
||||
const double &U31 = uv(v3, 0);
|
||||
const double &U32 = uv(v3, 1);
|
||||
|
||||
const double &V11 = d(v1, 0);
|
||||
const double &V12 = d(v1, 1);
|
||||
const double &V21 = d(v2, 0);
|
||||
const double &V22 = d(v2, 1);
|
||||
const double &V31 = d(v3, 0);
|
||||
const double &V32 = d(v3, 1);
|
||||
|
||||
double a = V11 * V22 - V12 * V21 - V11 * V32 + V12 * V31 + V21 * V32 - V22 * V31;
|
||||
double b = U11 * V22 - U12 * V21 - U21 * V12 + U22 * V11 - U11 * V32 + U12 * V31 + U31 * V12 -
|
||||
U32 * V11 + U21 * V32 - U22 * V31 - U31 * V22 + U32 * V21;
|
||||
double c = U11 * U22 - U12 * U21 - U11 * U32 + U12 * U31 + U21 * U32 - U22 * U31;
|
||||
|
||||
return get_smallest_pos_quad_zero(a, b, c);
|
||||
}
|
||||
|
||||
static inline double compute_max_step_from_singularities(const Eigen::MatrixXd &uv,
|
||||
const Eigen::MatrixXi &F,
|
||||
Eigen::MatrixXd &d)
|
||||
{
|
||||
using namespace std;
|
||||
double max_step = INFINITY;
|
||||
|
||||
/* The if statement is outside the for loops to avoid branching/ease parallelizing. */
|
||||
for (int f = 0; f < F.rows(); f++) {
|
||||
double min_positive_root = get_min_pos_root_2D(uv, F, d, f);
|
||||
max_step = min(max_step, min_positive_root);
|
||||
}
|
||||
return max_step;
|
||||
}
|
||||
|
||||
inline double flip_avoiding_line_search(const Eigen::MatrixXi F,
|
||||
Eigen::MatrixXd &cur_v,
|
||||
Eigen::MatrixXd &dst_v,
|
||||
std::function<double(Eigen::MatrixXd &)> energy,
|
||||
double cur_energy)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
Eigen::MatrixXd d = dst_v - cur_v;
|
||||
double min_step_to_singularity = compute_max_step_from_singularities(cur_v, F, d);
|
||||
double max_step_size = min(1., min_step_to_singularity * 0.8);
|
||||
return line_search(cur_v, d, max_step_size, energy, cur_energy);
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
46
blender-5.2.0/intern/slim/intern/flip_avoiding_line_search.h
Normal file
46
blender-5.2.0/intern/slim/intern/flip_avoiding_line_search.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Michael Rabinovich
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* A bisection line search for a mesh based energy that avoids triangle flips as suggested in
|
||||
* "Bijective Parameterization with Free Boundaries" (Smith J. and Schaefer S., 2015).
|
||||
*
|
||||
* The user specifies an initial vertices position (that has no flips) and target one (that my have
|
||||
* flipped triangles). This method first computes the largest step in direction of the destination
|
||||
* vertices that does not incur flips, and then minimizes a given energy using this maximal step
|
||||
* and a bisection linesearch (see igl::line_search).
|
||||
*
|
||||
* Supports triangle meshes.
|
||||
*
|
||||
* Inputs:
|
||||
* F #F by 3 list of mesh faces
|
||||
* cur_v #V by dim list of variables
|
||||
* dst_v #V by dim list of target vertices. This mesh may have flipped triangles
|
||||
* energy A function to compute the mesh-based energy (return an energy that is
|
||||
* bigger than 0) cur_energy(OPTIONAL) The energy at the given point. Helps save
|
||||
* redundant computations.
|
||||
* This is optional. If not specified, the function will compute it.
|
||||
* Outputs:
|
||||
* cur_v #V by dim list of variables at the new location
|
||||
* Returns the energy at the new point.
|
||||
*/
|
||||
inline double flip_avoiding_line_search(const Eigen::MatrixXi F,
|
||||
Eigen::MatrixXd &cur_v,
|
||||
Eigen::MatrixXd &dst_v,
|
||||
std::function<double(Eigen::MatrixXd &)> energy,
|
||||
double cur_energy = -1);
|
||||
|
||||
} // namespace slim
|
||||
|
||||
#include "flip_avoiding_line_search.cpp"
|
||||
227
blender-5.2.0/intern/slim/intern/geometry_data_retrieval.cpp
Normal file
227
blender-5.2.0/intern/slim/intern/geometry_data_retrieval.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "geometry_data_retrieval.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "slim.h"
|
||||
#include "slim_matrix_transfer.h"
|
||||
|
||||
#include "area_compensation.h"
|
||||
#include "least_squares_relocator.h"
|
||||
#include "uv_initializer.h"
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace slim {
|
||||
|
||||
GeometryData::GeometryData(const MatrixTransfer &mt, MatrixTransferChart &chart)
|
||||
: number_of_vertices(chart.verts_num),
|
||||
number_of_faces(chart.faces_num),
|
||||
/* `n_edges` in transferred_data accounts for boundary edges only once. */
|
||||
number_of_edges_twice(chart.edges_num + chart.boundary_vertices_num),
|
||||
number_of_boundary_vertices(chart.boundary_vertices_num),
|
||||
number_of_pinned_vertices(chart.pinned_vertices_num),
|
||||
use_weights(mt.use_weights),
|
||||
weight_influence(mt.weight_influence),
|
||||
vertex_positions3d(chart.v_matrices.data(), number_of_vertices, columns_3),
|
||||
uv_positions2d(chart.uv_matrices.data(), number_of_vertices, columns_2),
|
||||
positions_of_pinned_vertices2d(),
|
||||
positions_of_explicitly_pinned_vertices2d(
|
||||
number_of_pinned_vertices != 0 ? chart.pp_matrices.data() : nullptr,
|
||||
number_of_pinned_vertices,
|
||||
columns_2),
|
||||
faces_by_vertexindices(chart.f_matrices.data(), number_of_faces, columns_3),
|
||||
edges_by_vertexindices(chart.e_matrices.data(), number_of_edges_twice, columns_2),
|
||||
pinned_vertex_indices(),
|
||||
explicitly_pinned_vertex_indices(number_of_pinned_vertices != 0 ? chart.p_matrices.data() :
|
||||
nullptr,
|
||||
number_of_pinned_vertices),
|
||||
edge_lengths(chart.el_vectors.data(), number_of_edges_twice),
|
||||
boundary_vertex_indices(chart.b_vectors.data(), number_of_boundary_vertices),
|
||||
weights_per_vertex(chart.w_vectors.data(), number_of_vertices)
|
||||
{
|
||||
retrieve_pinned_vertices(mt.fixed_boundary);
|
||||
}
|
||||
|
||||
static void create_weights_per_face(SLIMData &slim_data)
|
||||
{
|
||||
if (!slim_data.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!slim_data.withWeightedParameterization) {
|
||||
slim_data.weightPerFaceMap = Eigen::VectorXf::Ones(slim_data.F.rows());
|
||||
return;
|
||||
}
|
||||
|
||||
slim_data.weightPerFaceMap = Eigen::VectorXf(slim_data.F.rows());
|
||||
|
||||
/* The actual weight is `max_factor ^ (2 * (mean - 0.5))` */
|
||||
int weight_influence_sign = (slim_data.weightInfluence >= 0) ? 1 : -1;
|
||||
double max_factor = std::abs(slim_data.weightInfluence) + 1;
|
||||
|
||||
for (int fid = 0; fid < slim_data.F.rows(); fid++) {
|
||||
Eigen::RowVector3i row = slim_data.F.row(fid);
|
||||
float w1, w2, w3, mean, weight_factor, flipped_mean;
|
||||
w1 = slim_data.weightmap(row(0));
|
||||
w2 = slim_data.weightmap(row(1));
|
||||
w3 = slim_data.weightmap(row(2));
|
||||
mean = (w1 + w2 + w3) / 3;
|
||||
flipped_mean = 1 - mean;
|
||||
|
||||
weight_factor = std::pow(max_factor, weight_influence_sign * 2 * (flipped_mean - 0.5));
|
||||
slim_data.weightPerFaceMap(fid) = weight_factor;
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryData::set_geometry_data_matrices(SLIMData &slim_data) const
|
||||
{
|
||||
if (!slim_data.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
slim_data.V = vertex_positions3d;
|
||||
slim_data.F = faces_by_vertexindices;
|
||||
slim_data.b = pinned_vertex_indices;
|
||||
slim_data.bc = positions_of_pinned_vertices2d;
|
||||
slim_data.V_o = uv_positions2d;
|
||||
slim_data.oldUVs = uv_positions2d;
|
||||
slim_data.weightmap = weights_per_vertex;
|
||||
create_weights_per_face(slim_data);
|
||||
}
|
||||
|
||||
bool GeometryData::has_valid_preinitialized_map() const
|
||||
{
|
||||
if (uv_positions2d.rows() == vertex_positions3d.rows() && uv_positions2d.cols() == columns_2) {
|
||||
|
||||
int number_of_flips = count_flips(faces_by_vertexindices, uv_positions2d);
|
||||
bool no_flips_present = (number_of_flips == 0);
|
||||
return (no_flips_present);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If we use interactive parametrisation, we usually start form an existing, flip-free unwrapping.
|
||||
* Also, pinning of vertices has some issues with initialisation with convex border.
|
||||
* We therefore may want to skip initialization. however, to skip initialization we need a
|
||||
* preexisting valid starting map. */
|
||||
bool GeometryData::can_initialization_be_skipped(bool skip_initialization) const
|
||||
{
|
||||
return (skip_initialization && has_valid_preinitialized_map());
|
||||
}
|
||||
|
||||
void GeometryData::construct_slim_data(SLIMData &slim_data,
|
||||
bool skip_initialization,
|
||||
int reflection_mode) const
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
slim_data.skipInitialization = can_initialization_be_skipped(skip_initialization);
|
||||
slim_data.weightInfluence = weight_influence;
|
||||
slim_data.reflection_mode = reflection_mode;
|
||||
slim_data.withWeightedParameterization = use_weights;
|
||||
set_geometry_data_matrices(slim_data);
|
||||
|
||||
double penalty_for_violating_pinned_positions = 10.0e100;
|
||||
slim_data.soft_const_p = penalty_for_violating_pinned_positions;
|
||||
slim_data.slim_energy = SLIMData::SYMMETRIC_DIRICHLET;
|
||||
|
||||
initialize_if_needed(slim_data);
|
||||
|
||||
transform_initialization_if_necessary(slim_data);
|
||||
correct_mesh_surface_area_if_necessary(slim_data);
|
||||
|
||||
slim_precompute(slim_data.V,
|
||||
slim_data.F,
|
||||
slim_data.V_o,
|
||||
slim_data,
|
||||
slim_data.slim_energy,
|
||||
slim_data.b,
|
||||
slim_data.bc,
|
||||
slim_data.soft_const_p);
|
||||
}
|
||||
|
||||
void GeometryData::combine_matrices_of_pinned_and_boundary_vertices()
|
||||
{
|
||||
/* Over - allocate pessimistically to avoid multiple reallocation. */
|
||||
int upper_bound_on_number_of_pinned_vertices = number_of_boundary_vertices +
|
||||
number_of_pinned_vertices;
|
||||
pinned_vertex_indices = VectorXi(upper_bound_on_number_of_pinned_vertices);
|
||||
positions_of_pinned_vertices2d = MatrixXd(upper_bound_on_number_of_pinned_vertices, columns_2);
|
||||
|
||||
/* Since border vertices use vertex indices 0 ... #bordervertices we can do: */
|
||||
pinned_vertex_indices.segment(0, number_of_boundary_vertices) = boundary_vertex_indices;
|
||||
positions_of_pinned_vertices2d.block(0, 0, number_of_boundary_vertices, columns_2) =
|
||||
uv_positions2d.block(0, 0, number_of_boundary_vertices, columns_2);
|
||||
|
||||
int index = number_of_boundary_vertices;
|
||||
int highest_vertex_index = (boundary_vertex_indices)(index - 1);
|
||||
|
||||
for (Map<VectorXi>::InnerIterator it(explicitly_pinned_vertex_indices, 0); it; ++it) {
|
||||
int vertex_index = it.value();
|
||||
if (vertex_index > highest_vertex_index) {
|
||||
pinned_vertex_indices(index) = vertex_index;
|
||||
positions_of_pinned_vertices2d.row(index) = uv_positions2d.row(vertex_index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
int actual_number_of_pinned_vertices = index;
|
||||
pinned_vertex_indices.conservativeResize(actual_number_of_pinned_vertices);
|
||||
positions_of_pinned_vertices2d.conservativeResize(actual_number_of_pinned_vertices, columns_2);
|
||||
|
||||
number_of_pinned_vertices = actual_number_of_pinned_vertices;
|
||||
}
|
||||
|
||||
/* If the border is fixed, we simply pin the border vertices additionally to other pinned vertices.
|
||||
*/
|
||||
void GeometryData::retrieve_pinned_vertices(bool border_vertices_are_pinned)
|
||||
{
|
||||
if (border_vertices_are_pinned) {
|
||||
combine_matrices_of_pinned_and_boundary_vertices();
|
||||
}
|
||||
else {
|
||||
pinned_vertex_indices = VectorXi(explicitly_pinned_vertex_indices);
|
||||
positions_of_pinned_vertices2d = MatrixXd(positions_of_explicitly_pinned_vertices2d);
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryData::initialize_if_needed(SLIMData &slim_data) const
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
if (!slim_data.skipInitialization) {
|
||||
initialize_uvs(slim_data);
|
||||
}
|
||||
}
|
||||
|
||||
void GeometryData::initialize_uvs(SLIMData &slim_data) const
|
||||
{
|
||||
Eigen::MatrixXd uv_positions_of_boundary(boundary_vertex_indices.rows(), 2);
|
||||
map_vertices_to_convex_border(uv_positions_of_boundary);
|
||||
|
||||
bool all_vertices_on_boundary = (slim_data.V_o.rows() == uv_positions_of_boundary.rows());
|
||||
if (all_vertices_on_boundary) {
|
||||
slim_data.V_o = uv_positions_of_boundary;
|
||||
return;
|
||||
}
|
||||
|
||||
mvc(faces_by_vertexindices,
|
||||
vertex_positions3d,
|
||||
edges_by_vertexindices,
|
||||
edge_lengths,
|
||||
boundary_vertex_indices,
|
||||
uv_positions_of_boundary,
|
||||
slim_data.V_o);
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
67
blender-5.2.0/intern/slim/intern/geometry_data_retrieval.h
Normal file
67
blender-5.2.0/intern/slim/intern/geometry_data_retrieval.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
#include "slim.h"
|
||||
#include "slim_matrix_transfer.h"
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace slim {
|
||||
|
||||
struct GeometryData {
|
||||
int columns_2 = 2;
|
||||
int columns_3 = 3;
|
||||
int number_of_vertices = 0;
|
||||
int number_of_faces = 0;
|
||||
int number_of_edges_twice = 0;
|
||||
int number_of_boundary_vertices = 0;
|
||||
int number_of_pinned_vertices = 0;
|
||||
|
||||
bool use_weights = false;
|
||||
double weight_influence = 0.0;
|
||||
|
||||
/* All the following maps have to be declared as last members. */
|
||||
Map<MatrixXd> vertex_positions3d = Map<MatrixXd>(nullptr, 0, 0);
|
||||
Map<MatrixXd> uv_positions2d = Map<MatrixXd>(nullptr, 0, 0);
|
||||
MatrixXd positions_of_pinned_vertices2d;
|
||||
Map<Matrix<double, Dynamic, Dynamic, RowMajor>> positions_of_explicitly_pinned_vertices2d =
|
||||
Map<Matrix<double, Dynamic, Dynamic, RowMajor>>(nullptr, 0, 0);
|
||||
|
||||
Map<MatrixXi> faces_by_vertexindices = Map<MatrixXi>(nullptr, 0, 0);
|
||||
Map<MatrixXi> edges_by_vertexindices = Map<MatrixXi>(nullptr, 0, 0);
|
||||
VectorXi pinned_vertex_indices;
|
||||
Map<VectorXi> explicitly_pinned_vertex_indices = Map<VectorXi>(nullptr, 0);
|
||||
|
||||
Map<VectorXd> edge_lengths = Map<VectorXd>(nullptr, 0);
|
||||
Map<VectorXi> boundary_vertex_indices = Map<VectorXi>(nullptr, 0);
|
||||
Map<VectorXf> weights_per_vertex = Map<VectorXf>(nullptr, 0);
|
||||
|
||||
GeometryData(const MatrixTransfer &mt, MatrixTransferChart &chart);
|
||||
GeometryData(const GeometryData &) = delete;
|
||||
GeometryData &operator=(const GeometryData &) = delete;
|
||||
|
||||
void construct_slim_data(SLIMData &slim_data,
|
||||
bool skip_initialization,
|
||||
int reflection_mode) const;
|
||||
|
||||
void retrieve_pinned_vertices(bool border_vertices_are_pinned);
|
||||
|
||||
private:
|
||||
void set_geometry_data_matrices(SLIMData &slim_data) const;
|
||||
bool has_valid_preinitialized_map() const;
|
||||
bool can_initialization_be_skipped(bool skip_initialization) const;
|
||||
void combine_matrices_of_pinned_and_boundary_vertices();
|
||||
void initialize_if_needed(SLIMData &slim_data) const;
|
||||
void initialize_uvs(SLIMData &slim_data) const;
|
||||
};
|
||||
|
||||
} // namespace slim
|
||||
240
blender-5.2.0/intern/slim/intern/least_squares_relocator.cpp
Normal file
240
blender-5.2.0/intern/slim/intern/least_squares_relocator.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "least_squares_relocator.h"
|
||||
|
||||
#include "slim.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
namespace slim {
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
static void apply_transformation(SLIMData &slim_data, Matrix2d &transformation_matrix)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
for (int i = 0; i < slim_data.V_o.rows(); i++) {
|
||||
slim_data.V_o.row(i) = transformation_matrix * slim_data.V_o.row(i).transpose();
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_translation(SLIMData &slim_data, Vector2d &translation_vector)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
for (int i = 0; i < slim_data.V_o.rows(); i++) {
|
||||
slim_data.V_o.row(i) = translation_vector.transpose() + slim_data.V_o.row(i);
|
||||
}
|
||||
}
|
||||
|
||||
static void retrieve_positions_of_pinned_vertices_in_initialization(
|
||||
const MatrixXd &all_uv_positions_in_initialization,
|
||||
const VectorXi &indices_of_pinned_vertices,
|
||||
MatrixXd &position_of_pinned_vertices_in_initialization)
|
||||
{
|
||||
int i = 0;
|
||||
for (VectorXi::InnerIterator it(indices_of_pinned_vertices, 0); it; ++it, i++) {
|
||||
int vertex_index = it.value();
|
||||
position_of_pinned_vertices_in_initialization.row(i) = all_uv_positions_in_initialization.row(
|
||||
vertex_index);
|
||||
}
|
||||
}
|
||||
|
||||
static void flip_input_geometry(SLIMData &slim_data)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
VectorXi temp = slim_data.F.col(0);
|
||||
slim_data.F.col(0) = slim_data.F.col(2);
|
||||
slim_data.F.col(2) = temp;
|
||||
}
|
||||
|
||||
static void compute_centroid(const MatrixXd &point_cloud, Vector2d ¢roid)
|
||||
{
|
||||
centroid << point_cloud.col(0).sum(), point_cloud.col(1).sum();
|
||||
centroid /= point_cloud.rows();
|
||||
}
|
||||
|
||||
/* Finds scaling matrix:
|
||||
*
|
||||
* T = |a 0|
|
||||
* |0 a|
|
||||
*
|
||||
* s.t. if to each point p in the inizialized map the following is applied
|
||||
*
|
||||
* T*p
|
||||
*
|
||||
* We get the closest scaling of the positions of the vertices in the initialized map to the pinned
|
||||
* vertices in a least squares sense. We find them by solving
|
||||
*
|
||||
* argmin_{t} At = p
|
||||
*
|
||||
* i.e.:
|
||||
*
|
||||
* | x_1 | |u_1|
|
||||
* | . | | . |
|
||||
* | . | | . |
|
||||
* | x_n | |u_n|
|
||||
* | y_1 | * | a | = |v_1|
|
||||
* | . | | . |
|
||||
* | . | | . |
|
||||
* | y_n | |v_n|
|
||||
*
|
||||
* `t` is of dimension `1 x 1` and `p` of dimension `2*numberOfPinnedVertices x 1`
|
||||
* is the vector holding the uv positions of the pinned vertices. */
|
||||
static void compute_least_squares_scaling(MatrixXd centered_pins,
|
||||
MatrixXd centered_initialized_pins,
|
||||
Matrix2d &transformation_matrix)
|
||||
{
|
||||
int number_of_pinned_vertices = centered_pins.rows();
|
||||
|
||||
MatrixXd a = MatrixXd::Zero(number_of_pinned_vertices * 2, 1);
|
||||
a << centered_initialized_pins.col(0), centered_initialized_pins.col(1);
|
||||
|
||||
VectorXd p(2 * number_of_pinned_vertices);
|
||||
p << centered_pins.col(0), centered_pins.col(1);
|
||||
|
||||
VectorXd t = a.colPivHouseholderQr().solve(p);
|
||||
t(0) = abs(t(0));
|
||||
transformation_matrix << t(0), 0, 0, t(0);
|
||||
}
|
||||
|
||||
static void comput_least_squares_rotation_scale_only(SLIMData &slim_data,
|
||||
Vector2d &translation_vector,
|
||||
Matrix2d &transformation_matrix,
|
||||
bool is_flip_allowed)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
MatrixXd position_of_initialized_pins(slim_data.b.rows(), 2);
|
||||
retrieve_positions_of_pinned_vertices_in_initialization(
|
||||
slim_data.V_o, slim_data.b, position_of_initialized_pins);
|
||||
|
||||
Vector2d centroid_of_initialized;
|
||||
compute_centroid(position_of_initialized_pins, centroid_of_initialized);
|
||||
|
||||
Vector2d centroid_of_pins;
|
||||
compute_centroid(slim_data.bc, centroid_of_pins);
|
||||
|
||||
MatrixXd centered_initialized_pins = position_of_initialized_pins.rowwise().operator-(
|
||||
centroid_of_initialized.transpose());
|
||||
MatrixXd centeredpins = slim_data.bc.rowwise().operator-(centroid_of_pins.transpose());
|
||||
|
||||
MatrixXd s = centered_initialized_pins.transpose() * centeredpins;
|
||||
|
||||
JacobiSVD<MatrixXd> svd(s, ComputeFullU | ComputeFullV);
|
||||
|
||||
Matrix2d vu_t = svd.matrixV() * svd.matrixU().transpose();
|
||||
|
||||
Matrix2d singular_values = Matrix2d::Identity();
|
||||
|
||||
bool contains_reflection = vu_t.determinant() < 0;
|
||||
if (contains_reflection) {
|
||||
if (!is_flip_allowed) {
|
||||
singular_values(1, 1) = vu_t.determinant();
|
||||
}
|
||||
else {
|
||||
flip_input_geometry(slim_data);
|
||||
}
|
||||
}
|
||||
|
||||
compute_least_squares_scaling(centeredpins, centered_initialized_pins, transformation_matrix);
|
||||
|
||||
transformation_matrix = transformation_matrix * svd.matrixV() * singular_values *
|
||||
svd.matrixU().transpose();
|
||||
|
||||
translation_vector = centroid_of_pins - transformation_matrix * centroid_of_initialized;
|
||||
}
|
||||
|
||||
static void compute_transformation_matrix2_pins(const SLIMData &slim_data,
|
||||
Matrix2d &transformation_matrix)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
Vector2d pinned_position_difference_vector = slim_data.bc.row(0) - slim_data.bc.row(1);
|
||||
Vector2d initialized_position_difference_vector = slim_data.V_o.row(slim_data.b(0)) -
|
||||
slim_data.V_o.row(slim_data.b(1));
|
||||
|
||||
double scale = pinned_position_difference_vector.norm() /
|
||||
initialized_position_difference_vector.norm();
|
||||
|
||||
pinned_position_difference_vector.normalize();
|
||||
initialized_position_difference_vector.normalize();
|
||||
|
||||
/* TODO: sometimes rotates in wrong direction. */
|
||||
double cos_angle = pinned_position_difference_vector.dot(initialized_position_difference_vector);
|
||||
double sin_angle = sqrt(1 - pow(cos_angle, 2));
|
||||
|
||||
transformation_matrix << cos_angle, -sin_angle, sin_angle, cos_angle;
|
||||
transformation_matrix = (Matrix2d::Identity() * scale) * transformation_matrix;
|
||||
}
|
||||
|
||||
static void compute_translation1_pin(const SLIMData &slim_data, Vector2d &translation_vector)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
translation_vector = slim_data.bc.row(0) - slim_data.V_o.row(slim_data.b(0));
|
||||
}
|
||||
|
||||
static void transform_initialized_map(SLIMData &slim_data)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
Matrix2d transformation_matrix;
|
||||
Vector2d translation_vector;
|
||||
|
||||
int number_of_pinned_vertices = slim_data.b.rows();
|
||||
|
||||
switch (number_of_pinned_vertices) {
|
||||
case 0:
|
||||
return;
|
||||
case 1: /* Only translation is needed with one pin. */
|
||||
compute_translation1_pin(slim_data, translation_vector);
|
||||
apply_translation(slim_data, translation_vector);
|
||||
break;
|
||||
case 2:
|
||||
compute_transformation_matrix2_pins(slim_data, transformation_matrix);
|
||||
apply_transformation(slim_data, transformation_matrix);
|
||||
compute_translation1_pin(slim_data, translation_vector);
|
||||
apply_translation(slim_data, translation_vector);
|
||||
break;
|
||||
default:
|
||||
|
||||
bool flip_allowed = slim_data.reflection_mode == 0;
|
||||
|
||||
comput_least_squares_rotation_scale_only(
|
||||
slim_data, translation_vector, transformation_matrix, flip_allowed);
|
||||
|
||||
apply_transformation(slim_data, transformation_matrix);
|
||||
apply_translation(slim_data, translation_vector);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_translation_needed(const SLIMData &slim_data)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
bool pinned_vertices_exist = (slim_data.b.rows() > 0);
|
||||
bool was_initialized = !slim_data.skipInitialization;
|
||||
return was_initialized && pinned_vertices_exist;
|
||||
}
|
||||
|
||||
void transform_initialization_if_necessary(SLIMData &slim_data)
|
||||
{
|
||||
BLI_assert(slim_data.valid);
|
||||
|
||||
if (!is_translation_needed(slim_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
transform_initialized_map(slim_data);
|
||||
}
|
||||
} // namespace slim
|
||||
17
blender-5.2.0/intern/slim/intern/least_squares_relocator.h
Normal file
17
blender-5.2.0/intern/slim/intern/least_squares_relocator.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "slim.h"
|
||||
|
||||
namespace slim {
|
||||
|
||||
void transform_initialization_if_necessary(SLIMData &slim_data);
|
||||
|
||||
}
|
||||
830
blender-5.2.0/intern/slim/intern/slim.cpp
Normal file
830
blender-5.2.0/intern/slim/intern/slim.cpp
Normal file
@@ -0,0 +1,830 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Michael Rabinovich
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "slim.h"
|
||||
#include "doublearea.h"
|
||||
#include "flip_avoiding_line_search.h"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_base.h" /* M_PI */
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
#include <Eigen/IterativeLinearSolvers>
|
||||
#include <Eigen/SVD>
|
||||
#include <Eigen/SparseCholesky>
|
||||
|
||||
namespace slim {
|
||||
|
||||
/* GRAD
|
||||
* G = grad(V,F)
|
||||
*
|
||||
* Compute the numerical gradient operator
|
||||
*
|
||||
* Inputs:
|
||||
* V #vertices by 3 list of mesh vertex positions
|
||||
* F #faces by 3 list of mesh face indices
|
||||
* uniform #boolean (default false) - Use a uniform mesh instead of the vertices V
|
||||
* Outputs:
|
||||
* G #faces*dim by #V Gradient operator
|
||||
*
|
||||
*
|
||||
* Gradient of a scalar function defined on piecewise linear elements (mesh)
|
||||
* is constant on each triangle i,j,k:
|
||||
* grad(Xijk) = (Xj-Xi) * (Vi - Vk)^R90 / 2A + (Xk-Xi) * (Vj - Vi)^R90 / 2A
|
||||
* where Xi is the scalar value at vertex i, Vi is the 3D position of vertex
|
||||
* i, and A is the area of triangle (i,j,k). ^R90 represent a rotation of
|
||||
* 90 degrees
|
||||
*/
|
||||
template<typename DerivedV, typename DerivedF>
|
||||
static inline void grad(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::SparseMatrix<typename DerivedV::Scalar> &G,
|
||||
bool uniform = false)
|
||||
{
|
||||
Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, 3> eperp21(F.rows(), 3),
|
||||
eperp13(F.rows(), 3);
|
||||
|
||||
for (int i = 0; i < F.rows(); ++i) {
|
||||
/* Renaming indices of vertices of triangles for convenience. */
|
||||
int i1 = F(i, 0);
|
||||
int i2 = F(i, 1);
|
||||
int i3 = F(i, 2);
|
||||
|
||||
/* #F x 3 matrices of triangle edge vectors, named after opposite vertices. */
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v32 = V.row(i3) - V.row(i2);
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v13 = V.row(i1) - V.row(i3);
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v21 = V.row(i2) - V.row(i1);
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> n = v32.cross(v13);
|
||||
/* Area of parallelogram is twice area of triangle.
|
||||
* Area of parallelogram is || v1 x v2 ||.
|
||||
* This does correct l2 norm of rows, so that it contains #F list of twice.
|
||||
* triangle areas. */
|
||||
double dblA = std::sqrt(n.dot(n));
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> u;
|
||||
if (!uniform) {
|
||||
/* Now normalize normals to get unit normals. */
|
||||
u = n / dblA;
|
||||
}
|
||||
else {
|
||||
/* Abstract equilateral triangle v1=(0,0), v2=(h,0), v3=(h/2, (sqrt(3)/2)*h) */
|
||||
|
||||
/* Get h (by the area of the triangle). */
|
||||
double h = sqrt((dblA) /
|
||||
sin(M_PI / 3.0)); /* (h^2*sin(60))/2. = Area => h = sqrt(2*Area/sin_60) */
|
||||
|
||||
Eigen::VectorXd v1, v2, v3;
|
||||
v1 << 0, 0, 0;
|
||||
v2 << h, 0, 0;
|
||||
v3 << h / 2., (sqrt(3) / 2.) * h, 0;
|
||||
|
||||
/* Now fix v32,v13,v21 and the normal. */
|
||||
v32 = v3 - v2;
|
||||
v13 = v1 - v3;
|
||||
v21 = v2 - v1;
|
||||
n = v32.cross(v13);
|
||||
}
|
||||
|
||||
/* Rotate each vector 90 degrees around normal. */
|
||||
double norm21 = std::sqrt(v21.dot(v21));
|
||||
double norm13 = std::sqrt(v13.dot(v13));
|
||||
eperp21.row(i) = u.cross(v21);
|
||||
eperp21.row(i) = eperp21.row(i) / std::sqrt(eperp21.row(i).dot(eperp21.row(i)));
|
||||
eperp21.row(i) *= norm21 / dblA;
|
||||
eperp13.row(i) = u.cross(v13);
|
||||
eperp13.row(i) = eperp13.row(i) / std::sqrt(eperp13.row(i).dot(eperp13.row(i)));
|
||||
eperp13.row(i) *= norm13 / dblA;
|
||||
}
|
||||
|
||||
std::vector<int> rs;
|
||||
rs.reserve(F.rows() * 4 * 3);
|
||||
std::vector<int> cs;
|
||||
cs.reserve(F.rows() * 4 * 3);
|
||||
std::vector<double> vs;
|
||||
vs.reserve(F.rows() * 4 * 3);
|
||||
|
||||
/* Row indices. */
|
||||
for (int r = 0; r < 3; r++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
for (int i = r * F.rows(); i < (r + 1) * F.rows(); i++) {
|
||||
rs.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Column indices. */
|
||||
for (int r = 0; r < 3; r++) {
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
cs.push_back(F(i, 1));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
cs.push_back(F(i, 0));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
cs.push_back(F(i, 2));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
cs.push_back(F(i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/* Values. */
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(eperp13(i, 0));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(-eperp13(i, 0));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(eperp21(i, 0));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(-eperp21(i, 0));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(eperp13(i, 1));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(-eperp13(i, 1));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(eperp21(i, 1));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(-eperp21(i, 1));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(eperp13(i, 2));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(-eperp13(i, 2));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(eperp21(i, 2));
|
||||
}
|
||||
for (int i = 0; i < F.rows(); i++) {
|
||||
vs.push_back(-eperp21(i, 2));
|
||||
}
|
||||
|
||||
/* Create sparse gradient operator matrix.. */
|
||||
G.resize(3 * F.rows(), V.rows());
|
||||
std::vector<Eigen::Triplet<typename DerivedV::Scalar>> triplets;
|
||||
for (int i = 0; i < (int)vs.size(); ++i) {
|
||||
triplets.push_back(Eigen::Triplet<typename DerivedV::Scalar>(rs[i], cs[i], vs[i]));
|
||||
}
|
||||
G.setFromTriplets(triplets.begin(), triplets.end());
|
||||
}
|
||||
|
||||
/* Computes the polar decomposition (R,T) of a matrix A using SVD singular
|
||||
* value decomposition
|
||||
*
|
||||
* Inputs:
|
||||
* A 3 by 3 matrix to be decomposed
|
||||
* Outputs:
|
||||
* R 3 by 3 rotation matrix part of decomposition (**always rotataion**)
|
||||
* T 3 by 3 stretch matrix part of decomposition
|
||||
* U 3 by 3 left-singular vectors
|
||||
* S 3 by 1 singular values
|
||||
* V 3 by 3 right-singular vectors
|
||||
*/
|
||||
template<typename DerivedA,
|
||||
typename DerivedR,
|
||||
typename DerivedT,
|
||||
typename DerivedU,
|
||||
typename DerivedS,
|
||||
typename DerivedV>
|
||||
static inline void polar_svd(const Eigen::PlainObjectBase<DerivedA> &A,
|
||||
Eigen::PlainObjectBase<DerivedR> &R,
|
||||
Eigen::PlainObjectBase<DerivedT> &T,
|
||||
Eigen::PlainObjectBase<DerivedU> &U,
|
||||
Eigen::PlainObjectBase<DerivedS> &S,
|
||||
Eigen::PlainObjectBase<DerivedV> &V)
|
||||
{
|
||||
using namespace std;
|
||||
Eigen::JacobiSVD<DerivedA, Eigen::ComputeFullU | Eigen::ComputeFullV> svd;
|
||||
svd.compute(A);
|
||||
U = svd.matrixU();
|
||||
V = svd.matrixV();
|
||||
S = svd.singularValues();
|
||||
R = U * V.transpose();
|
||||
const auto &SVT = S.asDiagonal() * V.adjoint();
|
||||
/* Check for reflection. */
|
||||
if (R.determinant() < 0) {
|
||||
/* Annoyingly the .eval() is necessary. */
|
||||
auto W = V.eval();
|
||||
W.col(V.cols() - 1) *= -1.;
|
||||
R = U * W.transpose();
|
||||
T = W * SVT;
|
||||
}
|
||||
else {
|
||||
T = V * SVT;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void compute_surface_gradient_matrix(const Eigen::MatrixXd &V,
|
||||
const Eigen::MatrixXi &F,
|
||||
const Eigen::MatrixXd &F1,
|
||||
const Eigen::MatrixXd &F2,
|
||||
Eigen::SparseMatrix<double> &D1,
|
||||
Eigen::SparseMatrix<double> &D2)
|
||||
{
|
||||
Eigen::SparseMatrix<double> G;
|
||||
grad(V, F, G);
|
||||
Eigen::SparseMatrix<double> Dx = G.block(0, 0, F.rows(), V.rows());
|
||||
Eigen::SparseMatrix<double> Dy = G.block(F.rows(), 0, F.rows(), V.rows());
|
||||
Eigen::SparseMatrix<double> Dz = G.block(2 * F.rows(), 0, F.rows(), V.rows());
|
||||
|
||||
D1 = F1.col(0).asDiagonal() * Dx + F1.col(1).asDiagonal() * Dy + F1.col(2).asDiagonal() * Dz;
|
||||
D2 = F2.col(0).asDiagonal() * Dx + F2.col(1).asDiagonal() * Dy + F2.col(2).asDiagonal() * Dz;
|
||||
}
|
||||
|
||||
static inline void compute_weighted_jacobians(SLIMData &s, const Eigen::MatrixXd &uv)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
|
||||
/* Ji=[D1*u,D2*u,D1*v,D2*v] */
|
||||
s.Ji.col(0) = s.Dx * uv.col(0);
|
||||
s.Ji.col(1) = s.Dy * uv.col(0);
|
||||
s.Ji.col(2) = s.Dx * uv.col(1);
|
||||
s.Ji.col(3) = s.Dy * uv.col(1);
|
||||
|
||||
/* Add weights. */
|
||||
Eigen::VectorXd weights = s.weightPerFaceMap.cast<double>();
|
||||
s.Ji.col(0) = weights.cwiseProduct(s.Ji.col(0));
|
||||
s.Ji.col(1) = weights.cwiseProduct(s.Ji.col(1));
|
||||
s.Ji.col(2) = weights.cwiseProduct(s.Ji.col(2));
|
||||
s.Ji.col(3) = weights.cwiseProduct(s.Ji.col(3));
|
||||
}
|
||||
|
||||
static inline void compute_unweighted_jacobians(SLIMData &s, const Eigen::MatrixXd &uv)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
|
||||
/* Ji=[D1*u,D2*u,D1*v,D2*v] */
|
||||
s.Ji.col(0) = s.Dx * uv.col(0);
|
||||
s.Ji.col(1) = s.Dy * uv.col(0);
|
||||
s.Ji.col(2) = s.Dx * uv.col(1);
|
||||
s.Ji.col(3) = s.Dy * uv.col(1);
|
||||
}
|
||||
|
||||
static inline void compute_jacobians(SLIMData &s, const Eigen::MatrixXd &uv)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
|
||||
if (s.withWeightedParameterization) {
|
||||
compute_weighted_jacobians(s, uv);
|
||||
}
|
||||
else {
|
||||
compute_unweighted_jacobians(s, uv);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void update_weights_and_closest_rotations(SLIMData &s, Eigen::MatrixXd &uv)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
compute_jacobians(s, uv);
|
||||
|
||||
const double eps = 1e-8;
|
||||
double exp_f = s.exp_factor;
|
||||
|
||||
for (int i = 0; i < s.Ji.rows(); ++i) {
|
||||
using Mat2 = Eigen::Matrix<double, 2, 2>;
|
||||
using Vec2 = Eigen::Matrix<double, 2, 1>;
|
||||
Mat2 ji, ri, ti, ui, vi;
|
||||
Vec2 sing;
|
||||
Vec2 closest_sing_vec;
|
||||
Mat2 mat_W;
|
||||
Vec2 m_sing_new;
|
||||
double s1, s2;
|
||||
|
||||
ji(0, 0) = s.Ji(i, 0);
|
||||
ji(0, 1) = s.Ji(i, 1);
|
||||
ji(1, 0) = s.Ji(i, 2);
|
||||
ji(1, 1) = s.Ji(i, 3);
|
||||
|
||||
polar_svd(ji, ri, ti, ui, sing, vi);
|
||||
|
||||
s1 = sing(0);
|
||||
s2 = sing(1);
|
||||
|
||||
/* Update Weights according to energy. */
|
||||
switch (s.slim_energy) {
|
||||
case SLIMData::ARAP: {
|
||||
m_sing_new << 1, 1;
|
||||
break;
|
||||
}
|
||||
case SLIMData::SYMMETRIC_DIRICHLET: {
|
||||
double s1_g = 2 * (s1 - pow(s1, -3));
|
||||
double s2_g = 2 * (s2 - pow(s2, -3));
|
||||
m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1)));
|
||||
break;
|
||||
}
|
||||
case SLIMData::LOG_ARAP: {
|
||||
double s1_g = 2 * (log(s1) / s1);
|
||||
double s2_g = 2 * (log(s2) / s2);
|
||||
m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1)));
|
||||
break;
|
||||
}
|
||||
case SLIMData::CONFORMAL: {
|
||||
double s1_g = 1 / (2 * s2) - s2 / (2 * pow(s1, 2));
|
||||
double s2_g = 1 / (2 * s1) - s1 / (2 * pow(s2, 2));
|
||||
|
||||
double geo_avg = sqrt(s1 * s2);
|
||||
double s1_min = geo_avg;
|
||||
double s2_min = geo_avg;
|
||||
|
||||
m_sing_new << sqrt(s1_g / (2 * (s1 - s1_min))), sqrt(s2_g / (2 * (s2 - s2_min)));
|
||||
|
||||
/* Change local step. */
|
||||
closest_sing_vec << s1_min, s2_min;
|
||||
ri = ui * closest_sing_vec.asDiagonal() * vi.transpose();
|
||||
break;
|
||||
}
|
||||
case SLIMData::EXP_CONFORMAL: {
|
||||
double s1_g = 2 * (s1 - pow(s1, -3));
|
||||
double s2_g = 2 * (s2 - pow(s2, -3));
|
||||
|
||||
double in_exp = exp_f * ((pow(s1, 2) + pow(s2, 2)) / (2 * s1 * s2));
|
||||
double exp_thing = exp(in_exp);
|
||||
|
||||
s1_g *= exp_thing * exp_f;
|
||||
s2_g *= exp_thing * exp_f;
|
||||
|
||||
m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1)));
|
||||
break;
|
||||
}
|
||||
case SLIMData::EXP_SYMMETRIC_DIRICHLET: {
|
||||
double s1_g = 2 * (s1 - pow(s1, -3));
|
||||
double s2_g = 2 * (s2 - pow(s2, -3));
|
||||
|
||||
double in_exp = exp_f * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2));
|
||||
double exp_thing = exp(in_exp);
|
||||
|
||||
s1_g *= exp_thing * exp_f;
|
||||
s2_g *= exp_thing * exp_f;
|
||||
|
||||
m_sing_new << sqrt(s1_g / (2 * (s1 - 1))), sqrt(s2_g / (2 * (s2 - 1)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::abs(s1 - 1) < eps) {
|
||||
m_sing_new(0) = 1;
|
||||
}
|
||||
if (std::abs(s2 - 1) < eps) {
|
||||
m_sing_new(1) = 1;
|
||||
}
|
||||
mat_W = ui * m_sing_new.asDiagonal() * ui.transpose();
|
||||
|
||||
s.W_11(i) = mat_W(0, 0);
|
||||
s.W_12(i) = mat_W(0, 1);
|
||||
s.W_21(i) = mat_W(1, 0);
|
||||
s.W_22(i) = mat_W(1, 1);
|
||||
|
||||
/* 2) Update local step (doesn't have to be a rotation, for instance in case of conformal
|
||||
* energy). */
|
||||
s.Ri(i, 0) = ri(0, 0);
|
||||
s.Ri(i, 1) = ri(1, 0);
|
||||
s.Ri(i, 2) = ri(0, 1);
|
||||
s.Ri(i, 3) = ri(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename DerivedV, typename DerivedF>
|
||||
static inline void local_basis(const Eigen::PlainObjectBase<DerivedV> &V,
|
||||
const Eigen::PlainObjectBase<DerivedF> &F,
|
||||
Eigen::PlainObjectBase<DerivedV> &B1,
|
||||
Eigen::PlainObjectBase<DerivedV> &B2,
|
||||
Eigen::PlainObjectBase<DerivedV> &B3)
|
||||
{
|
||||
using namespace Eigen;
|
||||
using namespace std;
|
||||
B1.resize(F.rows(), 3);
|
||||
B2.resize(F.rows(), 3);
|
||||
B3.resize(F.rows(), 3);
|
||||
|
||||
for (unsigned i = 0; i < F.rows(); ++i) {
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v1 =
|
||||
(V.row(F(i, 1)) - V.row(F(i, 0))).normalized();
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> t = V.row(F(i, 2)) - V.row(F(i, 0));
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v3 = v1.cross(t).normalized();
|
||||
Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v2 = v1.cross(v3).normalized();
|
||||
|
||||
B1.row(i) = v1;
|
||||
B2.row(i) = -v2;
|
||||
B3.row(i) = v3;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pre_calc(SLIMData &s)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
if (!s.has_pre_calc) {
|
||||
s.v_n = s.v_num;
|
||||
s.f_n = s.f_num;
|
||||
|
||||
s.dim = 2;
|
||||
Eigen::MatrixXd F1, F2, F3;
|
||||
local_basis(s.V, s.F, F1, F2, F3);
|
||||
compute_surface_gradient_matrix(s.V, s.F, F1, F2, s.Dx, s.Dy);
|
||||
|
||||
s.W_11.resize(s.f_n);
|
||||
s.W_12.resize(s.f_n);
|
||||
s.W_21.resize(s.f_n);
|
||||
s.W_22.resize(s.f_n);
|
||||
|
||||
s.Dx.makeCompressed();
|
||||
s.Dy.makeCompressed();
|
||||
s.Dz.makeCompressed();
|
||||
s.Ri.resize(s.f_n, s.dim * s.dim);
|
||||
s.Ji.resize(s.f_n, s.dim * s.dim);
|
||||
s.rhs.resize(s.dim * s.v_num);
|
||||
|
||||
/* Flattened weight matrix. */
|
||||
s.WGL_M.resize(s.dim * s.dim * s.f_n);
|
||||
for (int i = 0; i < s.dim * s.dim; i++) {
|
||||
for (int j = 0; j < s.f_n; j++) {
|
||||
s.WGL_M(i * s.f_n + j) = s.M(j);
|
||||
}
|
||||
}
|
||||
|
||||
s.first_solve = true;
|
||||
s.has_pre_calc = true;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void buildA(SLIMData &s, Eigen::SparseMatrix<double> &A)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
/* Formula (35) in paper. */
|
||||
std::vector<Eigen::Triplet<double>> IJV;
|
||||
|
||||
IJV.reserve(4 * (s.Dx.outerSize() + s.Dy.outerSize()));
|
||||
|
||||
/* A = [W11*Dx, W12*Dx;
|
||||
* W11*Dy, W12*Dy;
|
||||
* W21*Dx, W22*Dx;
|
||||
* W21*Dy, W22*Dy]; */
|
||||
for (int k = 0; k < s.Dx.outerSize(); ++k) {
|
||||
for (Eigen::SparseMatrix<double>::InnerIterator it(s.Dx, k); it; ++it) {
|
||||
int dx_r = it.row();
|
||||
int dx_c = it.col();
|
||||
double val = it.value();
|
||||
double weight = s.weightPerFaceMap(dx_r);
|
||||
|
||||
IJV.emplace_back(dx_r, dx_c, weight * val * s.W_11(dx_r));
|
||||
IJV.emplace_back(dx_r, s.v_n + dx_c, weight * val * s.W_12(dx_r));
|
||||
|
||||
IJV.emplace_back(2 * s.f_n + dx_r, dx_c, weight * val * s.W_21(dx_r));
|
||||
IJV.emplace_back(2 * s.f_n + dx_r, s.v_n + dx_c, weight * val * s.W_22(dx_r));
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < s.Dy.outerSize(); ++k) {
|
||||
for (Eigen::SparseMatrix<double>::InnerIterator it(s.Dy, k); it; ++it) {
|
||||
int dy_r = it.row();
|
||||
int dy_c = it.col();
|
||||
double val = it.value();
|
||||
double weight = s.weightPerFaceMap(dy_r);
|
||||
|
||||
IJV.emplace_back(s.f_n + dy_r, dy_c, weight * val * s.W_11(dy_r));
|
||||
IJV.emplace_back(s.f_n + dy_r, s.v_n + dy_c, weight * val * s.W_12(dy_r));
|
||||
|
||||
IJV.emplace_back(3 * s.f_n + dy_r, dy_c, weight * val * s.W_21(dy_r));
|
||||
IJV.emplace_back(3 * s.f_n + dy_r, s.v_n + dy_c, weight * val * s.W_22(dy_r));
|
||||
}
|
||||
}
|
||||
|
||||
A.setFromTriplets(IJV.begin(), IJV.end());
|
||||
}
|
||||
|
||||
static inline void buildRhs(SLIMData &s, const Eigen::SparseMatrix<double> &At)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
|
||||
Eigen::VectorXd f_rhs(s.dim * s.dim * s.f_n);
|
||||
f_rhs.setZero();
|
||||
|
||||
/* b = [W11*R11 + W12*R21; (formula (36))
|
||||
* W11*R12 + W12*R22;
|
||||
* W21*R11 + W22*R21;
|
||||
* W21*R12 + W22*R22]; */
|
||||
for (int i = 0; i < s.f_n; i++) {
|
||||
f_rhs(i + 0 * s.f_n) = s.W_11(i) * s.Ri(i, 0) + s.W_12(i) * s.Ri(i, 1);
|
||||
f_rhs(i + 1 * s.f_n) = s.W_11(i) * s.Ri(i, 2) + s.W_12(i) * s.Ri(i, 3);
|
||||
f_rhs(i + 2 * s.f_n) = s.W_21(i) * s.Ri(i, 0) + s.W_22(i) * s.Ri(i, 1);
|
||||
f_rhs(i + 3 * s.f_n) = s.W_21(i) * s.Ri(i, 2) + s.W_22(i) * s.Ri(i, 3);
|
||||
}
|
||||
|
||||
Eigen::VectorXd uv_flat(s.dim * s.v_n);
|
||||
for (int i = 0; i < s.dim; i++) {
|
||||
for (int j = 0; j < s.v_n; j++) {
|
||||
uv_flat(s.v_n * i + j) = s.V_o(j, i);
|
||||
}
|
||||
}
|
||||
|
||||
s.rhs = (At * s.WGL_M.asDiagonal() * f_rhs + s.proximal_p * uv_flat);
|
||||
}
|
||||
|
||||
static inline void add_soft_constraints(SLIMData &s, Eigen::SparseMatrix<double> &L)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
int v_n = s.v_num;
|
||||
for (int d = 0; d < s.dim; d++) {
|
||||
for (int i = 0; i < s.b.rows(); i++) {
|
||||
int v_idx = s.b(i);
|
||||
s.rhs(d * v_n + v_idx) += s.soft_const_p * s.bc(i, d); /* Right hand side. */
|
||||
L.coeffRef(d * v_n + v_idx, d * v_n + v_idx) += s.soft_const_p; /* Diagonal of matrix. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void build_linear_system(SLIMData &s, Eigen::SparseMatrix<double> &L)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
/* Formula (35) in paper. */
|
||||
Eigen::SparseMatrix<double> A(s.dim * s.dim * s.f_n, s.dim * s.v_n);
|
||||
buildA(s, A);
|
||||
|
||||
Eigen::SparseMatrix<double> At = A.transpose();
|
||||
At.makeCompressed();
|
||||
|
||||
Eigen::SparseMatrix<double> id_m(At.rows(), At.rows());
|
||||
id_m.setIdentity();
|
||||
|
||||
/* Add proximal penalty. */
|
||||
L = At * s.WGL_M.asDiagonal() * A + s.proximal_p * id_m; /* Add also a proximal term. */
|
||||
L.makeCompressed();
|
||||
|
||||
buildRhs(s, At);
|
||||
Eigen::SparseMatrix<double> OldL = L;
|
||||
add_soft_constraints(s, L);
|
||||
L.makeCompressed();
|
||||
}
|
||||
|
||||
static inline double compute_energy_with_jacobians(SLIMData &s,
|
||||
const Eigen::MatrixXd &Ji,
|
||||
Eigen::VectorXd &areas,
|
||||
Eigen::VectorXd &singularValues,
|
||||
bool gatherSingularValues)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
double energy = 0;
|
||||
|
||||
Eigen::Matrix<double, 2, 2> ji;
|
||||
for (int i = 0; i < s.f_n; i++) {
|
||||
ji(0, 0) = Ji(i, 0);
|
||||
ji(0, 1) = Ji(i, 1);
|
||||
ji(1, 0) = Ji(i, 2);
|
||||
ji(1, 1) = Ji(i, 3);
|
||||
|
||||
using Mat2 = Eigen::Matrix<double, 2, 2>;
|
||||
using Vec2 = Eigen::Matrix<double, 2, 1>;
|
||||
Mat2 ri, ti, ui, vi;
|
||||
Vec2 sing;
|
||||
polar_svd(ji, ri, ti, ui, sing, vi);
|
||||
double s1 = sing(0);
|
||||
double s2 = sing(1);
|
||||
|
||||
switch (s.slim_energy) {
|
||||
case SLIMData::ARAP: {
|
||||
energy += areas(i) * (pow(s1 - 1, 2) + pow(s2 - 1, 2));
|
||||
break;
|
||||
}
|
||||
case SLIMData::SYMMETRIC_DIRICHLET: {
|
||||
energy += areas(i) * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2));
|
||||
|
||||
if (gatherSingularValues) {
|
||||
singularValues(i) = s1;
|
||||
singularValues(i + s.F.rows()) = s2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SLIMData::EXP_SYMMETRIC_DIRICHLET: {
|
||||
energy += areas(i) *
|
||||
exp(s.exp_factor * (pow(s1, 2) + pow(s1, -2) + pow(s2, 2) + pow(s2, -2)));
|
||||
break;
|
||||
}
|
||||
case SLIMData::LOG_ARAP: {
|
||||
energy += areas(i) * (pow(log(s1), 2) + pow(log(s2), 2));
|
||||
break;
|
||||
}
|
||||
case SLIMData::CONFORMAL: {
|
||||
energy += areas(i) * ((pow(s1, 2) + pow(s2, 2)) / (2 * s1 * s2));
|
||||
break;
|
||||
}
|
||||
case SLIMData::EXP_CONFORMAL: {
|
||||
energy += areas(i) * exp(s.exp_factor * ((pow(s1, 2) + pow(s2, 2)) / (2 * s1 * s2)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return energy;
|
||||
}
|
||||
|
||||
static inline double compute_soft_const_energy(SLIMData &s, Eigen::MatrixXd &V_o)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
double e = 0;
|
||||
for (int i = 0; i < s.b.rows(); i++) {
|
||||
e += s.soft_const_p * (s.bc.row(i) - V_o.row(s.b(i))).squaredNorm();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static inline double compute_energy(SLIMData &s,
|
||||
Eigen::MatrixXd &V_new,
|
||||
Eigen::VectorXd &singularValues,
|
||||
bool gatherSingularValues)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
compute_jacobians(s, V_new);
|
||||
return compute_energy_with_jacobians(s, s.Ji, s.M, singularValues, gatherSingularValues) +
|
||||
compute_soft_const_energy(s, V_new);
|
||||
}
|
||||
|
||||
static inline double compute_energy(SLIMData &s, Eigen::MatrixXd &V_new)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
Eigen::VectorXd temp;
|
||||
return compute_energy(s, V_new, temp, false);
|
||||
}
|
||||
|
||||
static inline double compute_energy(SLIMData &s,
|
||||
Eigen::MatrixXd &V_new,
|
||||
Eigen::VectorXd &singularValues)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
return compute_energy(s, V_new, singularValues, true);
|
||||
}
|
||||
|
||||
void slim_precompute(Eigen::MatrixXd &V,
|
||||
Eigen::MatrixXi &F,
|
||||
Eigen::MatrixXd &V_init,
|
||||
SLIMData &data,
|
||||
SLIMData::SLIM_ENERGY slim_energy,
|
||||
Eigen::VectorXi &b,
|
||||
Eigen::MatrixXd &bc,
|
||||
double soft_p)
|
||||
{
|
||||
BLI_assert(data.valid);
|
||||
data.V = V;
|
||||
data.F = F;
|
||||
data.V_o = V_init;
|
||||
|
||||
data.v_num = V.rows();
|
||||
data.f_num = F.rows();
|
||||
|
||||
data.slim_energy = slim_energy;
|
||||
|
||||
data.b = b;
|
||||
data.bc = bc;
|
||||
data.soft_const_p = soft_p;
|
||||
|
||||
data.proximal_p = 0.0001;
|
||||
|
||||
doublearea(V, F, data.M);
|
||||
data.M /= 2.;
|
||||
data.mesh_area = data.M.sum();
|
||||
|
||||
data.mesh_improvement_3d = false; /* Whether to use a jacobian derived from a real mesh or an
|
||||
* abstract regular mesh (used for mesh improvement). */
|
||||
data.exp_factor =
|
||||
1.0; /* Param used only for exponential energies (e.g exponential symmetric dirichlet). */
|
||||
|
||||
assert(F.cols() == 3);
|
||||
|
||||
pre_calc(data);
|
||||
|
||||
data.energy = compute_energy(data, data.V_o) / data.mesh_area;
|
||||
}
|
||||
|
||||
inline double computeGlobalScaleInvarianceFactor(Eigen::VectorXd &singularValues,
|
||||
Eigen::VectorXd &areas)
|
||||
{
|
||||
int nFaces = singularValues.rows() / 2;
|
||||
|
||||
Eigen::VectorXd areasChained(2 * nFaces);
|
||||
areasChained << areas, areas;
|
||||
|
||||
/* Per face energy for face i with singvals si1 and si2 and area ai when scaling geometry by x is
|
||||
*
|
||||
* ai*(si1*x)^2 + ai*(si2*x)^2 + ai/(si1*x)^2 + ai/(si2*x)^2)
|
||||
*
|
||||
* The combined Energy of all faces is therefore
|
||||
* (s1 and s2 are the sums over all ai*(si1^2) and ai*(si2^2) respectively. t1 and t2
|
||||
* are the sums over all ai/(si1^2) and ai/(si2^2) respectively)
|
||||
*
|
||||
* s1*(x^2) + s2*(x^2) + t1/(x^2) + t2/(x^2)
|
||||
*
|
||||
* with a = (s1 + s2) and b = (t1 + t2) we get
|
||||
*
|
||||
* ax^2 + b/x^2
|
||||
*
|
||||
* it's derivative is
|
||||
*
|
||||
* 2ax - 2b/(x^3)
|
||||
*
|
||||
* and when we set it zero we get
|
||||
*
|
||||
* x^4 = b/a => x = sqrt(sqrt(b/a))
|
||||
*/
|
||||
|
||||
Eigen::VectorXd squaredSingularValues = singularValues.cwiseProduct(singularValues);
|
||||
Eigen::VectorXd inverseSquaredSingularValues =
|
||||
singularValues.cwiseProduct(singularValues).cwiseInverse();
|
||||
|
||||
Eigen::VectorXd weightedSquaredSingularValues = squaredSingularValues.cwiseProduct(areasChained);
|
||||
Eigen::VectorXd weightedInverseSquaredSingularValues = inverseSquaredSingularValues.cwiseProduct(
|
||||
areasChained);
|
||||
|
||||
double s1 = weightedSquaredSingularValues.head(nFaces).sum();
|
||||
double s2 = weightedSquaredSingularValues.tail(nFaces).sum();
|
||||
|
||||
double t1 = weightedInverseSquaredSingularValues.head(nFaces).sum();
|
||||
double t2 = weightedInverseSquaredSingularValues.tail(nFaces).sum();
|
||||
|
||||
double a = s1 + s2;
|
||||
double b = t1 + t2;
|
||||
|
||||
double x = sqrt(sqrt(b / a));
|
||||
|
||||
return 1 / x;
|
||||
}
|
||||
|
||||
static inline void solve_weighted_arap(SLIMData &s, Eigen::MatrixXd &uv)
|
||||
{
|
||||
BLI_assert(s.valid);
|
||||
using namespace Eigen;
|
||||
|
||||
Eigen::SparseMatrix<double> L;
|
||||
build_linear_system(s, L);
|
||||
|
||||
/* Solve. */
|
||||
Eigen::VectorXd Uc;
|
||||
SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
|
||||
Uc = solver.compute(L).solve(s.rhs);
|
||||
|
||||
for (int i = 0; i < Uc.size(); i++) {
|
||||
if (!std::isfinite(Uc(i))) {
|
||||
throw SlimFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < s.dim; i++) {
|
||||
uv.col(i) = Uc.block(i * s.v_n, 0, s.v_n, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::MatrixXd slim_solve(SLIMData &data, int iter_num)
|
||||
{
|
||||
BLI_assert(data.valid);
|
||||
Eigen::VectorXd singularValues;
|
||||
bool are_pins_present = data.b.rows() > 0;
|
||||
|
||||
if (are_pins_present) {
|
||||
singularValues.resize(data.F.rows() * 2);
|
||||
data.energy = compute_energy(data, data.V_o, singularValues) / data.mesh_area;
|
||||
}
|
||||
|
||||
for (int i = 0; i < iter_num; i++) {
|
||||
Eigen::MatrixXd dest_res;
|
||||
dest_res = data.V_o;
|
||||
|
||||
/* Solve Weighted Proxy. */
|
||||
update_weights_and_closest_rotations(data, dest_res);
|
||||
solve_weighted_arap(data, dest_res);
|
||||
|
||||
std::function<double(Eigen::MatrixXd &)> compute_energy_func = [&](Eigen::MatrixXd &aaa) {
|
||||
return are_pins_present ? compute_energy(data, aaa, singularValues) :
|
||||
compute_energy(data, aaa);
|
||||
};
|
||||
|
||||
data.energy = flip_avoiding_line_search(data.F,
|
||||
data.V_o,
|
||||
dest_res,
|
||||
compute_energy_func,
|
||||
data.energy * data.mesh_area) /
|
||||
data.mesh_area;
|
||||
|
||||
if (are_pins_present) {
|
||||
data.globalScaleInvarianceFactor = computeGlobalScaleInvarianceFactor(singularValues,
|
||||
data.M);
|
||||
data.Dx /= data.globalScaleInvarianceFactor;
|
||||
data.Dy /= data.globalScaleInvarianceFactor;
|
||||
data.energy = compute_energy(data, data.V_o, singularValues) / data.mesh_area;
|
||||
}
|
||||
}
|
||||
|
||||
return data.V_o;
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
122
blender-5.2.0/intern/slim/intern/slim.h
Normal file
122
blender-5.2.0/intern/slim/intern/slim.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Michael Rabinovich
|
||||
* 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <Eigen/Sparse>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace slim {
|
||||
|
||||
class SlimFailedException : public std::runtime_error {
|
||||
public:
|
||||
SlimFailedException() : std::runtime_error("Slim operation failed") {}
|
||||
};
|
||||
|
||||
/* Compute a SLIM map as derived in "Scalable Locally Injective Maps" [Rabinovich et al. 2016].. */
|
||||
struct SLIMData {
|
||||
bool valid = true;
|
||||
|
||||
/* Input. */
|
||||
Eigen::MatrixXd V; /* #V by 3 list of mesh vertex positions. */
|
||||
Eigen::MatrixXi F; /* #F by 3/3 list of mesh faces (triangles). */
|
||||
enum SLIM_ENERGY {
|
||||
ARAP,
|
||||
LOG_ARAP,
|
||||
SYMMETRIC_DIRICHLET,
|
||||
CONFORMAL,
|
||||
EXP_CONFORMAL,
|
||||
EXP_SYMMETRIC_DIRICHLET
|
||||
};
|
||||
SLIM_ENERGY slim_energy;
|
||||
|
||||
/* Optional Input. */
|
||||
/* Soft constraints. */
|
||||
Eigen::VectorXi b;
|
||||
Eigen::MatrixXd bc;
|
||||
double soft_const_p;
|
||||
|
||||
double exp_factor; /* Used for exponential energies, ignored otherwise. */
|
||||
bool mesh_improvement_3d; /* Only supported for 3d. */
|
||||
|
||||
int reflection_mode;
|
||||
bool skipInitialization = false;
|
||||
bool validPreInitialization = false;
|
||||
double expectedSurfaceAreaOfResultingMap = 0;
|
||||
|
||||
/* Output. */
|
||||
Eigen::MatrixXd V_o; /* #V by dim list of mesh vertex positions (dim = 2 for parametrization, 3
|
||||
otherwise). */
|
||||
Eigen::MatrixXd
|
||||
oldUVs; /* #V by dim list of mesh vertex positions (dim = 2 for parametrization,. */
|
||||
/* 3 otherwise). */
|
||||
|
||||
/* Weight-map for weighted parameterization. */
|
||||
bool withWeightedParameterization;
|
||||
Eigen::VectorXf weightmap;
|
||||
Eigen::VectorXf weightPerFaceMap;
|
||||
double weightInfluence;
|
||||
double globalScaleInvarianceFactor = 1.0;
|
||||
|
||||
double energy; /* Objective value. */
|
||||
|
||||
/* Internal. */
|
||||
Eigen::VectorXd M;
|
||||
double mesh_area;
|
||||
double avg_edge_length;
|
||||
int v_num;
|
||||
int f_num;
|
||||
double proximal_p;
|
||||
|
||||
Eigen::VectorXd WGL_M;
|
||||
Eigen::VectorXd rhs;
|
||||
Eigen::MatrixXd Ri, Ji;
|
||||
Eigen::VectorXd W_11;
|
||||
Eigen::VectorXd W_12;
|
||||
Eigen::VectorXd W_13;
|
||||
Eigen::VectorXd W_21;
|
||||
Eigen::VectorXd W_22;
|
||||
Eigen::VectorXd W_23;
|
||||
Eigen::VectorXd W_31;
|
||||
Eigen::VectorXd W_32;
|
||||
Eigen::VectorXd W_33;
|
||||
Eigen::SparseMatrix<double> Dx, Dy, Dz;
|
||||
int f_n, v_n;
|
||||
bool first_solve;
|
||||
bool has_pre_calc = false;
|
||||
int dim;
|
||||
};
|
||||
|
||||
/* Compute necessary information to start using SLIM
|
||||
* Inputs:
|
||||
* V #V by 3 list of mesh vertex positions
|
||||
* F #F by 3/3 list of mesh faces (triangles)
|
||||
* b list of boundary indices into V
|
||||
* bc #b by dim list of boundary conditions
|
||||
* soft_p Soft penalty factor (can be zero)
|
||||
* slim_energy Energy to minimize
|
||||
*/
|
||||
void slim_precompute(Eigen::MatrixXd &V,
|
||||
Eigen::MatrixXi &F,
|
||||
Eigen::MatrixXd &V_init,
|
||||
SLIMData &data,
|
||||
SLIMData::SLIM_ENERGY slim_energy,
|
||||
Eigen::VectorXi &b,
|
||||
Eigen::MatrixXd &bc,
|
||||
double soft_p);
|
||||
|
||||
/* Run iter_num iterations of SLIM
|
||||
* Outputs:
|
||||
* V_o (in SLIMData): #V by dim list of mesh vertex positions
|
||||
*/
|
||||
Eigen::MatrixXd slim_solve(SLIMData &data, int iter_num);
|
||||
|
||||
} // namespace slim
|
||||
50
blender-5.2.0/intern/slim/intern/slim_matrix_transfer.cpp
Normal file
50
blender-5.2.0/intern/slim/intern/slim_matrix_transfer.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
#include "geometry_data_retrieval.h"
|
||||
#include "slim.h"
|
||||
#include "slim_matrix_transfer.h"
|
||||
|
||||
namespace slim {
|
||||
|
||||
MatrixTransferChart::MatrixTransferChart() = default;
|
||||
MatrixTransferChart::MatrixTransferChart(MatrixTransferChart &&) = default;
|
||||
MatrixTransferChart::~MatrixTransferChart() = default;
|
||||
MatrixTransfer::MatrixTransfer() = default;
|
||||
MatrixTransfer::~MatrixTransfer() = default;
|
||||
|
||||
void MatrixTransferChart::free_slim_data()
|
||||
{
|
||||
data.reset(nullptr);
|
||||
}
|
||||
|
||||
void MatrixTransfer::setup_slim_data(MatrixTransferChart &chart) const
|
||||
{
|
||||
SLIMDataPtr slim_data = std::make_unique<SLIMDataPtr::element_type>();
|
||||
|
||||
try {
|
||||
if (!chart.succeeded) {
|
||||
throw SlimFailedException();
|
||||
}
|
||||
|
||||
GeometryData geometry_data(*this, chart);
|
||||
geometry_data.construct_slim_data(*slim_data, skip_initialization, reflection_mode);
|
||||
|
||||
chart.pinned_vertices_num = geometry_data.number_of_pinned_vertices;
|
||||
}
|
||||
catch (SlimFailedException &) {
|
||||
slim_data->valid = false;
|
||||
chart.succeeded = false;
|
||||
}
|
||||
|
||||
chart.data = std::move(slim_data);
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
157
blender-5.2.0/intern/slim/intern/slim_parametrizer.cpp
Normal file
157
blender-5.2.0/intern/slim/intern/slim_parametrizer.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "slim.h"
|
||||
#include "slim_matrix_transfer.h"
|
||||
|
||||
#include "area_compensation.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <Eigen/Sparse>
|
||||
#include <Eigen/SparseCholesky>
|
||||
|
||||
namespace slim {
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
static void transfer_uvs_back_to_native_part(MatrixTransferChart &chart, Eigen::MatrixXd &uv)
|
||||
{
|
||||
if (!chart.succeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &uv_coordinate_array = chart.uv_matrices;
|
||||
int number_of_vertices = chart.verts_num;
|
||||
|
||||
for (int i = 0; i < number_of_vertices; i++) {
|
||||
uv_coordinate_array[i] = uv(i, 0);
|
||||
uv_coordinate_array[number_of_vertices + i] = uv(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static Eigen::MatrixXd get_interactive_result_blended_with_original(float blend,
|
||||
const SLIMData &slim_data)
|
||||
{
|
||||
Eigen::MatrixXd original_map_weighted = blend * slim_data.oldUVs;
|
||||
Eigen::MatrixXd interactive_result_map = (1.0 - blend) * slim_data.V_o;
|
||||
return original_map_weighted + interactive_result_map;
|
||||
}
|
||||
|
||||
static void adjust_pins(SLIMData &slim_data, const PinnedVertexData &pinned_vertex_data)
|
||||
{
|
||||
if (!slim_data.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &pinned_vertex_indices = pinned_vertex_data.pinned_vertex_indices;
|
||||
const auto &pinned_vertex_positions_2D = pinned_vertex_data.pinned_vertex_positions_2D;
|
||||
const auto &selected_pins = pinned_vertex_data.selected_pins;
|
||||
|
||||
int n_pins = pinned_vertex_indices.size();
|
||||
int n_selected_pins = selected_pins.size();
|
||||
|
||||
Eigen::VectorXi old_pin_indices = slim_data.b;
|
||||
Eigen::MatrixXd old_pin_positions = slim_data.bc;
|
||||
|
||||
slim_data.b.resize(n_pins);
|
||||
slim_data.bc.resize(n_pins, 2);
|
||||
|
||||
int old_pin_pointer = 0;
|
||||
int selected_pin_pointer = 0;
|
||||
|
||||
for (int new_pin_pointer = 0; new_pin_pointer < n_pins; new_pin_pointer++) {
|
||||
|
||||
int pinned_vertex_index = pinned_vertex_indices[new_pin_pointer];
|
||||
slim_data.b(new_pin_pointer) = pinned_vertex_index;
|
||||
|
||||
while ((old_pin_pointer < old_pin_indices.size()) &&
|
||||
(old_pin_indices(old_pin_pointer) < pinned_vertex_index))
|
||||
{
|
||||
++old_pin_pointer;
|
||||
}
|
||||
bool old_pointer_valid = (old_pin_pointer < old_pin_indices.size()) &&
|
||||
(old_pin_indices(old_pin_pointer) == pinned_vertex_index);
|
||||
|
||||
while ((selected_pin_pointer < n_selected_pins) &&
|
||||
(selected_pins[selected_pin_pointer] < pinned_vertex_index))
|
||||
{
|
||||
++selected_pin_pointer;
|
||||
}
|
||||
bool pin_selected = (selected_pin_pointer < n_selected_pins) &&
|
||||
(selected_pins[selected_pin_pointer] == pinned_vertex_index);
|
||||
|
||||
if (!pin_selected && old_pointer_valid) {
|
||||
slim_data.bc.row(new_pin_pointer) = old_pin_positions.row(old_pin_pointer);
|
||||
}
|
||||
else {
|
||||
slim_data.bc(new_pin_pointer, 0) = pinned_vertex_positions_2D[2 * new_pin_pointer];
|
||||
slim_data.bc(new_pin_pointer, 1) = pinned_vertex_positions_2D[2 * new_pin_pointer + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MatrixTransferChart::transfer_uvs_blended(float blend)
|
||||
{
|
||||
if (!succeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
Eigen::MatrixXd blended_uvs = get_interactive_result_blended_with_original(blend, *data);
|
||||
correct_map_surface_area_if_necessary(*data);
|
||||
transfer_uvs_back_to_native_part(*this, blended_uvs);
|
||||
}
|
||||
|
||||
void MatrixTransferChart::try_slim_solve(int iter_num)
|
||||
{
|
||||
if (!succeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
slim_solve(*data, iter_num);
|
||||
}
|
||||
catch (SlimFailedException &) {
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MatrixTransferChart::parametrize_single_iteration()
|
||||
{
|
||||
int number_of_iterations = 1;
|
||||
try_slim_solve(number_of_iterations);
|
||||
}
|
||||
|
||||
void MatrixTransfer::parametrize_live(MatrixTransferChart &chart,
|
||||
const PinnedVertexData &pinned_vertex_data)
|
||||
{
|
||||
int number_of_iterations = 3;
|
||||
adjust_pins(*chart.data, pinned_vertex_data);
|
||||
|
||||
chart.try_slim_solve(number_of_iterations);
|
||||
|
||||
correct_map_surface_area_if_necessary(*chart.data);
|
||||
transfer_uvs_back_to_native_part(chart, chart.data->V_o);
|
||||
}
|
||||
|
||||
void MatrixTransfer::parametrize()
|
||||
{
|
||||
for (MatrixTransferChart &chart : charts) {
|
||||
setup_slim_data(chart);
|
||||
|
||||
chart.try_slim_solve(n_iterations);
|
||||
|
||||
correct_map_surface_area_if_necessary(*chart.data);
|
||||
transfer_uvs_back_to_native_part(chart, chart.data->V_o);
|
||||
|
||||
chart.free_slim_data();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
292
blender-5.2.0/intern/slim/intern/uv_initializer.cpp
Normal file
292
blender-5.2.0/intern/slim/intern/uv_initializer.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#include "uv_initializer.h"
|
||||
|
||||
#include <Eigen/SparseLU>
|
||||
#include <cassert>
|
||||
|
||||
namespace slim {
|
||||
|
||||
static double compute_angle(const Eigen::Vector3d &a, const Eigen::Vector3d &b)
|
||||
{
|
||||
return acos(a.dot(b) / (a.norm() * b.norm()));
|
||||
}
|
||||
|
||||
static void find_vertex_to_opposite_angles_correspondence(
|
||||
const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
Eigen::SparseMatrix<double> &vertex_to_face_indices)
|
||||
{
|
||||
using t = Eigen::Triplet<double>;
|
||||
std::vector<t> coefficients;
|
||||
|
||||
for (int i = 0; i < f.rows(); i++) {
|
||||
|
||||
int vertex_index1 = f(i, 0);
|
||||
int vertex_index2 = f(i, 1);
|
||||
int vertex_index3 = f(i, 2);
|
||||
|
||||
double angle1 = compute_angle(v.row(vertex_index2) - v.row(vertex_index1),
|
||||
v.row(vertex_index3) - v.row(vertex_index1));
|
||||
double angle2 = compute_angle(v.row(vertex_index3) - v.row(vertex_index2),
|
||||
v.row(vertex_index1) - v.row(vertex_index2));
|
||||
double angle3 = compute_angle(v.row(vertex_index1) - v.row(vertex_index3),
|
||||
v.row(vertex_index2) - v.row(vertex_index3));
|
||||
|
||||
coefficients.emplace_back(vertex_index1, 2 * vertex_index2, angle3);
|
||||
coefficients.emplace_back(vertex_index1, 2 * vertex_index3 + 1, angle2);
|
||||
|
||||
coefficients.emplace_back(vertex_index2, 2 * vertex_index1 + 1, angle3);
|
||||
coefficients.emplace_back(vertex_index2, 2 * vertex_index3, angle1);
|
||||
|
||||
coefficients.emplace_back(vertex_index3, 2 * vertex_index1, angle2);
|
||||
coefficients.emplace_back(vertex_index3, 2 * vertex_index2 + 1, angle1);
|
||||
}
|
||||
|
||||
vertex_to_face_indices.setFromTriplets(coefficients.begin(), coefficients.end());
|
||||
}
|
||||
|
||||
static void find_vertex_to_its_angles_correspondence(
|
||||
const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
Eigen::SparseMatrix<double> &vertex_to_face_indices)
|
||||
{
|
||||
|
||||
using t = Eigen::Triplet<double>;
|
||||
std::vector<t> coefficients;
|
||||
|
||||
for (int i = 0; i < f.rows(); i++) {
|
||||
|
||||
int vertex_index1 = f(i, 0);
|
||||
int vertex_index2 = f(i, 1);
|
||||
int vertex_index3 = f(i, 2);
|
||||
|
||||
double angle1 = compute_angle(v.row(vertex_index2) - v.row(vertex_index1),
|
||||
v.row(vertex_index3) - v.row(vertex_index1));
|
||||
double angle2 = compute_angle(v.row(vertex_index3) - v.row(vertex_index2),
|
||||
v.row(vertex_index1) - v.row(vertex_index2));
|
||||
double angle3 = compute_angle(v.row(vertex_index1) - v.row(vertex_index3),
|
||||
v.row(vertex_index2) - v.row(vertex_index3));
|
||||
|
||||
coefficients.emplace_back(vertex_index1, 2 * vertex_index2, angle1);
|
||||
coefficients.emplace_back(vertex_index1, 2 * vertex_index3 + 1, angle1);
|
||||
|
||||
coefficients.emplace_back(vertex_index2, 2 * vertex_index1 + 1, angle2);
|
||||
coefficients.emplace_back(vertex_index2, 2 * vertex_index3, angle2);
|
||||
|
||||
coefficients.emplace_back(vertex_index3, 2 * vertex_index1, angle3);
|
||||
coefficients.emplace_back(vertex_index3, 2 * vertex_index2 + 1, angle3);
|
||||
}
|
||||
|
||||
vertex_to_face_indices.setFromTriplets(coefficients.begin(), coefficients.end());
|
||||
}
|
||||
|
||||
/* Implementation of different fixed-border parameterizations, mean value coordinates, harmonic,
|
||||
* tutte. */
|
||||
void convex_border_parameterization(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &uv,
|
||||
Method method)
|
||||
{
|
||||
int verts_num = uv.rows();
|
||||
int edges_num = e.rows();
|
||||
|
||||
Eigen::SparseMatrix<double> vertex_to_angles(verts_num, verts_num * 2);
|
||||
|
||||
switch (method) {
|
||||
case HARMONIC:
|
||||
find_vertex_to_opposite_angles_correspondence(f, v, vertex_to_angles);
|
||||
break;
|
||||
case MVC:
|
||||
find_vertex_to_its_angles_correspondence(f, v, vertex_to_angles);
|
||||
break;
|
||||
case TUTTE:
|
||||
break;
|
||||
}
|
||||
|
||||
int n_unknowns = verts_num - bnd.size();
|
||||
int n_knowns = bnd.size();
|
||||
|
||||
Eigen::SparseMatrix<double> aint(n_unknowns, n_unknowns);
|
||||
Eigen::SparseMatrix<double> abnd(n_unknowns, n_knowns);
|
||||
Eigen::VectorXd z(n_knowns);
|
||||
|
||||
std::vector<Eigen::Triplet<double>> int_triplet_vector;
|
||||
std::vector<Eigen::Triplet<double>> bnd_triplet_vector;
|
||||
|
||||
int rowindex;
|
||||
int columnindex;
|
||||
double edge_weight, edge_length;
|
||||
Eigen::RowVector2i edge;
|
||||
|
||||
int first_vertex, second_vertex;
|
||||
|
||||
for (int e_idx = 0; e_idx < edges_num; e_idx++) {
|
||||
edge = e.row(e_idx);
|
||||
edge_length = el(e_idx);
|
||||
first_vertex = edge(0);
|
||||
second_vertex = edge(1);
|
||||
|
||||
if (first_vertex >= n_knowns) {
|
||||
/* Into aint. */
|
||||
rowindex = first_vertex - n_knowns;
|
||||
|
||||
double angle1 = vertex_to_angles.coeff(first_vertex, 2 * second_vertex);
|
||||
double angle2 = vertex_to_angles.coeff(first_vertex, 2 * second_vertex + 1);
|
||||
|
||||
switch (method) {
|
||||
case HARMONIC:
|
||||
edge_weight = 1 / tan(angle1) + 1 / tan(angle2);
|
||||
break;
|
||||
case MVC:
|
||||
edge_weight = tan(angle1 / 2) + tan(angle2 / 2);
|
||||
edge_weight /= edge_length;
|
||||
break;
|
||||
case TUTTE:
|
||||
edge_weight = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
int_triplet_vector.emplace_back(rowindex, rowindex, edge_weight);
|
||||
|
||||
if (second_vertex >= n_knowns) {
|
||||
/* Also an unknown point in the interior. */
|
||||
columnindex = second_vertex - n_knowns;
|
||||
|
||||
int_triplet_vector.emplace_back(rowindex, columnindex, -edge_weight);
|
||||
}
|
||||
else {
|
||||
/* Known point on the border. */
|
||||
columnindex = second_vertex;
|
||||
bnd_triplet_vector.emplace_back(rowindex, columnindex, edge_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aint.setFromTriplets(int_triplet_vector.begin(), int_triplet_vector.end());
|
||||
aint.makeCompressed();
|
||||
|
||||
abnd.setFromTriplets(bnd_triplet_vector.begin(), bnd_triplet_vector.end());
|
||||
abnd.makeCompressed();
|
||||
|
||||
for (int i = 0; i < n_unknowns; i++) {
|
||||
double factor = aint.coeff(i, i);
|
||||
aint.row(i) /= factor;
|
||||
abnd.row(i) /= factor;
|
||||
}
|
||||
|
||||
Eigen::SparseLU<Eigen::SparseMatrix<double>> solver;
|
||||
solver.compute(aint);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
||||
for (int zindex = 0; zindex < n_knowns; zindex++) {
|
||||
z(zindex) = bnd_uv(bnd(zindex), i);
|
||||
}
|
||||
|
||||
Eigen::VectorXd b = abnd * z;
|
||||
|
||||
Eigen::VectorXd uvs;
|
||||
uvs = solver.solve(b);
|
||||
|
||||
Eigen::VectorXd boundary = bnd_uv.col(i);
|
||||
Eigen::VectorXd interior = uvs;
|
||||
|
||||
uv.col(i) << boundary, interior;
|
||||
}
|
||||
}
|
||||
|
||||
void mvc(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &uv)
|
||||
{
|
||||
|
||||
convex_border_parameterization(f, v, e, el, bnd, bnd_uv, uv, Method::MVC);
|
||||
}
|
||||
|
||||
void harmonic(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &uv)
|
||||
{
|
||||
|
||||
convex_border_parameterization(f, v, e, el, bnd, bnd_uv, uv, Method::HARMONIC);
|
||||
}
|
||||
|
||||
void tutte(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &uv)
|
||||
{
|
||||
|
||||
convex_border_parameterization(f, v, e, el, bnd, bnd_uv, uv, Method::TUTTE);
|
||||
}
|
||||
|
||||
void map_vertices_to_convex_border(Eigen::MatrixXd &vertex_positions)
|
||||
{
|
||||
double pi = atan(1) * 4;
|
||||
int boundary_vertices_num = vertex_positions.rows();
|
||||
double x, y;
|
||||
double angle = 2 * pi / boundary_vertices_num;
|
||||
|
||||
for (int i = 0; i < boundary_vertices_num; i++) {
|
||||
x = cos(angle * i);
|
||||
y = sin(angle * i);
|
||||
vertex_positions(i, 0) = (x * 0.5) + 0.5;
|
||||
vertex_positions(i, 1) = (y * 0.5) + 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
static void get_flips(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &uv,
|
||||
std::vector<int> &flip_idx)
|
||||
{
|
||||
flip_idx.resize(0);
|
||||
for (int i = 0; i < f.rows(); i++) {
|
||||
|
||||
Eigen::Vector2d v1_n = uv.row(f(i, 0));
|
||||
Eigen::Vector2d v2_n = uv.row(f(i, 1));
|
||||
Eigen::Vector2d v3_n = uv.row(f(i, 2));
|
||||
|
||||
Eigen::MatrixXd t2_homo(3, 3);
|
||||
t2_homo.col(0) << v1_n(0), v1_n(1), 1;
|
||||
t2_homo.col(1) << v2_n(0), v2_n(1), 1;
|
||||
t2_homo.col(2) << v3_n(0), v3_n(1), 1;
|
||||
double det = t2_homo.determinant();
|
||||
assert(det == det);
|
||||
if (det < 0) {
|
||||
flip_idx.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int count_flips(const Eigen::MatrixXi &f, const Eigen::MatrixXd &uv)
|
||||
{
|
||||
|
||||
std::vector<int> flip_idx;
|
||||
get_flips(f, uv, flip_idx);
|
||||
|
||||
return flip_idx.size();
|
||||
}
|
||||
|
||||
} // namespace slim
|
||||
62
blender-5.2.0/intern/slim/intern/uv_initializer.h
Normal file
62
blender-5.2.0/intern/slim/intern/uv_initializer.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <Eigen/Sparse>
|
||||
|
||||
enum Method { TUTTE, HARMONIC, MVC };
|
||||
|
||||
namespace slim {
|
||||
|
||||
void convex_border_parameterization(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &UV,
|
||||
Method method);
|
||||
|
||||
void mvc(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &UV);
|
||||
|
||||
void harmonic(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &UV);
|
||||
|
||||
void tutte(const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &e,
|
||||
const Eigen::VectorXd &el,
|
||||
const Eigen::VectorXi &bnd,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
Eigen::MatrixXd &UV);
|
||||
|
||||
void harmonic(const Eigen::MatrixXd &v,
|
||||
const Eigen::MatrixXi &f,
|
||||
const Eigen::MatrixXi &B,
|
||||
const Eigen::MatrixXd &bnd_uv,
|
||||
int power_of_harmonic_operaton,
|
||||
Eigen::MatrixXd &UV);
|
||||
|
||||
void map_vertices_to_convex_border(Eigen::MatrixXd &vertex_positions);
|
||||
|
||||
int count_flips(const Eigen::MatrixXi &f, const Eigen::MatrixXd &uv);
|
||||
|
||||
} // namespace slim
|
||||
110
blender-5.2.0/intern/slim/slim_matrix_transfer.h
Normal file
110
blender-5.2.0/intern/slim/slim_matrix_transfer.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_slim
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace slim {
|
||||
|
||||
struct SLIMData;
|
||||
|
||||
using SLIMDataPtr = std::unique_ptr<SLIMData>;
|
||||
|
||||
/**
|
||||
* MatrixTransferChart holds all information and data matrices to be
|
||||
* transferred from Blender to SLIM.
|
||||
*/
|
||||
struct MatrixTransferChart {
|
||||
int verts_num = 0;
|
||||
int faces_num = 0;
|
||||
int pinned_vertices_num = 0;
|
||||
int boundary_vertices_num = 0;
|
||||
int edges_num = 0;
|
||||
|
||||
/** Field indicating whether a given SLIM operation succeeded or not. */
|
||||
bool succeeded = false;
|
||||
|
||||
/** Vertex positions (matrix [verts_num x 3] in a linearized form). */
|
||||
std::vector<double> v_matrices;
|
||||
/** UV positions of vertices (matrix [verts_num x 2] in a linearized form). */
|
||||
std::vector<double> uv_matrices;
|
||||
/** Positions of pinned vertices (matrix [pinned_vertices_num x 2] in a linearized form). */
|
||||
std::vector<double> pp_matrices;
|
||||
/** Edge lengths. */
|
||||
std::vector<double> el_vectors;
|
||||
/** Weights per vertex. */
|
||||
std::vector<float> w_vectors;
|
||||
|
||||
/** Vertex index triplets making up faces (matrix [faces_num x 3] in a linearized form). */
|
||||
std::vector<int> f_matrices;
|
||||
/** Indices of pinned vertices. */
|
||||
std::vector<int> p_matrices;
|
||||
/** Vertex index tuples making up edges (matrix [edges_num x 2] in a linearized form). */
|
||||
std::vector<int> e_matrices;
|
||||
/** Vertex indices of boundary vertices. */
|
||||
std::vector<int> b_vectors;
|
||||
|
||||
SLIMDataPtr data;
|
||||
|
||||
MatrixTransferChart();
|
||||
MatrixTransferChart(MatrixTransferChart &&);
|
||||
|
||||
MatrixTransferChart(const MatrixTransferChart &) = delete;
|
||||
MatrixTransferChart &operator=(const MatrixTransferChart &) = delete;
|
||||
|
||||
~MatrixTransferChart();
|
||||
|
||||
void try_slim_solve(int iter_num);
|
||||
/** Executes a single iteration of SLIM, must follow a proper setup & initialization. */
|
||||
void parametrize_single_iteration();
|
||||
/**
|
||||
* Called from the native part during each iteration of interactive parametrization.
|
||||
* The blend parameter decides the linear blending between the original UV map and the one
|
||||
* obtained from the accumulated SLIM iterations so far.
|
||||
*/
|
||||
void transfer_uvs_blended(float blend);
|
||||
void free_slim_data();
|
||||
};
|
||||
|
||||
struct PinnedVertexData {
|
||||
std::vector<int> pinned_vertex_indices;
|
||||
std::vector<double> pinned_vertex_positions_2D;
|
||||
std::vector<int> selected_pins;
|
||||
};
|
||||
|
||||
struct MatrixTransfer {
|
||||
bool fixed_boundary = false;
|
||||
bool use_weights = false;
|
||||
double weight_influence = 0.0;
|
||||
int reflection_mode = 0;
|
||||
int n_iterations = 0;
|
||||
bool skip_initialization = false;
|
||||
bool is_minimize_stretch = false;
|
||||
|
||||
std::vector<MatrixTransferChart> charts;
|
||||
|
||||
/** Used for pins update in live unwrap. */
|
||||
PinnedVertexData pinned_vertex_data;
|
||||
|
||||
MatrixTransfer();
|
||||
MatrixTransfer(const MatrixTransfer &) = delete;
|
||||
MatrixTransfer &operator=(const MatrixTransfer &) = delete;
|
||||
~MatrixTransfer();
|
||||
|
||||
void parametrize();
|
||||
|
||||
/** Executes slim iterations during live unwrap. needs to provide new selected-pin positions. */
|
||||
void parametrize_live(MatrixTransferChart &chart, const PinnedVertexData &pinned_vertex_data);
|
||||
|
||||
/** Transfers all the matrices from the native part and initializes SLIM. */
|
||||
void setup_slim_data(MatrixTransferChart &chart) const;
|
||||
};
|
||||
|
||||
} // namespace slim
|
||||
Reference in New Issue
Block a user