Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Parborg <darkdefende@gmail.com>2019-08-26 19:34:11 +0300
committerSebastian Parborg <darkdefende@gmail.com>2019-09-13 11:36:05 +0300
commit57e55906f04a48a951fbbcfd7c197eef35ad4387 (patch)
treea4246ffdd501027a37d7329dca05de4d9ed19b15 /extern/quadriflow/src
parent1c44d08a69eb3e66c7f942d748f549d6b8ca138f (diff)
Add QuadriFlow remesher
Diffstat (limited to 'extern/quadriflow/src')
-rw-r--r--extern/quadriflow/src/Optimizer.cu281
-rw-r--r--extern/quadriflow/src/adjacent-matrix.cpp35
-rw-r--r--extern/quadriflow/src/adjacent-matrix.hpp37
-rw-r--r--extern/quadriflow/src/compare-key.hpp102
-rw-r--r--extern/quadriflow/src/config.hpp34
-rw-r--r--extern/quadriflow/src/dedge.cpp487
-rw-r--r--extern/quadriflow/src/dedge.hpp25
-rw-r--r--extern/quadriflow/src/disajoint-tree.hpp151
-rw-r--r--extern/quadriflow/src/dset.hpp163
-rw-r--r--extern/quadriflow/src/field-math.hpp483
-rw-r--r--extern/quadriflow/src/flow.hpp375
-rw-r--r--extern/quadriflow/src/hierarchy.cpp1343
-rw-r--r--extern/quadriflow/src/hierarchy.hpp99
-rw-r--r--extern/quadriflow/src/loader.cpp159
-rw-r--r--extern/quadriflow/src/loader.hpp15
-rw-r--r--extern/quadriflow/src/localsat.cpp295
-rw-r--r--extern/quadriflow/src/localsat.hpp31
-rw-r--r--extern/quadriflow/src/main.cpp127
-rw-r--r--extern/quadriflow/src/merge-vertex.cpp44
-rw-r--r--extern/quadriflow/src/merge-vertex.hpp14
-rw-r--r--extern/quadriflow/src/optimizer.cpp1419
-rw-r--r--extern/quadriflow/src/optimizer.hpp56
-rw-r--r--extern/quadriflow/src/parametrizer-flip.cpp583
-rw-r--r--extern/quadriflow/src/parametrizer-int.cpp425
-rw-r--r--extern/quadriflow/src/parametrizer-mesh.cpp615
-rw-r--r--extern/quadriflow/src/parametrizer-scale.cpp119
-rw-r--r--extern/quadriflow/src/parametrizer-sing.cpp142
-rw-r--r--extern/quadriflow/src/parametrizer.cpp247
-rw-r--r--extern/quadriflow/src/parametrizer.hpp177
-rw-r--r--extern/quadriflow/src/post-solver.cpp427
-rw-r--r--extern/quadriflow/src/post-solver.hpp64
-rw-r--r--extern/quadriflow/src/serialize.hpp127
-rw-r--r--extern/quadriflow/src/subdivide.cpp516
-rw-r--r--extern/quadriflow/src/subdivide.hpp17
34 files changed, 9234 insertions, 0 deletions
diff --git a/extern/quadriflow/src/Optimizer.cu b/extern/quadriflow/src/Optimizer.cu
new file mode 100644
index 00000000000..8520c3f90a8
--- /dev/null
+++ b/extern/quadriflow/src/Optimizer.cu
@@ -0,0 +1,281 @@
+#include <glm/glm.hpp>
+#include <cuda_runtime.h>
+#include "AdjacentMatrix.h"
+
+__device__ __host__ glm::dvec3
+middle_point(const glm::dvec3 &p0, const glm::dvec3 &n0, const glm::dvec3 &p1, const glm::dvec3 &n1) {
+ /* How was this derived?
+ *
+ * Minimize \|x-p0\|^2 + \|x-p1\|^2, where
+ * dot(n0, x) == dot(n0, p0)
+ * dot(n1, x) == dot(n1, p1)
+ *
+ * -> Lagrange multipliers, set derivative = 0
+ * Use first 3 equalities to write x in terms of
+ * lambda_1 and lambda_2. Substitute that into the last
+ * two equations and solve for the lambdas. Finally,
+ * add a small epsilon term to avoid issues when n1=n2.
+ */
+ double n0p0 = glm::dot(n0, p0), n0p1 = glm::dot(n0, p1),
+ n1p0 = glm::dot(n1, p0), n1p1 = glm::dot(n1, p1),
+ n0n1 = glm::dot(n0, n1),
+ denom = 1.0f / (1.0f - n0n1*n0n1 + 1e-4f),
+ lambda_0 = 2.0f*(n0p1 - n0p0 - n0n1*(n1p0 - n1p1))*denom,
+ lambda_1 = 2.0f*(n1p0 - n1p1 - n0n1*(n0p1 - n0p0))*denom;
+
+ return 0.5 * (p0 + p1) - 0.25 * (n0 * lambda_0 + n1 * lambda_1);
+}
+
+__device__ __host__ glm::dvec3
+position_round_4(const glm::dvec3 &o, const glm::dvec3 &q,
+const glm::dvec3 &n, const glm::dvec3 &p,
+double scale) {
+ double inv_scale = 1.0 / scale;
+ glm::dvec3 t = glm::cross(n, q);
+ glm::dvec3 d = p - o;
+ return o +
+ q * std::round(glm::dot(q, d) * inv_scale) * scale +
+ t * std::round(glm::dot(t, d) * inv_scale) * scale;
+}
+
+__device__ __host__ glm::dvec3
+position_floor_4(const glm::dvec3 &o, const glm::dvec3 &q,
+const glm::dvec3 &n, const glm::dvec3 &p,
+double scale) {
+ double inv_scale = 1.0 / scale;
+ glm::dvec3 t = glm::cross(n,q);
+ glm::dvec3 d = p - o;
+ return o +
+ q * std::floor(glm::dot(q, d) * inv_scale) * scale +
+ t * std::floor(glm::dot(t, d) * inv_scale) * scale;
+}
+
+
+__device__ __host__ double cudaSignum(double value) {
+ return std::copysign((double)1, value);
+}
+
+__device__ __host__ void
+compat_orientation_extrinsic_4(const glm::dvec3 &q0, const glm::dvec3 &n0,
+const glm::dvec3 &q1, const glm::dvec3 &n1, glm::dvec3& value1, glm::dvec3& value2) {
+ const glm::dvec3 A[2] = { q0, glm::cross(n0, q0) };
+ const glm::dvec3 B[2] = { q1, glm::cross(n1, q1) };
+
+ double best_score = -1e10;
+ int best_a = 0, best_b = 0;
+
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < 2; ++j) {
+ double score = std::abs(glm::dot(A[i], B[j]));
+ if (score > best_score + 1e-6) {
+ best_a = i;
+ best_b = j;
+ best_score = score;
+ }
+ }
+ }
+ const double dp = glm::dot(A[best_a], B[best_b]);
+ value1 = A[best_a];
+ value2 = B[best_b] * cudaSignum(dp);
+}
+
+__device__ __host__ void
+compat_position_extrinsic_4(
+const glm::dvec3 &p0, const glm::dvec3 &n0, const glm::dvec3 &q0, const glm::dvec3 &o0,
+const glm::dvec3 &p1, const glm::dvec3 &n1, const glm::dvec3 &q1, const glm::dvec3 &o1,
+double scale, glm::dvec3& v1, glm::dvec3& v2) {
+
+ glm::dvec3 t0 = glm::cross(n0, q0), t1 = glm::cross(n1, q1);
+ glm::dvec3 middle = middle_point(p0, n0, p1, n1);
+ glm::dvec3 o0p = position_floor_4(o0, q0, n0, middle, scale);
+ glm::dvec3 o1p = position_floor_4(o1, q1, n1, middle, scale);
+
+ double best_cost = 1e10;
+ int best_i = -1, best_j = -1;
+
+ for (int i = 0; i<4; ++i) {
+ glm::dvec3 o0t = o0p + (q0 * ((i & 1) * scale) + t0 * (((i & 2) >> 1) * scale));
+ for (int j = 0; j<4; ++j) {
+ glm::dvec3 o1t = o1p + (q1 * ((j & 1) * scale) + t1 * (((j & 2) >> 1) * scale));
+ glm::dvec3 t = o0t - o1t;
+ double cost = glm::dot(t, t);
+
+ if (cost < best_cost) {
+ best_i = i;
+ best_j = j;
+ best_cost = cost;
+ }
+ }
+ }
+
+ v1 = o0p + (q0 * ((best_i & 1) * scale) + t0 * (((best_i & 2) >> 1) * scale)),
+ v2 = o1p + (q1 * ((best_j & 1) * scale) + t1 * (((best_j & 2) >> 1) * scale));
+}
+
+__global__
+void cudaUpdateOrientation(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj) {
+ int pi = blockIdx.x * blockDim.x + threadIdx.x;
+
+// for (int pi = 0; pi < num_phases; ++pi) {
+ if (pi >= num_phases)
+ return;
+ int i = phase[pi];
+ glm::dvec3 n_i = N[i];
+ double weight_sum = 0.0f;
+ glm::dvec3 sum = Q[i];
+
+ for (int l = adjOffset[i]; l < adjOffset[i + 1]; ++l) {
+ Link link = adj[l];
+ const int j = link.id;
+ const double weight = link.weight;
+ if (weight == 0)
+ continue;
+ glm::dvec3 n_j = N[j];
+ glm::dvec3 q_j = Q[j];
+ glm::dvec3 value1, value2;
+ compat_orientation_extrinsic_4(sum, n_i, q_j, n_j, value1, value2);
+ sum = value1 * weight_sum + value2 * weight;
+ sum -= n_i*glm::dot(n_i, sum);
+ weight_sum += weight;
+
+ double norm = glm::length(sum);
+ if (norm > 2.93873587705571876e-39f)
+ sum /= norm;
+ }
+
+ if (weight_sum > 0) {
+ Q[i] = sum;
+ }
+// }
+}
+
+__global__
+void cudaPropagateOrientationUpper(glm::dvec3* srcField, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* destField, int num_orientation) {
+ int i = blockIdx.x * blockDim.x + threadIdx.x;
+// for (int i = 0; i < num_orientation; ++i) {
+ if (i >= num_orientation)
+ return;
+ for (int k = 0; k < 2; ++k) {
+ int dest = toUpper[i][k];
+ if (dest == -1)
+ continue;
+ glm::dvec3 q = srcField[i];
+ glm::dvec3 n = N[dest];
+ destField[dest] = q - n * glm::dot(n, q);
+ }
+// }
+}
+
+__global__
+void cudaPropagateOrientationLower(glm::ivec2* toUpper, glm::dvec3* Q, glm::dvec3* N, glm::dvec3* Q_next, glm::dvec3* N_next, int num_toUpper) {
+ int i = blockIdx.x * blockDim.x + threadIdx.x;
+// for (int i = 0; i < num_toUpper; ++i) {
+ if (i >= num_toUpper)
+ return;
+ glm::ivec2 upper = toUpper[i];
+ glm::dvec3 q0 = Q[upper[0]];
+ glm::dvec3 n0 = N[upper[0]];
+
+ glm::dvec3 q, q1, n1, value1, value2;
+ if (upper[1] != -1) {
+ q1 = Q[upper[1]];
+ n1 = N[upper[1]];
+ compat_orientation_extrinsic_4(q0, n0, q1, n1, value1, value2);
+ q = value1 + value2;
+ }
+ else {
+ q = q0;
+ }
+ glm::dvec3 n = N_next[i];
+ q -= glm::dot(n, q) * n;
+
+ double len = q.x * q.x + q.y * q.y + q.z * q.z;
+ if (len > 2.93873587705571876e-39f)
+ q /= sqrt(len);
+ Q_next[i] = q;
+// }
+}
+
+
+__global__
+void cudaUpdatePosition(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj, glm::dvec3* V, glm::dvec3* O, double scale) {
+ int pi = blockIdx.x * blockDim.x + threadIdx.x;
+
+// for (int pi = 0; pi < num_phases; ++pi) {
+ if (pi >= num_phases)
+ return;
+ int i = phase[pi];
+ glm::dvec3 n_i = N[i], v_i = V[i];
+ glm::dvec3 q_i = Q[i];
+ glm::dvec3 sum = O[i];
+ double weight_sum = 0.0f;
+
+ for (int l = adjOffset[i]; l < adjOffset[i + 1]; ++l) {
+ Link link = adj[l];
+ int j = link.id;
+ const double weight = link.weight;
+ if (weight == 0)
+ continue;
+
+ glm::dvec3 n_j = N[j], v_j = V[j];
+ glm::dvec3 q_j = Q[j], o_j = O[j];
+ glm::dvec3 v1, v2;
+ compat_position_extrinsic_4(
+ v_i, n_i, q_i, sum, v_j, n_j, q_j, o_j, scale, v1, v2);
+
+ sum = v1*weight_sum +v2*weight;
+ weight_sum += weight;
+ if (weight_sum > 2.93873587705571876e-39f)
+ sum /= weight_sum;
+ sum -= glm::dot(n_i, sum - v_i)*n_i;
+ }
+
+ if (weight_sum > 0) {
+ O[i] = position_round_4(sum, q_i, n_i, v_i, scale);
+ }
+// }
+}
+
+__global__
+void cudaPropagatePositionUpper(glm::dvec3* srcField, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* V, glm::dvec3* destField, int num_position) {
+ int i = blockIdx.x * blockDim.x + threadIdx.x;
+// for (int i = 0; i < num_position; ++i) {
+ if (i >= num_position)
+ return;
+ for (int k = 0; k < 2; ++k) {
+ int dest = toUpper[i][k];
+ if (dest == -1)
+ continue;
+ glm::dvec3 o = srcField[i], n = N[dest], v = V[dest];
+ o -= n * glm::dot(n, o - v);
+ destField[dest] = o;
+ }
+// }
+}
+
+
+void UpdateOrientation(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj) {
+ cudaUpdateOrientation << <(num_phases + 255) / 256, 256 >> >(phase, num_phases, N, Q, adj, adjOffset, num_adj);
+// cudaUpdateOrientation(phase, num_phases, N, Q, adj, adjOffset, num_adj);
+}
+
+void PropagateOrientationUpper(glm::dvec3* srcField, int num_orientation, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* destField) {
+ cudaPropagateOrientationUpper << <(num_orientation + 255) / 256, 256 >> >(srcField, toUpper, N, destField, num_orientation);
+// cudaPropagateOrientationUpper(srcField, toUpper, N, destField, num_orientation);
+}
+
+void PropagateOrientationLower(glm::ivec2* toUpper, glm::dvec3* Q, glm::dvec3* N, glm::dvec3* Q_next, glm::dvec3* N_next, int num_toUpper) {
+ cudaPropagateOrientationLower << <(num_toUpper + 255) / 256, 256 >> >(toUpper, Q, N, Q_next, N_next, num_toUpper);
+// cudaPropagateOrientationLower(toUpper, Q, N, Q_next, N_next, num_toUpper);
+}
+
+
+void UpdatePosition(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj, glm::dvec3* V, glm::dvec3* O, double scale) {
+ cudaUpdatePosition << <(num_phases + 255) / 256, 256 >> >(phase, num_phases, N, Q, adj, adjOffset, num_adj, V, O, scale);
+// cudaUpdatePosition(phase, num_phases, N, Q, adj, adjOffset, num_adj, V, O, scale);
+}
+
+void PropagatePositionUpper(glm::dvec3* srcField, int num_position, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* V, glm::dvec3* destField) {
+ cudaPropagatePositionUpper << <(num_position + 255) / 256, 256 >> >(srcField, toUpper, N, V, destField, num_position);
+// cudaPropagatePositionUpper(srcField, toUpper, N, V, destField, num_position);
+}
diff --git a/extern/quadriflow/src/adjacent-matrix.cpp b/extern/quadriflow/src/adjacent-matrix.cpp
new file mode 100644
index 00000000000..e20ffb05f6d
--- /dev/null
+++ b/extern/quadriflow/src/adjacent-matrix.cpp
@@ -0,0 +1,35 @@
+#include "config.hpp"
+#include "adjacent-matrix.hpp"
+#include "dedge.hpp"
+#include <fstream>
+
+namespace qflow {
+
+void generate_adjacency_matrix_uniform(
+ const MatrixXi &F, const VectorXi &V2E, const VectorXi &E2E,
+ const VectorXi &nonManifold, AdjacentMatrix& adj) {
+ adj.resize(V2E.size());
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < adj.size(); ++i) {
+ int start = V2E[i];
+ int edge = start;
+ if (start == -1)
+ continue;
+ do {
+ int base = edge % 3, f = edge / 3;
+ int opp = E2E[edge], next = dedge_next_3(opp);
+ if (adj[i].empty())
+ adj[i].push_back(Link(F((base + 2) % 3, f)));
+ if (opp == -1 || next != start) {
+ adj[i].push_back(Link(F((base + 1) % 3, f)));
+ if (opp == -1)
+ break;
+ }
+ edge = next;
+ } while (edge != start);
+ }
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/adjacent-matrix.hpp b/extern/quadriflow/src/adjacent-matrix.hpp
new file mode 100644
index 00000000000..e3eb2e12a47
--- /dev/null
+++ b/extern/quadriflow/src/adjacent-matrix.hpp
@@ -0,0 +1,37 @@
+#ifndef ADJACENT_MATRIX_H_
+#define ADJACENT_MATRIX_H_
+
+#include <vector>
+
+namespace qflow {
+
+struct Link
+{
+ Link(){}
+ Link(int _id, double _w = 1)
+ : id(_id), weight(_w)
+ {}
+ inline bool operator<(const Link &link) const { return id < link.id; }
+ int id;
+ double weight;
+};
+
+struct TaggedLink {
+ int id;
+ unsigned char flag;
+ TaggedLink(){}
+ TaggedLink(int id) : id(id), flag(0) { }
+ bool used() const { return flag & 1; }
+ void markUsed() { flag |= 1; }
+ TaggedLink& operator=(const Link& l) {
+ flag = 0;
+ id = l.id;
+ return *this;
+ }
+};
+
+typedef std::vector<std::vector<Link> > AdjacentMatrix;
+
+} // namespace qflow
+
+#endif \ No newline at end of file
diff --git a/extern/quadriflow/src/compare-key.hpp b/extern/quadriflow/src/compare-key.hpp
new file mode 100644
index 00000000000..df9109d62e0
--- /dev/null
+++ b/extern/quadriflow/src/compare-key.hpp
@@ -0,0 +1,102 @@
+#ifndef COMPARE_KEY_H_
+#define COMPARE_KEY_H_
+
+#include <iostream>
+#include <map>
+
+namespace qflow {
+
+struct Key2i
+{
+ Key2i(int x, int y)
+ : key(std::make_pair(x, y))
+ {}
+ bool operator==(const Key2i& other) const
+ {
+ return key == other.key;
+ }
+ bool operator<(const Key2i& other) const
+ {
+ return key < other.key;
+ }
+ std::pair<int, int> key;
+};
+
+struct Key3i
+{
+ Key3i(int x, int y, int z)
+ : key(std::make_pair(x, std::make_pair(y, z)))
+ {}
+ bool operator==(const Key3i& other) const
+ {
+ return key == other.key;
+ }
+ bool operator<(const Key3i& other) const
+ {
+ return key < other.key;
+ }
+ std::pair<int, std::pair<int, int> > key;
+};
+
+struct Key3f
+{
+ Key3f(double x, double y, double z, double threshold)
+ : key(std::make_pair(x / threshold, std::make_pair(y / threshold, z / threshold)))
+ {}
+ bool operator==(const Key3f& other) const
+ {
+ return key == other.key;
+ }
+ bool operator<(const Key3f& other) const
+ {
+ return key < other.key;
+ }
+ std::pair<int, std::pair<int, int> > key;
+};
+
+struct KeySorted2i
+{
+ KeySorted2i(int x, int y)
+ : key(std::make_pair(x, y))
+ {
+ if (x > y)
+ std::swap(key.first, key.second);
+ }
+ bool operator==(const KeySorted2i& other) const
+ {
+ return key == other.key;
+ }
+ bool operator<(const KeySorted2i& other) const
+ {
+ return key < other.key;
+ }
+ std::pair<int, int> key;
+};
+
+struct KeySorted3i
+{
+ KeySorted3i(int x, int y, int z)
+ : key(std::make_pair(x, std::make_pair(y, z)))
+ {
+ if (key.first > key.second.first)
+ std::swap(key.first, key.second.first);
+ if (key.first > key.second.second)
+ std::swap(key.first, key.second.second);
+ if (key.second.first > key.second.second)
+ std::swap(key.second.first, key.second.second);
+ }
+ bool operator==(const Key3i& other) const
+ {
+ return key == other.key;
+ }
+ bool operator<(const Key3i& other) const
+ {
+ return key < other.key;
+ }
+ std::pair<int, std::pair<int, int> > key;
+};
+
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/config.hpp b/extern/quadriflow/src/config.hpp
new file mode 100644
index 00000000000..842b885a209
--- /dev/null
+++ b/extern/quadriflow/src/config.hpp
@@ -0,0 +1,34 @@
+#ifndef CONFIG_H_
+#define CONFIG_H_
+
+// Move settings to cmake to make CMake happy :)
+
+// #define WITH_SCALE
+// #define WITH_CUDA
+
+const int GRAIN_SIZE = 1024;
+
+#ifdef LOG_OUTPUT
+
+#define lprintf(...) printf(__VA_ARGS__)
+#define lputs(...) puts(__VA_ARGS__)
+
+#else
+
+#define lprintf(...) void(0)
+#define lputs(...) void(0)
+
+#endif
+
+#include <chrono>
+
+namespace qflow {
+
+// simulation of Windows GetTickCount()
+unsigned long long inline GetCurrentTime64() {
+ using namespace std::chrono;
+ return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
+}
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/dedge.cpp b/extern/quadriflow/src/dedge.cpp
new file mode 100644
index 00000000000..b030e20d6a1
--- /dev/null
+++ b/extern/quadriflow/src/dedge.cpp
@@ -0,0 +1,487 @@
+#include "dedge.hpp"
+#include "config.hpp"
+
+#include <atomic>
+#include <fstream>
+#include <iostream>
+#include <set>
+#include <vector>
+#include "compare-key.hpp"
+#ifdef WITH_TBB
+#include "tbb/tbb.h"
+#endif
+namespace qflow {
+
+inline int dedge_prev(int e, int deg) { return (e % deg == 0u) ? e + (deg - 1) : e - 1; }
+
+inline bool atomicCompareAndExchange(volatile int* v, uint32_t newValue, int oldValue) {
+#if defined(_WIN32)
+ return _InterlockedCompareExchange(reinterpret_cast<volatile long*>(v), (long)newValue,
+ (long)oldValue) == (long)oldValue;
+#else
+ return __sync_bool_compare_and_swap(v, oldValue, newValue);
+#endif
+}
+
+const int INVALID = -1;
+
+#undef max
+#undef min
+bool compute_direct_graph(MatrixXd& V, MatrixXi& F, VectorXi& V2E, VectorXi& E2E,
+ VectorXi& boundary, VectorXi& nonManifold) {
+ V2E.resize(V.cols());
+ V2E.setConstant(INVALID);
+
+ uint32_t deg = F.rows();
+ std::vector<std::pair<uint32_t, uint32_t>> tmp(F.size());
+
+#ifdef WITH_TBB
+ tbb::parallel_for(
+ tbb::blocked_range<uint32_t>(0u, (uint32_t)F.cols(), GRAIN_SIZE),
+ [&](const tbb::blocked_range<uint32_t>& range) {
+ for (uint32_t f = range.begin(); f != range.end(); ++f) {
+ for (uint32_t i = 0; i < deg; ++i) {
+ uint32_t idx_cur = F(i, f), idx_next = F((i + 1) % deg, f),
+ edge_id = deg * f + i;
+ if (idx_cur >= V.cols() || idx_next >= V.cols())
+ throw std::runtime_error(
+ "Mesh data contains an out-of-bounds vertex reference!");
+ if (idx_cur == idx_next) continue;
+
+ tmp[edge_id] = std::make_pair(idx_next, INVALID);
+ if (!atomicCompareAndExchange(&V2E[idx_cur], edge_id, INVALID)) {
+ uint32_t idx = V2E[idx_cur];
+ while (!atomicCompareAndExchange((int*)&tmp[idx].second, edge_id, INVALID))
+ idx = tmp[idx].second;
+ }
+ }
+ }
+ });
+#else
+ for (int f = 0; f < F.cols(); ++f) {
+ for (unsigned int i = 0; i < deg; ++i) {
+ unsigned int idx_cur = F(i, f), idx_next = F((i + 1) % deg, f), edge_id = deg * f + i;
+ if (idx_cur >= V.cols() || idx_next >= V.cols())
+ throw std::runtime_error("Mesh data contains an out-of-bounds vertex reference!");
+ if (idx_cur == idx_next) continue;
+
+ tmp[edge_id] = std::make_pair(idx_next, -1);
+ if (V2E[idx_cur] == -1)
+ V2E[idx_cur] = edge_id;
+ else {
+ unsigned int idx = V2E[idx_cur];
+ while (tmp[idx].second != -1) {
+ idx = tmp[idx].second;
+ }
+ tmp[idx].second = edge_id;
+ }
+ }
+ }
+#endif
+
+ nonManifold.resize(V.cols());
+ nonManifold.setConstant(false);
+
+ E2E.resize(F.cols() * deg);
+ E2E.setConstant(INVALID);
+
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int f = 0; f < F.cols(); ++f) {
+ for (uint32_t i = 0; i < deg; ++i) {
+ uint32_t idx_cur = F(i, f), idx_next = F((i + 1) % deg, f), edge_id_cur = deg * f + i;
+
+ if (idx_cur == idx_next) continue;
+
+ uint32_t it = V2E[idx_next], edge_id_opp = INVALID;
+ while (it != INVALID) {
+ if (tmp[it].first == idx_cur) {
+ if (edge_id_opp == INVALID) {
+ edge_id_opp = it;
+ } else {
+ nonManifold[idx_cur] = true;
+ nonManifold[idx_next] = true;
+ edge_id_opp = INVALID;
+ break;
+ }
+ }
+ it = tmp[it].second;
+ }
+
+ if (edge_id_opp != INVALID && edge_id_cur < edge_id_opp) {
+ E2E[edge_id_cur] = edge_id_opp;
+ E2E[edge_id_opp] = edge_id_cur;
+ }
+ }
+ }
+ std::atomic<uint32_t> nonManifoldCounter(0), boundaryCounter(0), isolatedCounter(0);
+
+ boundary.resize(V.cols());
+ boundary.setConstant(false);
+
+ /* Detect boundary regions of the mesh and adjust vertex->edge pointers*/
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V.cols(); ++i) {
+ uint32_t edge = V2E[i];
+ if (edge == INVALID) {
+ isolatedCounter++;
+ continue;
+ }
+ if (nonManifold[i]) {
+ nonManifoldCounter++;
+ V2E[i] = INVALID;
+ continue;
+ }
+
+ /* Walk backwards to the first boundary edge (if any) */
+ uint32_t start = edge, v2e = INVALID;
+ do {
+ v2e = std::min(v2e, edge);
+ uint32_t prevEdge = E2E[dedge_prev(edge, deg)];
+ if (prevEdge == INVALID) {
+ /* Reached boundary -- update the vertex->edge link */
+ v2e = edge;
+ boundary[i] = true;
+ boundaryCounter++;
+ break;
+ }
+ edge = prevEdge;
+ } while (edge != start);
+ V2E[i] = v2e;
+ }
+#ifdef LOG_OUTPUT
+ printf("counter triangle %d %d\n", (int)boundaryCounter, (int)nonManifoldCounter);
+#endif
+ return true;
+ std::vector<std::vector<int>> vert_to_edges(V2E.size());
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int v = F(j, i);
+ vert_to_edges[v].push_back(i * 3 + j);
+ }
+ }
+ std::vector<int> colors(F.cols() * 3, -1);
+ bool update = false;
+ int num_v = V.cols();
+ std::map<int, int> new_vertices;
+ for (int i = 0; i < vert_to_edges.size(); ++i) {
+ int num_color = 0;
+ for (int j = 0; j < vert_to_edges[i].size(); ++j) {
+ int deid0 = vert_to_edges[i][j];
+ if (colors[deid0] == -1) {
+ int deid = deid0;
+ do {
+ colors[deid] = num_color;
+ if (num_color != 0) F(deid % 3, deid / 3) = num_v;
+ deid = deid / 3 * 3 + (deid + 2) % 3;
+ deid = E2E[deid];
+ } while (deid != deid0);
+ num_color += 1;
+ if (num_color > 1) {
+ update = true;
+ new_vertices[num_v] = i;
+ num_v += 1;
+ }
+ }
+ }
+ }
+ if (update) {
+ V.conservativeResize(3, num_v);
+ for (auto& p : new_vertices) {
+ V.col(p.first) = V.col(p.second);
+ }
+ return false;
+ }
+ return true;
+}
+
+void compute_direct_graph_quad(std::vector<Vector3d>& V, std::vector<Vector4i>& F, std::vector<int>& V2E, std::vector<int>& E2E, VectorXi& boundary, VectorXi& nonManifold) {
+ V2E.clear();
+ E2E.clear();
+ boundary = VectorXi();
+ nonManifold = VectorXi();
+ V2E.resize(V.size(), INVALID);
+
+ uint32_t deg = 4;
+ std::vector<std::pair<uint32_t, uint32_t>> tmp(F.size() * deg);
+
+#ifdef WITH_TBB
+ tbb::parallel_for(
+ tbb::blocked_range<uint32_t>(0u, (uint32_t)F.size(), GRAIN_SIZE),
+ [&](const tbb::blocked_range<uint32_t>& range) {
+ for (uint32_t f = range.begin(); f != range.end(); ++f) {
+ for (uint32_t i = 0; i < deg; ++i) {
+ uint32_t idx_cur = F[f][i], idx_next = F[f][(i + 1) % deg],
+ edge_id = deg * f + i;
+ if (idx_cur >= V.size() || idx_next >= V.size())
+ throw std::runtime_error(
+ "Mesh data contains an out-of-bounds vertex reference!");
+ if (idx_cur == idx_next) continue;
+
+ tmp[edge_id] = std::make_pair(idx_next, INVALID);
+ if (!atomicCompareAndExchange(&V2E[idx_cur], edge_id, INVALID)) {
+ uint32_t idx = V2E[idx_cur];
+ while (!atomicCompareAndExchange((int*)&tmp[idx].second, edge_id, INVALID))
+ idx = tmp[idx].second;
+ }
+ }
+ }
+ });
+#else
+ for (int f = 0; f < F.size(); ++f) {
+ for (unsigned int i = 0; i < deg; ++i) {
+ unsigned int idx_cur = F[f][i], idx_next = F[f][(i + 1) % deg], edge_id = deg * f + i;
+ if (idx_cur >= V.size() || idx_next >= V.size())
+ throw std::runtime_error("Mesh data contains an out-of-bounds vertex reference!");
+ if (idx_cur == idx_next) continue;
+ tmp[edge_id] = std::make_pair(idx_next, -1);
+ if (V2E[idx_cur] == -1) {
+ V2E[idx_cur] = edge_id;
+ }
+ else {
+ unsigned int idx = V2E[idx_cur];
+ while (tmp[idx].second != -1) {
+ idx = tmp[idx].second;
+ }
+ tmp[idx].second = edge_id;
+ }
+ }
+ }
+#endif
+ nonManifold.resize(V.size());
+ nonManifold.setConstant(false);
+
+ E2E.resize(F.size() * deg, INVALID);
+
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int f = 0; f < F.size(); ++f) {
+ for (uint32_t i = 0; i < deg; ++i) {
+ uint32_t idx_cur = F[f][i], idx_next = F[f][(i + 1) % deg], edge_id_cur = deg * f + i;
+
+ if (idx_cur == idx_next) continue;
+
+ uint32_t it = V2E[idx_next], edge_id_opp = INVALID;
+ while (it != INVALID) {
+ if (tmp[it].first == idx_cur) {
+ if (edge_id_opp == INVALID) {
+ edge_id_opp = it;
+ } else {
+ nonManifold[idx_cur] = true;
+ nonManifold[idx_next] = true;
+ edge_id_opp = INVALID;
+ break;
+ }
+ }
+ it = tmp[it].second;
+ }
+
+ if (edge_id_opp != INVALID && edge_id_cur < edge_id_opp) {
+ E2E[edge_id_cur] = edge_id_opp;
+ E2E[edge_id_opp] = edge_id_cur;
+ }
+ }
+ }
+ std::atomic<uint32_t> nonManifoldCounter(0), boundaryCounter(0), isolatedCounter(0);
+
+ boundary.resize(V.size());
+ boundary.setConstant(false);
+
+ /* Detect boundary regions of the mesh and adjust vertex->edge pointers*/
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V.size(); ++i) {
+ uint32_t edge = V2E[i];
+ if (edge == INVALID) {
+ isolatedCounter++;
+ continue;
+ }
+ if (nonManifold[i]) {
+ nonManifoldCounter++;
+ V2E[i] = INVALID;
+ continue;
+ }
+
+ /* Walk backwards to the first boundary edge (if any) */
+ uint32_t start = edge, v2e = INVALID;
+ do {
+ v2e = std::min(v2e, edge);
+ uint32_t prevEdge = E2E[dedge_prev(edge, deg)];
+ if (prevEdge == INVALID) {
+ /* Reached boundary -- update the vertex->edge link */
+ v2e = edge;
+ boundary[i] = true;
+ boundaryCounter++;
+ break;
+ }
+ edge = prevEdge;
+ } while (edge != start);
+ V2E[i] = v2e;
+ }
+#ifdef LOG_OUTPUT
+ printf("counter %d %d\n", (int)boundaryCounter, (int)nonManifoldCounter);
+#endif
+}
+
+void remove_nonmanifold(std::vector<Vector4i>& F, std::vector<Vector3d>& V) {
+ typedef std::pair<uint32_t, uint32_t> Edge;
+
+ int degree = 4;
+ std::map<uint32_t, std::map<uint32_t, std::pair<uint32_t, uint32_t>>> irregular;
+ std::vector<std::set<int>> E(V.size());
+ std::vector<std::set<int>> VF(V.size());
+
+ auto kill_face_single = [&](uint32_t f) {
+ if (F[f][0] == INVALID) return;
+ for (int i = 0; i < degree; ++i) E[F[f][i]].erase(F[f][(i + 1) % degree]);
+ F[f].setConstant(INVALID);
+ };
+
+ auto kill_face = [&](uint32_t f) {
+ if (degree == 4 && F[f][2] == F[f][3]) {
+ auto it = irregular.find(F[f][2]);
+ if (it != irregular.end()) {
+ for (auto& item : it->second) {
+ kill_face_single(item.second.second);
+ }
+ }
+ }
+ kill_face_single(f);
+ };
+
+ uint32_t nm_edge = 0, nm_vert = 0;
+
+ for (uint32_t f = 0; f < (uint32_t)F.size(); ++f) {
+ if (F[f][0] == INVALID) continue;
+ if (degree == 4 && F[f][2] == F[f][3]) {
+ /* Special handling of irregular faces */
+ irregular[F[f][2]][F[f][0]] = std::make_pair(F[f][1], f);
+ continue;
+ }
+
+ bool nonmanifold = false;
+ for (uint32_t e = 0; e < degree; ++e) {
+ uint32_t v0 = F[f][e], v1 = F[f][(e + 1) % degree], v2 = F[f][(e + 2) % degree];
+ if (E[v0].find(v1) != E[v0].end() || (degree == 4 && E[v0].find(v2) != E[v0].end()))
+ nonmanifold = true;
+ }
+
+ if (nonmanifold) {
+ nm_edge++;
+ F[f].setConstant(INVALID);
+ continue;
+ }
+
+ for (uint32_t e = 0; e < degree; ++e) {
+ uint32_t v0 = F[f][e], v1 = F[f][(e + 1) % degree], v2 = F[f][(e + 2) % degree];
+
+ E[v0].insert(v1);
+ if (degree == 4) E[v0].insert(v2);
+ VF[v0].insert(f);
+ }
+ }
+
+ std::vector<Edge> edges;
+ for (auto item : irregular) {
+ bool nonmanifold = false;
+ auto face = item.second;
+ edges.clear();
+
+ uint32_t cur = face.begin()->first, stop = cur;
+ while (true) {
+ uint32_t pred = cur;
+ cur = face[cur].first;
+ uint32_t next = face[cur].first, it = 0;
+ while (true) {
+ ++it;
+ if (next == pred) break;
+ if (E[cur].find(next) != E[cur].end() && it == 1) nonmanifold = true;
+ edges.push_back(Edge(cur, next));
+ next = face[next].first;
+ }
+ if (cur == stop) break;
+ }
+
+ if (nonmanifold) {
+ nm_edge++;
+ for (auto& i : item.second) F[i.second.second].setConstant(INVALID);
+ continue;
+ } else {
+ for (auto e : edges) {
+ E[e.first].insert(e.second);
+
+ for (auto e2 : face) VF[e.first].insert(e2.second.second);
+ }
+ }
+ }
+
+ /* Check vertices */
+ std::set<uint32_t> v_marked, v_unmarked, f_adjacent;
+
+ std::function<void(uint32_t)> dfs = [&](uint32_t i) {
+ v_marked.insert(i);
+ v_unmarked.erase(i);
+
+ for (uint32_t f : VF[i]) {
+ if (f_adjacent.find(f) == f_adjacent.end()) /* if not part of adjacent face */
+ continue;
+ for (uint32_t j = 0; j < degree; ++j) {
+ uint32_t k = F[f][j];
+ if (v_unmarked.find(k) == v_unmarked.end() || /* if not unmarked OR */
+ v_marked.find(k) != v_marked.end()) /* if already marked */
+ continue;
+ dfs(k);
+ }
+ }
+ };
+
+ for (uint32_t i = 0; i < (uint32_t)V.size(); ++i) {
+ v_marked.clear();
+ v_unmarked.clear();
+ f_adjacent.clear();
+
+ for (uint32_t f : VF[i]) {
+ if (F[f][0] == INVALID) continue;
+
+ for (uint32_t k = 0; k < degree; ++k) v_unmarked.insert(F[f][k]);
+
+ f_adjacent.insert(f);
+ }
+
+ if (v_unmarked.empty()) continue;
+ v_marked.insert(i);
+ v_unmarked.erase(i);
+
+ dfs(*v_unmarked.begin());
+
+ if (v_unmarked.size() > 0) {
+ nm_vert++;
+ for (uint32_t f : f_adjacent) kill_face(f);
+ }
+ }
+
+ if (nm_vert > 0 || nm_edge > 0) {
+ std::cout << "Non-manifold elements: vertices=" << nm_vert << ", edges=" << nm_edge
+ << std::endl;
+ }
+ uint32_t nFaces = 0, nFacesOrig = F.size();
+ for (uint32_t f = 0; f < (uint32_t)F.size(); ++f) {
+ if (F[f][0] == INVALID) continue;
+ if (nFaces != f) {
+ F[nFaces] = F[f];
+ }
+ ++nFaces;
+ }
+
+ if (nFacesOrig != nFaces) {
+ F.resize(nFaces);
+ std::cout << "Faces reduced from " << nFacesOrig << " -> " << nFaces << std::endl;
+ }
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/dedge.hpp b/extern/quadriflow/src/dedge.hpp
new file mode 100644
index 00000000000..e8ee372f012
--- /dev/null
+++ b/extern/quadriflow/src/dedge.hpp
@@ -0,0 +1,25 @@
+#ifndef DEDGE_H_
+#define DEDGE_H_
+
+#include <Eigen/Core>
+#include <Eigen/Dense>
+#include <vector>
+
+namespace qflow {
+
+using namespace Eigen;
+
+inline int dedge_prev_3(int e) { return (e % 3 == 0) ? e + 2 : e - 1; }
+inline int dedge_next_3(int e) { return (e % 3 == 2) ? e - 2 : e + 1; }
+
+bool compute_direct_graph(MatrixXd& V, MatrixXi& F, VectorXi& V2E,
+ VectorXi& E2E, VectorXi& boundary, VectorXi& nonManifold);
+
+void compute_direct_graph_quad(std::vector<Vector3d>& V, std::vector<Vector4i>& F, std::vector<int>& V2E,
+ std::vector<int>& E2E, VectorXi& boundary, VectorXi& nonManifold);
+
+void remove_nonmanifold(std::vector<Vector4i> &F, std::vector<Vector3d> &V);
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/disajoint-tree.hpp b/extern/quadriflow/src/disajoint-tree.hpp
new file mode 100644
index 00000000000..1822b83a208
--- /dev/null
+++ b/extern/quadriflow/src/disajoint-tree.hpp
@@ -0,0 +1,151 @@
+#ifndef DISAJOINT_TREE_H_
+#define DISAJOINT_TREE_H_
+
+#include <vector>
+
+namespace qflow {
+
+class DisajointTree {
+ public:
+ DisajointTree() {}
+ DisajointTree(int n) {
+ parent.resize(n);
+ rank.resize(n, 1);
+ for (int i = 0; i < n; ++i) parent[i] = i;
+ }
+ int Parent(int x) {
+ if (x == parent[x]) return x;
+ int y = Parent(parent[x]);
+ parent[x] = y;
+ return y;
+ }
+ int Index(int x) { return indices[x]; }
+ int IndexToParent(int x) {return indices_to_parent[x]; };
+ void MergeFromTo(int x, int y) {
+ int px = Parent(x);
+ int py = Parent(y);
+ if (px == py) return;
+ rank[py] += rank[px];
+ parent[px] = py;
+ }
+ void Merge(int x, int y) {
+ int px = Parent(x);
+ int py = Parent(y);
+ if (px == py) return;
+ if (rank[px] < rank[py]) {
+ rank[py] += rank[px];
+ parent[px] = py;
+ } else {
+ rank[px] += rank[py];
+ parent[py] = px;
+ }
+ }
+
+ // renumber the root so that it is consecutive.
+ void BuildCompactParent() {
+ std::vector<int> compact_parent;
+ compact_parent.resize(parent.size());
+ compact_num = 0;
+ for (int i = 0; i < parent.size(); ++i) {
+ if (parent[i] == i) {
+ compact_parent[i] = compact_num++;
+ indices_to_parent.push_back(i);
+ }
+ }
+ indices.resize(parent.size());
+ for (int i = 0; i < parent.size(); ++i) {
+ indices[i] = compact_parent[Parent(i)];
+ }
+ }
+
+ int CompactNum() { return compact_num; }
+
+ int compact_num;
+ std::vector<int> parent;
+ std::vector<int> indices, indices_to_parent;
+ std::vector<int> rank;
+};
+
+class DisajointOrientTree {
+ public:
+ DisajointOrientTree() {}
+ DisajointOrientTree(int n) {
+ parent.resize(n);
+ rank.resize(n, 1);
+ for (int i = 0; i < n; ++i) parent[i] = std::make_pair(i, 0);
+ }
+ int Parent(int j) {
+ if (j == parent[j].first) return j;
+ int k = Parent(parent[j].first);
+ parent[j].second = (parent[j].second + parent[parent[j].first].second) % 4;
+ parent[j].first = k;
+ return k;
+ }
+ int Orient(int j) {
+ if (j == parent[j].first) return parent[j].second;
+ return (parent[j].second + Orient(parent[j].first)) % 4;
+ }
+ int Index(int x) { return indices[x]; }
+ void MergeFromTo(int v0, int v1, int orient0, int orient1) {
+ int p0 = Parent(v0);
+ int p1 = Parent(v1);
+ if (p0 == p1) return;
+ int orientp0 = Orient(v0);
+ int orientp1 = Orient(v1);
+
+ if (p0 == p1) {
+ return;
+ }
+ rank[p1] += rank[p0];
+ parent[p0].first = p1;
+ parent[p0].second = (orient0 - orient1 + orientp1 - orientp0 + 8) % 4;
+ }
+
+ void Merge(int v0, int v1, int orient0, int orient1) {
+ int p0 = Parent(v0);
+ int p1 = Parent(v1);
+ if (p0 == p1) {
+ return;
+ }
+ int orientp0 = Orient(v0);
+ int orientp1 = Orient(v1);
+
+ if (p0 == p1) {
+ return;
+ }
+ if (rank[p1] < rank[p0]) {
+ rank[p0] += rank[p1];
+ parent[p1].first = p0;
+ parent[p1].second = (orient1 - orient0 + orientp0 - orientp1 + 8) % 4;
+ } else {
+ rank[p1] += rank[p0];
+ parent[p0].first = p1;
+ parent[p0].second = (orient0 - orient1 + orientp1 - orientp0 + 8) % 4;
+ }
+ }
+ void BuildCompactParent() {
+ std::vector<int> compact_parent;
+ compact_parent.resize(parent.size());
+ compact_num = 0;
+ for (int i = 0; i < parent.size(); ++i) {
+ if (parent[i].first == i) {
+ compact_parent[i] = compact_num++;
+ }
+ }
+ indices.resize(parent.size());
+ for (int i = 0; i < parent.size(); ++i) {
+ indices[i] = compact_parent[Parent(i)];
+ }
+ }
+
+ int CompactNum() { return compact_num; }
+
+ int compact_num;
+ std::vector<std::pair<int, int>> parent;
+ std::vector<int> indices;
+ std::vector<int> rank;
+};
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/dset.hpp b/extern/quadriflow/src/dset.hpp
new file mode 100644
index 00000000000..f8e6f6da6b6
--- /dev/null
+++ b/extern/quadriflow/src/dset.hpp
@@ -0,0 +1,163 @@
+#if !defined(__UNIONFIND_H)
+#define __UNIONFIND_H
+
+#include <vector>
+#include <atomic>
+#include <iostream>
+
+namespace qflow {
+
+/**
+* Lock-free parallel disjoint set data structure (aka UNION-FIND)
+* with path compression and union by rank
+*
+* Supports concurrent find(), same() and unite() calls as described
+* in the paper
+*
+* "Wait-free Parallel Algorithms for the Union-Find Problem"
+* by Richard J. Anderson and Heather Woll
+*
+* In addition, this class supports optimistic locking (try_lock/unlock)
+* of disjoint sets and a combined unite+unlock operation.
+*
+* \author Wenzel Jakob
+*/
+class DisjointSets {
+public:
+ DisjointSets(uint32_t size) : mData(size) {
+ for (uint32_t i = 0; i<size; ++i)
+ mData[i] = (uint32_t)i;
+ }
+
+ uint32_t find(uint32_t id) const {
+ while (id != parent(id)) {
+ uint64_t value = mData[id];
+ uint32_t new_parent = parent((uint32_t)value);
+ uint64_t new_value =
+ (value & 0xFFFFFFFF00000000ULL) | new_parent;
+ /* Try to update parent (may fail, that's ok) */
+ if (value != new_value)
+ mData[id].compare_exchange_weak(value, new_value);
+ id = new_parent;
+ }
+ return id;
+ }
+
+ bool same(uint32_t id1, uint32_t id2) const {
+ for (;;) {
+ id1 = find(id1);
+ id2 = find(id2);
+ if (id1 == id2)
+ return true;
+ if (parent(id1) == id1)
+ return false;
+ }
+ }
+
+ uint32_t unite(uint32_t id1, uint32_t id2) {
+ for (;;) {
+ id1 = find(id1);
+ id2 = find(id2);
+
+ if (id1 == id2)
+ return id1;
+
+ uint32_t r1 = rank(id1), r2 = rank(id2);
+
+ if (r1 > r2 || (r1 == r2 && id1 < id2)) {
+ std::swap(r1, r2);
+ std::swap(id1, id2);
+ }
+
+ uint64_t oldEntry = ((uint64_t)r1 << 32) | id1;
+ uint64_t newEntry = ((uint64_t)r1 << 32) | id2;
+
+ if (!mData[id1].compare_exchange_strong(oldEntry, newEntry))
+ continue;
+
+ if (r1 == r2) {
+ oldEntry = ((uint64_t)r2 << 32) | id2;
+ newEntry = ((uint64_t)(r2 + 1) << 32) | id2;
+ /* Try to update the rank (may fail, that's ok) */
+ mData[id2].compare_exchange_weak(oldEntry, newEntry);
+ }
+
+ break;
+ }
+ return id2;
+ }
+
+ /**
+ * Try to lock the a disjoint union identified by one
+ * of its elements (this can occasionally fail when there
+ * are concurrent operations). The parameter 'id' will be
+ * updated to store the current representative ID of the
+ * union
+ */
+ bool try_lock(uint32_t &id) {
+ const uint64_t lock_flag = 1ULL << 63;
+ id = find(id);
+ uint64_t value = mData[id];
+ if ((value & lock_flag) || (uint32_t)value != id)
+ return false;
+ // On IA32/x64, a PAUSE instruction is recommended for CAS busy loops
+#if defined(__i386__) || defined(__amd64__)
+ __asm__ __volatile__("pause\n");
+#endif
+ return mData[id].compare_exchange_strong(value, value | lock_flag);
+ }
+
+ void unlock(uint32_t id) {
+ const uint64_t lock_flag = 1ULL << 63;
+ mData[id] &= ~lock_flag;
+ }
+
+ /**
+ * Return the representative index of the set that results from merging
+ * locked disjoint sets 'id1' and 'id2'
+ */
+ uint32_t unite_index_locked(uint32_t id1, uint32_t id2) const {
+ uint32_t r1 = rank(id1), r2 = rank(id2);
+ return (r1 > r2 || (r1 == r2 && id1 < id2)) ? id1 : id2;
+ }
+
+ /**
+ * Atomically unite two locked disjoint sets and unlock them. Assumes
+ * that here are no other concurrent unite() involving the same sets
+ */
+ uint32_t unite_unlock(uint32_t id1, uint32_t id2) {
+ uint32_t r1 = rank(id1), r2 = rank(id2);
+
+ if (r1 > r2 || (r1 == r2 && id1 < id2)) {
+ std::swap(r1, r2);
+ std::swap(id1, id2);
+ }
+
+ mData[id1] = ((uint64_t)r1 << 32) | id2;
+ mData[id2] = ((uint64_t)(r2 + ((r1 == r2) ? 1 : 0)) << 32) | id2;
+
+ return id2;
+ }
+
+ uint32_t size() const { return (uint32_t)mData.size(); }
+
+ uint32_t rank(uint32_t id) const {
+ return ((uint32_t)(mData[id] >> 32)) & 0x7FFFFFFFu;
+ }
+
+ uint32_t parent(uint32_t id) const {
+ return (uint32_t)mData[id];
+ }
+
+ friend std::ostream &operator<<(std::ostream &os, const DisjointSets &f) {
+ for (size_t i = 0; i<f.mData.size(); ++i)
+ os << i << ": parent=" << f.parent(i) << ", rank=" << f.rank(i) << std::endl;
+ return os;
+ }
+
+ mutable std::vector<std::atomic<uint64_t>> mData;
+};
+
+} // namespace qflow
+
+#endif /* __UNIONFIND_H */
diff --git a/extern/quadriflow/src/field-math.hpp b/extern/quadriflow/src/field-math.hpp
new file mode 100644
index 00000000000..86ed4b2b1f4
--- /dev/null
+++ b/extern/quadriflow/src/field-math.hpp
@@ -0,0 +1,483 @@
+#ifndef FIELD_MATH_H_
+#define FIELD_MATH_H_
+
+#ifdef WITH_CUDA
+# include <glm/glm.hpp>
+#endif
+#include <Eigen/Core>
+#include <Eigen/Dense>
+#include <algorithm>
+#include <vector>
+
+namespace qflow {
+
+using namespace Eigen;
+
+struct DEdge
+{
+ DEdge()
+ : x(0), y(0)
+ {}
+ DEdge(int _x, int _y) {
+ if (_x > _y)
+ x = _y, y = _x;
+ else
+ x = _x, y = _y;
+ }
+ bool operator<(const DEdge& e) const {
+ return (x < e.x) || (x == e.x && y < e.y);
+ }
+ bool operator==(const DEdge& e) const {
+ return x == e.x && y == e.y;
+ }
+ bool operator!=(const DEdge& e) const {
+ return x != e.x || y != e.y;
+ }
+ int x, y;
+};
+
+inline int get_parents(std::vector<std::pair<int, int>>& parents, int j) {
+ if (j == parents[j].first) return j;
+ int k = get_parents(parents, parents[j].first);
+ parents[j].second = (parents[j].second + parents[parents[j].first].second) % 4;
+ parents[j].first = k;
+ return k;
+}
+
+inline int get_parents_orient(std::vector<std::pair<int, int>>& parents, int j) {
+ if (j == parents[j].first) return parents[j].second;
+ return (parents[j].second + get_parents_orient(parents, parents[j].first)) % 4;
+}
+
+inline double fast_acos(double x) {
+ double negate = double(x < 0.0f);
+ x = std::abs(x);
+ double ret = -0.0187293f;
+ ret *= x;
+ ret = ret + 0.0742610f;
+ ret *= x;
+ ret = ret - 0.2121144f;
+ ret *= x;
+ ret = ret + 1.5707288f;
+ ret = ret * std::sqrt(1.0f - x);
+ ret = ret - 2.0f * negate * ret;
+ return negate * (double)M_PI + ret;
+}
+
+inline double signum(double value) { return std::copysign((double)1, value); }
+
+/// Always-positive modulo function (assumes b > 0)
+inline int modulo(int a, int b) {
+ int r = a % b;
+ return (r < 0) ? r + b : r;
+}
+
+inline Vector3d rotate90_by(const Vector3d &q, const Vector3d &n, int amount) {
+ return ((amount & 1) ? (n.cross(q)) : q) * (amount < 2 ? 1.0f : -1.0f);
+}
+
+inline Vector2i rshift90(Vector2i shift, int amount) {
+ if (amount & 1) shift = Vector2i(-shift.y(), shift.x());
+ if (amount >= 2) shift = -shift;
+ return shift;
+}
+
+inline std::pair<int, int> compat_orientation_extrinsic_index_4(const Vector3d &q0,
+ const Vector3d &n0,
+ const Vector3d &q1,
+ const Vector3d &n1) {
+ const Vector3d A[2] = {q0, n0.cross(q0)};
+ const Vector3d B[2] = {q1, n1.cross(q1)};
+
+ double best_score = -std::numeric_limits<double>::infinity();
+ int best_a = 0, best_b = 0;
+
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < 2; ++j) {
+ double score = std::abs(A[i].dot(B[j]));
+ if (score > best_score) {
+ best_a = i;
+ best_b = j;
+ best_score = score;
+ }
+ }
+ }
+
+ if (A[best_a].dot(B[best_b]) < 0) best_b += 2;
+
+ return std::make_pair(best_a, best_b);
+}
+
+inline std::pair<Vector3d, Vector3d> compat_orientation_extrinsic_4(const Vector3d &q0,
+ const Vector3d &n0,
+ const Vector3d &q1,
+ const Vector3d &n1) {
+ const Vector3d A[2] = {q0, n0.cross(q0)};
+ const Vector3d B[2] = {q1, n1.cross(q1)};
+
+ double best_score = -std::numeric_limits<double>::infinity();
+ int best_a = 0, best_b = 0;
+
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < 2; ++j) {
+ double score = std::abs(A[i].dot(B[j]));
+ if (score > best_score + 1e-6) {
+ best_a = i;
+ best_b = j;
+ best_score = score;
+ }
+ }
+ }
+
+ const double dp = A[best_a].dot(B[best_b]);
+ return std::make_pair(A[best_a], B[best_b] * signum(dp));
+}
+
+inline Vector3d middle_point(const Vector3d &p0, const Vector3d &n0, const Vector3d &p1,
+ const Vector3d &n1) {
+ /* How was this derived?
+ *
+ * Minimize \|x-p0\|^2 + \|x-p1\|^2, where
+ * dot(n0, x) == dot(n0, p0)
+ * dot(n1, x) == dot(n1, p1)
+ *
+ * -> Lagrange multipliers, set derivative = 0
+ * Use first 3 equalities to write x in terms of
+ * lambda_1 and lambda_2. Substitute that into the last
+ * two equations and solve for the lambdas. Finally,
+ * add a small epsilon term to avoid issues when n1=n2.
+ */
+ double n0p0 = n0.dot(p0), n0p1 = n0.dot(p1), n1p0 = n1.dot(p0), n1p1 = n1.dot(p1),
+ n0n1 = n0.dot(n1), denom = 1.0f / (1.0f - n0n1 * n0n1 + 1e-4f),
+ lambda_0 = 2.0f * (n0p1 - n0p0 - n0n1 * (n1p0 - n1p1)) * denom,
+ lambda_1 = 2.0f * (n1p0 - n1p1 - n0n1 * (n0p1 - n0p0)) * denom;
+
+ return 0.5f * (p0 + p1) - 0.25f * (n0 * lambda_0 + n1 * lambda_1);
+}
+
+inline Vector3d position_floor_4(const Vector3d &o, const Vector3d &q, const Vector3d &n,
+ const Vector3d &p, double scale_x, double scale_y,
+ double inv_scale_x, double inv_scale_y) {
+ Vector3d t = n.cross(q);
+ Vector3d d = p - o;
+ return o + q * std::floor(q.dot(d) * inv_scale_x) * scale_x +
+ t * std::floor(t.dot(d) * inv_scale_y) * scale_y;
+}
+
+inline std::pair<Vector3d, Vector3d> compat_position_extrinsic_4(
+ const Vector3d &p0, const Vector3d &n0, const Vector3d &q0, const Vector3d &o0,
+ const Vector3d &p1, const Vector3d &n1, const Vector3d &q1, const Vector3d &o1, double scale_x,
+ double scale_y, double inv_scale_x, double inv_scale_y, double scale_x_1, double scale_y_1,
+ double inv_scale_x_1, double inv_scale_y_1) {
+ Vector3d t0 = n0.cross(q0), t1 = n1.cross(q1);
+ Vector3d middle = middle_point(p0, n0, p1, n1);
+ Vector3d o0p =
+ position_floor_4(o0, q0, n0, middle, scale_x, scale_y, inv_scale_x, inv_scale_y);
+ Vector3d o1p =
+ position_floor_4(o1, q1, n1, middle, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1);
+
+ double best_cost = std::numeric_limits<double>::infinity();
+ int best_i = -1, best_j = -1;
+
+ for (int i = 0; i < 4; ++i) {
+ Vector3d o0t = o0p + (q0 * (i & 1) * scale_x + t0 * ((i & 2) >> 1) * scale_y);
+ for (int j = 0; j < 4; ++j) {
+ Vector3d o1t = o1p + (q1 * (j & 1) * scale_x_1 + t1 * ((j & 2) >> 1) * scale_y_1);
+ double cost = (o0t - o1t).squaredNorm();
+
+ if (cost < best_cost) {
+ best_i = i;
+ best_j = j;
+ best_cost = cost;
+ }
+ }
+ }
+
+ return std::make_pair(
+ o0p + (q0 * (best_i & 1) * scale_x + t0 * ((best_i & 2) >> 1) * scale_y),
+ o1p + (q1 * (best_j & 1) * scale_x_1 + t1 * ((best_j & 2) >> 1) * scale_y_1));
+}
+
+inline Vector3d position_round_4(const Vector3d &o, const Vector3d &q, const Vector3d &n,
+ const Vector3d &p, double scale_x, double scale_y,
+ double inv_scale_x, double inv_scale_y) {
+ Vector3d t = n.cross(q);
+ Vector3d d = p - o;
+ return o + q * std::round(q.dot(d) * inv_scale_x) * scale_x +
+ t * std::round(t.dot(d) * inv_scale_y) * scale_y;
+}
+
+inline Vector2i position_floor_index_4(const Vector3d &o, const Vector3d &q, const Vector3d &n,
+ const Vector3d &p, double /* unused */, double /* unused */,
+ double inv_scale_x, double inv_scale_y) {
+ Vector3d t = n.cross(q);
+ Vector3d d = p - o;
+ return Vector2i((int)std::floor(q.dot(d) * inv_scale_x),
+ (int)std::floor(t.dot(d) * inv_scale_y));
+}
+
+inline std::pair<Vector2i, Vector2i> compat_position_extrinsic_index_4(
+ const Vector3d &p0, const Vector3d &n0, const Vector3d &q0, const Vector3d &o0,
+ const Vector3d &p1, const Vector3d &n1, const Vector3d &q1, const Vector3d &o1, double scale_x,
+ double scale_y, double inv_scale_x, double inv_scale_y, double scale_x_1, double scale_y_1,
+ double inv_scale_x_1, double inv_scale_y_1, double *error) {
+ Vector3d t0 = n0.cross(q0), t1 = n1.cross(q1);
+ Vector3d middle = middle_point(p0, n0, p1, n1);
+ Vector2i o0p =
+ position_floor_index_4(o0, q0, n0, middle, scale_x, scale_y, inv_scale_x, inv_scale_y);
+ Vector2i o1p = position_floor_index_4(o1, q1, n1, middle, scale_x_1, scale_y_1, inv_scale_x_1,
+ inv_scale_y_1);
+
+ double best_cost = std::numeric_limits<double>::infinity();
+ int best_i = -1, best_j = -1;
+
+ for (int i = 0; i < 4; ++i) {
+ Vector3d o0t =
+ o0 + (q0 * ((i & 1) + o0p[0]) * scale_x + t0 * (((i & 2) >> 1) + o0p[1]) * scale_y);
+ for (int j = 0; j < 4; ++j) {
+ Vector3d o1t = o1 + (q1 * ((j & 1) + o1p[0]) * scale_x_1 +
+ t1 * (((j & 2) >> 1) + o1p[1]) * scale_y_1);
+ double cost = (o0t - o1t).squaredNorm();
+
+ if (cost < best_cost) {
+ best_i = i;
+ best_j = j;
+ best_cost = cost;
+ }
+ }
+ }
+ if (error) *error = best_cost;
+
+ return std::make_pair(Vector2i((best_i & 1) + o0p[0], ((best_i & 2) >> 1) + o0p[1]),
+ Vector2i((best_j & 1) + o1p[0], ((best_j & 2) >> 1) + o1p[1]));
+}
+
+inline void coordinate_system(const Vector3d &a, Vector3d &b, Vector3d &c) {
+ if (std::abs(a.x()) > std::abs(a.y())) {
+ double invLen = 1.0f / std::sqrt(a.x() * a.x() + a.z() * a.z());
+ c = Vector3d(a.z() * invLen, 0.0f, -a.x() * invLen);
+ } else {
+ double invLen = 1.0f / std::sqrt(a.y() * a.y() + a.z() * a.z());
+ c = Vector3d(0.0f, a.z() * invLen, -a.y() * invLen);
+ }
+ b = c.cross(a);
+}
+
+inline Vector3d rotate_vector_into_plane(Vector3d q, const Vector3d &source_normal,
+ const Vector3d &target_normal) {
+ const double cosTheta = source_normal.dot(target_normal);
+ if (cosTheta < 0.9999f) {
+ if (cosTheta < -0.9999f) return -q;
+ Vector3d axis = source_normal.cross(target_normal);
+ q = q * cosTheta + axis.cross(q) +
+ axis * (axis.dot(q) * (1.0 - cosTheta) / axis.dot(axis));
+ }
+ return q;
+}
+
+inline Vector3d Travel(Vector3d p, const Vector3d &dir, double &len, int &f, VectorXi &E2E,
+ MatrixXd &V, MatrixXi &F, MatrixXd &NF,
+ std::vector<MatrixXd> &triangle_space, double *tx = 0, double *ty = 0) {
+ Vector3d N = NF.col(f);
+ Vector3d pt = (dir - dir.dot(N) * N).normalized();
+ int prev_id = -1;
+ int count = 0;
+ while (len > 0) {
+ count += 1;
+ Vector3d t1 = V.col(F(1, f)) - V.col(F(0, f));
+ Vector3d t2 = V.col(F(2, f)) - V.col(F(0, f));
+ Vector3d N = NF.col(f);
+ // printf("point dis: %f\n", (p - V.col(F(1, f))).dot(N));
+ int edge_id = f * 3;
+ double max_len = 1e30;
+ bool found = false;
+ int next_id, next_f;
+ Vector3d next_q;
+ Matrix3d m, n;
+ m.col(0) = t1;
+ m.col(1) = t2;
+ m.col(2) = N;
+ n = m.inverse();
+ MatrixXd &T = triangle_space[f];
+ VectorXd coord = T * Vector3d(p - V.col(F(0, f)));
+ VectorXd dirs = (T * pt);
+
+ double lens[3];
+ lens[0] = -coord.y() / dirs.y();
+ lens[1] = (1 - coord.x() - coord.y()) / (dirs.x() + dirs.y());
+ lens[2] = -coord.x() / dirs.x();
+ for (int fid = 0; fid < 3; ++fid) {
+ if (fid + edge_id == prev_id) continue;
+
+ if (lens[fid] >= 0 && lens[fid] < max_len) {
+ max_len = lens[fid];
+ next_id = E2E[edge_id + fid];
+ next_f = next_id;
+ if (next_f != -1) next_f /= 3;
+ found = true;
+ }
+ }
+ if (!found) {
+ printf("error...\n");
+ exit(0);
+ }
+ // printf("status: %f %f %d\n", len, max_len, f);
+ if (max_len >= len) {
+ if (tx && ty) {
+ *tx = coord.x() + dirs.x() * len;
+ *ty = coord.y() + dirs.y() * len;
+ }
+ p = p + len * pt;
+ len = 0;
+ return p;
+ }
+ p = V.col(F(0, f)) + t1 * (coord.x() + dirs.x() * max_len) +
+ t2 * (coord.y() + dirs.y() * max_len);
+ len -= max_len;
+ if (next_f == -1) {
+ if (tx && ty) {
+ *tx = coord.x() + dirs.x() * max_len;
+ *ty = coord.y() + dirs.y() * max_len;
+ }
+ return p;
+ }
+ pt = rotate_vector_into_plane(pt, NF.col(f), NF.col(next_f));
+ f = next_f;
+ prev_id = next_id;
+ }
+ return p;
+}
+inline Vector3d TravelField(Vector3d p, Vector3d &pt, double &len, int &f, VectorXi &E2E,
+ MatrixXd &V, MatrixXi &F, MatrixXd &NF, MatrixXd &QF, MatrixXd &QV,
+ MatrixXd &NV, std::vector<MatrixXd> &triangle_space, double *tx = 0,
+ double *ty = 0, Vector3d *dir_unfold = 0) {
+ Vector3d N = NF.col(f);
+ pt = (pt - pt.dot(N) * N).normalized();
+ int prev_id = -1;
+ int count = 0;
+ std::vector<Vector3d> Ns;
+
+ auto FaceQFromVertices = [&](int f, double tx, double ty) {
+ const Vector3d &n = NF.col(f);
+ const Vector3d &q_1 = QV.col(F(0, f)), &q_2 = QV.col(F(1, f)), &q_3 = QV.col(F(2, f));
+ const Vector3d &n_1 = NV.col(F(0, f)), &n_2 = NV.col(F(1, f)), &n_3 = NV.col(F(2, f));
+ Vector3d q_1n = rotate_vector_into_plane(q_1, n_1, n);
+ Vector3d q_2n = rotate_vector_into_plane(q_2, n_2, n);
+ Vector3d q_3n = rotate_vector_into_plane(q_3, n_3, n);
+ auto orient = compat_orientation_extrinsic_4(q_1n, n, q_2n, n);
+ Vector3d q = (orient.first * tx + orient.second * ty).normalized();
+ orient = compat_orientation_extrinsic_4(q, n, q_3n, n);
+ q = (orient.first * (tx + ty) + orient.second * (1 - tx - ty)).normalized();
+ return q;
+ };
+
+ auto BestQFromGivenQ = [&](const Vector3d &n, const Vector3d &q, const Vector3d &given_q) {
+ Vector3d q_1 = n.cross(q);
+ double t1 = q.dot(given_q);
+ double t2 = q_1.dot(given_q);
+ if (fabs(t1) > fabs(t2)) {
+ if (t1 > 0.0)
+ return Vector3d(q);
+ else
+ return Vector3d(-q);
+ } else {
+ if (t2 > 0.0)
+ return Vector3d(q_1);
+ else
+ return Vector3d(-q_1);
+ }
+ };
+
+ while (len > 0) {
+ count += 1;
+ Vector3d t1 = V.col(F(1, f)) - V.col(F(0, f));
+ Vector3d t2 = V.col(F(2, f)) - V.col(F(0, f));
+ Vector3d N = NF.col(f);
+ Ns.push_back(N);
+ // printf("point dis: %f\n", (p - V.col(F(1, f))).dot(N));
+ int edge_id = f * 3;
+ double max_len = 1e30;
+ bool found = false;
+ int next_id = -1, next_f = -1;
+ Vector3d next_q;
+ Matrix3d m, n;
+ m.col(0) = t1;
+ m.col(1) = t2;
+ m.col(2) = N;
+ n = m.inverse();
+ MatrixXd &T = triangle_space[f];
+ VectorXd coord = T * Vector3d(p - V.col(F(0, f)));
+ VectorXd dirs = (T * pt);
+ double lens[3];
+ lens[0] = -coord.y() / dirs.y();
+ lens[1] = (1 - coord.x() - coord.y()) / (dirs.x() + dirs.y());
+ lens[2] = -coord.x() / dirs.x();
+ for (int fid = 0; fid < 3; ++fid) {
+ if (fid + edge_id == prev_id) continue;
+
+ if (lens[fid] >= 0 && lens[fid] < max_len) {
+ max_len = lens[fid];
+ next_id = E2E[edge_id + fid];
+ next_f = next_id;
+ if (next_f != -1) next_f /= 3;
+ found = true;
+ }
+ }
+ double w1 = (coord.x() + dirs.x() * max_len);
+ double w2 = (coord.y() + dirs.y() * max_len);
+ if (w1 < 0) w1 = 0.0f;
+ if (w2 < 0) w2 = 0.0f;
+ if (w1 + w2 > 1) {
+ double w = w1 + w2;
+ w1 /= w;
+ w2 /= w;
+ }
+
+ if (!found) {
+ printf("error...\n");
+ exit(0);
+ }
+ // printf("status: %f %f %d\n", len, max_len, f);
+ if (max_len >= len) {
+ if (tx && ty) {
+ *tx = w1;
+ *ty = w2;
+ }
+ Vector3d ideal_q = FaceQFromVertices(f, *tx, *ty);
+ *dir_unfold = BestQFromGivenQ(NF.col(f), ideal_q, *dir_unfold);
+ for (int i = Ns.size() - 1; i > 0; --i) {
+ *dir_unfold = rotate_vector_into_plane(*dir_unfold, Ns[i], Ns[i - 1]);
+ }
+ p = p + len * pt;
+ len = 0;
+ return p;
+ }
+ p = V.col(F(0, f)) + t1 * w1 + t2 * w2;
+ len -= max_len;
+ if (next_f == -1) {
+ if (tx && ty) {
+ *tx = w1;
+ *ty = w2;
+ }
+ Vector3d ideal_q = FaceQFromVertices(f, *tx, *ty);
+ *dir_unfold = BestQFromGivenQ(NF.col(f), ideal_q, *dir_unfold);
+ for (int i = Ns.size() - 1; i > 0; --i) {
+ *dir_unfold = rotate_vector_into_plane(*dir_unfold, Ns[i], Ns[i - 1]);
+ }
+ return p;
+ }
+ pt = rotate_vector_into_plane(pt, NF.col(f), NF.col(next_f));
+ // pt = BestQFromGivenQ(NF.col(next_f), QF.col(next_f), pt);
+ if (dir_unfold) {
+ *dir_unfold = BestQFromGivenQ(NF.col(next_f), QF.col(next_f), *dir_unfold);
+ }
+ f = next_f;
+ prev_id = next_id;
+ }
+
+ return p;
+}
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/flow.hpp b/extern/quadriflow/src/flow.hpp
new file mode 100644
index 00000000000..ab4a01cb4ed
--- /dev/null
+++ b/extern/quadriflow/src/flow.hpp
@@ -0,0 +1,375 @@
+#ifndef FLOW_H_
+#define FLOW_H_
+
+#include <Eigen/Core>
+#include <list>
+#include <map>
+#include <vector>
+
+#include "config.hpp"
+
+#include <boost/graph/adjacency_list.hpp>
+#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
+#include <boost/graph/edmonds_karp_max_flow.hpp>
+#include <boost/graph/push_relabel_max_flow.hpp>
+
+#include <lemon/network_simplex.h>
+#include <lemon/preflow.h>
+#include <lemon/smart_graph.h>
+
+using namespace boost;
+using namespace Eigen;
+
+namespace qflow {
+
+class MaxFlowHelper {
+ public:
+ MaxFlowHelper() {}
+ virtual ~MaxFlowHelper(){};
+ virtual void resize(int n, int m) = 0;
+ virtual void addEdge(int x, int y, int c, int rc, int v, int cost = 1) = 0;
+ virtual int compute() = 0;
+ virtual void applyTo(std::vector<Vector2i>& edge_diff) = 0;
+};
+
+class BoykovMaxFlowHelper : public MaxFlowHelper {
+ public:
+ typedef int EdgeWeightType;
+ typedef adjacency_list_traits<vecS, vecS, directedS> Traits;
+ // clang-format off
+ typedef adjacency_list < vecS, vecS, directedS,
+ property < vertex_name_t, std::string,
+ property < vertex_index_t, long,
+ property < vertex_color_t, boost::default_color_type,
+ property < vertex_distance_t, long,
+ property < vertex_predecessor_t, Traits::edge_descriptor > > > > >,
+
+ property < edge_capacity_t, EdgeWeightType,
+ property < edge_residual_capacity_t, EdgeWeightType,
+ property < edge_reverse_t, Traits::edge_descriptor > > > > Graph;
+ // clang-format on
+
+ public:
+ BoykovMaxFlowHelper() { rev = get(edge_reverse, g); }
+ void resize(int n, int m) {
+ vertex_descriptors.resize(n);
+ for (int i = 0; i < n; ++i) vertex_descriptors[i] = add_vertex(g);
+ }
+ int compute() {
+ EdgeWeightType flow =
+ boykov_kolmogorov_max_flow(g, vertex_descriptors.front(), vertex_descriptors.back());
+ return flow;
+ }
+ void addDirectEdge(Traits::vertex_descriptor& v1, Traits::vertex_descriptor& v2,
+ property_map<Graph, edge_reverse_t>::type& rev, const int capacity,
+ const int inv_capacity, Graph& g, Traits::edge_descriptor& e1,
+ Traits::edge_descriptor& e2) {
+ e1 = add_edge(v1, v2, g).first;
+ e2 = add_edge(v2, v1, g).first;
+ put(edge_capacity, g, e1, capacity);
+ put(edge_capacity, g, e2, inv_capacity);
+
+ rev[e1] = e2;
+ rev[e2] = e1;
+ }
+ void addEdge(int x, int y, int c, int rc, int v, int cost = 1) {
+ Traits::edge_descriptor e1, e2;
+ addDirectEdge(vertex_descriptors[x], vertex_descriptors[y], rev, c, rc, g, e1, e2);
+ if (v != -1) {
+ edge_to_variables[e1] = std::make_pair(v, -1);
+ edge_to_variables[e2] = std::make_pair(v, 1);
+ }
+ }
+ void applyTo(std::vector<Vector2i>& edge_diff) {
+ property_map<Graph, edge_capacity_t>::type capacity = get(edge_capacity, g);
+ property_map<Graph, edge_residual_capacity_t>::type residual_capacity =
+ get(edge_residual_capacity, g);
+
+ graph_traits<Graph>::vertex_iterator u_iter, u_end;
+ graph_traits<Graph>::out_edge_iterator ei, e_end;
+ for (tie(u_iter, u_end) = vertices(g); u_iter != u_end; ++u_iter)
+ for (tie(ei, e_end) = out_edges(*u_iter, g); ei != e_end; ++ei)
+ if (capacity[*ei] > 0) {
+ int flow = (capacity[*ei] - residual_capacity[*ei]);
+ if (flow > 0) {
+ auto it = edge_to_variables.find(*ei);
+ if (it != edge_to_variables.end()) {
+ edge_diff[it->second.first / 2][it->second.first % 2] +=
+ it->second.second * flow;
+ }
+ }
+ }
+ }
+
+ private:
+ Graph g;
+ property_map<Graph, edge_reverse_t>::type rev;
+ std::vector<Traits::vertex_descriptor> vertex_descriptors;
+ std::map<Traits::edge_descriptor, std::pair<int, int>> edge_to_variables;
+};
+
+class NetworkSimplexFlowHelper : public MaxFlowHelper {
+ public:
+ using Weight = int;
+ using Capacity = int;
+ using Graph = lemon::SmartDigraph;
+ using Node = Graph::Node;
+ using Arc = Graph::Arc;
+ template <typename ValueType>
+ using ArcMap = lemon::SmartDigraph::ArcMap<ValueType>;
+ using Preflow = lemon::Preflow<lemon::SmartDigraph, ArcMap<Capacity>>;
+ using NetworkSimplex = lemon::NetworkSimplex<lemon::SmartDigraph, Capacity, Weight>;
+
+ public:
+ NetworkSimplexFlowHelper() : cost(graph), capacity(graph), flow(graph), variable(graph) {}
+ ~NetworkSimplexFlowHelper(){};
+ void resize(int n, int m) {
+ nodes.reserve(n);
+ for (int i = 0; i < n; ++i) nodes.push_back(graph.addNode());
+ }
+ void addEdge(int x, int y, int c, int rc, int v, int cst = 1) {
+ assert(x >= 0);
+ assert(v >= -1);
+ if (c) {
+ auto e1 = graph.addArc(nodes[x], nodes[y]);
+ cost[e1] = cst;
+ capacity[e1] = c;
+ variable[e1] = std::make_pair(v, 1);
+ }
+
+ if (rc) {
+ auto e2 = graph.addArc(nodes[y], nodes[x]);
+ cost[e2] = cst;
+ capacity[e2] = rc;
+ variable[e2] = std::make_pair(v, -1);
+ }
+ }
+ int compute() {
+ Preflow pf(graph, capacity, nodes.front(), nodes.back());
+ NetworkSimplex ns(graph);
+
+ // Run preflow to find maximum flow
+ lprintf("push-relabel flow... ");
+ pf.runMinCut();
+ int maxflow = pf.flowValue();
+
+ // Run network simplex to find minimum cost maximum flow
+ ns.costMap(cost).upperMap(capacity).stSupply(nodes.front(), nodes.back(), maxflow);
+ auto status = ns.run();
+ switch (status) {
+ case NetworkSimplex::OPTIMAL:
+ ns.flowMap(flow);
+ break;
+ case NetworkSimplex::INFEASIBLE:
+ lputs("NetworkSimplex::INFEASIBLE");
+ assert(0);
+ break;
+ default:
+ lputs("Unknown: NetworkSimplex::Default");
+ assert(0);
+ break;
+ }
+
+ return maxflow;
+ }
+ void applyTo(std::vector<Vector2i>& edge_diff) {
+ for (Graph::ArcIt e(graph); e != lemon::INVALID; ++e) {
+ int var = variable[e].first;
+ if (var == -1) continue;
+ int sgn = variable[e].second;
+ edge_diff[var / 2][var % 2] -= sgn * flow[e];
+ }
+ }
+
+ private:
+ Graph graph;
+ ArcMap<Weight> cost;
+ ArcMap<Capacity> capacity;
+ ArcMap<Capacity> flow;
+ ArcMap<std::pair<int, int>> variable;
+ std::vector<Node> nodes;
+ std::vector<Arc> edges;
+};
+
+#ifdef WITH_GUROBI
+
+#include <gurobi_c++.h>
+
+class GurobiFlowHelper : public MaxFlowHelper {
+ public:
+ GurobiFlowHelper() {}
+ virtual ~GurobiFlowHelper(){};
+ virtual void resize(int n, int m) {
+ nodes.resize(n * 2);
+ edges.resize(m);
+ }
+ virtual void addEdge(int x, int y, int c, int rc, int v, int cost = 1) {
+ nodes[x * 2 + 0].push_back(vars.size());
+ nodes[y * 2 + 1].push_back(vars.size());
+ vars.push_back(model.addVar(0, c, 0, GRB_INTEGER));
+ edges.push_back(std::make_pair(v, 1));
+
+ nodes[y * 2 + 0].push_back(vars.size());
+ nodes[x * 2 + 1].push_back(vars.size());
+ vars.push_back(model.addVar(0, rc, 0, GRB_INTEGER));
+ edges.push_back(std::make_pair(v, -1));
+ }
+ virtual int compute() {
+ std::cerr << "compute" << std::endl;
+ int ns = nodes.size() / 2;
+
+ int flow;
+ for (int i = 1; i < ns - 1; ++i) {
+ GRBLinExpr cons = 0;
+ for (auto n : nodes[2 * i + 0]) cons += vars[n];
+ for (auto n : nodes[2 * i + 1]) cons -= vars[n];
+ model.addConstr(cons == 0);
+ }
+
+ // first pass, maximum flow
+ GRBLinExpr outbound = 0;
+ {
+ lprintf("first pass\n");
+ for (auto& n : nodes[0]) outbound += vars[n];
+ for (auto& n : nodes[1]) outbound -= vars[n];
+ model.setObjective(outbound, GRB_MAXIMIZE);
+ model.optimize();
+
+ flow = (int)model.get(GRB_DoubleAttr_ObjVal);
+ lprintf("Gurobi result: %d\n", flow);
+ }
+
+ // second pass, minimum cost flow
+ {
+ lprintf("second pass\n");
+ model.addConstr(outbound == flow);
+ GRBLinExpr cost = 0;
+ for (auto& v : vars) cost += v;
+ model.setObjective(cost, GRB_MINIMIZE);
+ model.optimize();
+
+ double optimal_cost = (int)model.get(GRB_DoubleAttr_ObjVal);
+ lprintf("Gurobi result: %.3f\n", optimal_cost);
+ }
+ return flow;
+ }
+ virtual void applyTo(std::vector<Vector2i>& edge_diff) { assert(0); };
+
+ private:
+ GRBEnv env = GRBEnv();
+ GRBModel model = GRBModel(env);
+ std::vector<GRBVar> vars;
+ std::vector<std::pair<int, int>> edges;
+ std::vector<std::vector<int>> nodes;
+};
+
+#endif
+
+class ECMaxFlowHelper : public MaxFlowHelper {
+ public:
+ struct FlowInfo {
+ int id;
+ int capacity, flow;
+ int v, d;
+ FlowInfo* rev;
+ };
+ struct SearchInfo {
+ SearchInfo(int _id, int _prev_id, FlowInfo* _info)
+ : id(_id), prev_id(_prev_id), info(_info) {}
+ int id;
+ int prev_id;
+ FlowInfo* info;
+ };
+ ECMaxFlowHelper() { num = 0; }
+ int num;
+ std::vector<FlowInfo*> variable_to_edge;
+ void resize(int n, int m) {
+ graph.resize(n);
+ variable_to_edge.resize(m, 0);
+ num = n;
+ }
+ void addEdge(int x, int y, int c, int rc, int v, int cost = 0) {
+ FlowInfo flow;
+ flow.id = y;
+ flow.capacity = c;
+ flow.flow = 0;
+ flow.v = v;
+ flow.d = -1;
+ graph[x].push_back(flow);
+ auto& f1 = graph[x].back();
+ flow.id = x;
+ flow.capacity = rc;
+ flow.flow = 0;
+ flow.v = v;
+ flow.d = 1;
+ graph[y].push_back(flow);
+ auto& f2 = graph[y].back();
+ f2.rev = &f1;
+ f1.rev = &f2;
+ }
+ int compute() {
+ int total_flow = 0;
+ int count = 0;
+ while (true) {
+ count += 1;
+ std::vector<int> vhash(num, 0);
+ std::vector<SearchInfo> q;
+ q.push_back(SearchInfo(0, -1, 0));
+ vhash[0] = 1;
+ int q_front = 0;
+ bool found = false;
+ while (q_front < q.size()) {
+ int vert = q[q_front].id;
+ for (auto& l : graph[vert]) {
+ if (vhash[l.id] || l.capacity <= l.flow) continue;
+ q.push_back(SearchInfo(l.id, q_front, &l));
+ vhash[l.id] = 1;
+ if (l.id == num - 1) {
+ found = true;
+ break;
+ }
+ }
+ if (found) break;
+ q_front += 1;
+ }
+ if (q_front == q.size()) break;
+ int loc = q.size() - 1;
+ while (q[loc].prev_id != -1) {
+ q[loc].info->flow += 1;
+ q[loc].info->rev->flow -= 1;
+ loc = q[loc].prev_id;
+ // int prev_v = q[loc].id;
+ // applyFlow(prev_v, current_v, 1);
+ // applyFlow(current_v, prev_v, -1);
+ }
+ total_flow += 1;
+ }
+ return total_flow;
+ }
+ void applyTo(std::vector<Vector2i>& edge_diff) {
+ for (int i = 0; i < graph.size(); ++i) {
+ for (auto& flow : graph[i]) {
+ if (flow.flow > 0 && flow.v != -1) {
+ if (flow.flow > 0) {
+ edge_diff[flow.v / 2][flow.v % 2] += flow.d * flow.flow;
+ if (abs(edge_diff[flow.v / 2][flow.v % 2]) > 2) {
+ }
+ }
+ }
+ }
+ }
+ }
+ void applyFlow(int v1, int v2, int flow) {
+ for (auto& it : graph[v1]) {
+ if (it.id == v2) {
+ it.flow += flow;
+ break;
+ }
+ }
+ }
+ std::vector<std::list<FlowInfo>> graph;
+};
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/hierarchy.cpp b/extern/quadriflow/src/hierarchy.cpp
new file mode 100644
index 00000000000..c333256a139
--- /dev/null
+++ b/extern/quadriflow/src/hierarchy.cpp
@@ -0,0 +1,1343 @@
+#include "hierarchy.hpp"
+#include <fstream>
+#include <algorithm>
+#include <unordered_map>
+#include "config.hpp"
+#include "field-math.hpp"
+#include <queue>
+#include "localsat.hpp"
+#include "pcg32/pcg32.h"
+#ifdef WITH_TBB
+# include "tbb/tbb.h"
+# include "pss/parallel_stable_sort.h"
+#endif
+
+namespace qflow {
+
+Hierarchy::Hierarchy() {
+ mAdj.resize(MAX_DEPTH + 1);
+ mV.resize(MAX_DEPTH + 1);
+ mN.resize(MAX_DEPTH + 1);
+ mA.resize(MAX_DEPTH + 1);
+ mPhases.resize(MAX_DEPTH + 1);
+ mToLower.resize(MAX_DEPTH);
+ mToUpper.resize(MAX_DEPTH);
+ rng_seed = 0;
+
+ mCQ.reserve(MAX_DEPTH + 1);
+ mCQw.reserve(MAX_DEPTH + 1);
+ mCO.reserve(MAX_DEPTH + 1);
+ mCOw.reserve(MAX_DEPTH + 1);
+}
+
+#undef max
+
+void Hierarchy::Initialize(double scale, int with_scale) {
+ this->with_scale = with_scale;
+ generate_graph_coloring_deterministic(mAdj[0], mV[0].cols(), mPhases[0]);
+
+ for (int i = 0; i < MAX_DEPTH; ++i) {
+ DownsampleGraph(mAdj[i], mV[i], mN[i], mA[i], mV[i + 1], mN[i + 1], mA[i + 1], mToUpper[i],
+ mToLower[i], mAdj[i + 1]);
+ generate_graph_coloring_deterministic(mAdj[i + 1], mV[i + 1].cols(), mPhases[i + 1]);
+ if (mV[i + 1].cols() == 1) {
+ mAdj.resize(i + 2);
+ mV.resize(i + 2);
+ mN.resize(i + 2);
+ mA.resize(i + 2);
+ mToUpper.resize(i + 1);
+ mToLower.resize(i + 1);
+ break;
+ }
+ }
+ mQ.resize(mV.size());
+ mO.resize(mV.size());
+ mS.resize(mV.size());
+ mK.resize(mV.size());
+
+ mCO.resize(mV.size());
+ mCOw.resize(mV.size());
+ mCQ.resize(mV.size());
+ mCQw.resize(mV.size());
+
+ //Set random seed
+ srand(rng_seed);
+
+ mScale = scale;
+ for (int i = 0; i < mV.size(); ++i) {
+ mQ[i].resize(mN[i].rows(), mN[i].cols());
+ mO[i].resize(mN[i].rows(), mN[i].cols());
+ mS[i].resize(2, mN[i].cols());
+ mK[i].resize(2, mN[i].cols());
+ for (int j = 0; j < mN[i].cols(); ++j) {
+ Vector3d s, t;
+ coordinate_system(mN[i].col(j), s, t);
+ //rand() is not thread safe!
+ double angle = ((double)rand()) / RAND_MAX * 2 * M_PI;
+ double x = ((double)rand()) / RAND_MAX * 2 - 1.f;
+ double y = ((double)rand()) / RAND_MAX * 2 - 1.f;
+ mQ[i].col(j) = s * std::cos(angle) + t * std::sin(angle);
+ mO[i].col(j) = mV[i].col(j) + (s * x + t * y) * scale;
+ if (with_scale) {
+ mS[i].col(j) = Vector2d(1.0f, 1.0f);
+ mK[i].col(j) = Vector2d(0.0, 0.0);
+ }
+ }
+ }
+#ifdef WITH_CUDA
+ printf("copy to device...\n");
+ CopyToDevice();
+ printf("copy to device finish...\n");
+#endif
+}
+
+#ifdef WITH_TBB
+void Hierarchy::generate_graph_coloring_deterministic(const AdjacentMatrix& adj, int size,
+ std::vector<std::vector<int>>& phases) {
+ struct ColorData {
+ uint8_t nColors;
+ uint32_t nNodes[256];
+ ColorData() : nColors(0) {}
+ };
+
+ const uint8_t INVALID_COLOR = 0xFF;
+ phases.clear();
+
+ /* Generate a permutation */
+ std::vector<uint32_t> perm(size);
+ std::vector<tbb::spin_mutex> mutex(size);
+ for (uint32_t i = 0; i < size; ++i) perm[i] = i;
+
+ tbb::parallel_for(tbb::blocked_range<uint32_t>(0u, size, GRAIN_SIZE),
+ [&](const tbb::blocked_range<uint32_t>& range) {
+ pcg32 rng;
+ rng.advance(range.begin());
+ for (uint32_t i = range.begin(); i != range.end(); ++i) {
+ uint32_t j = i, k = rng.nextUInt(size - i) + i;
+ if (j == k) continue;
+ if (j > k) std::swap(j, k);
+ tbb::spin_mutex::scoped_lock l0(mutex[j]);
+ tbb::spin_mutex::scoped_lock l1(mutex[k]);
+ std::swap(perm[j], perm[k]);
+ }
+ });
+
+ std::vector<uint8_t> color(size, INVALID_COLOR);
+ ColorData colorData = tbb::parallel_reduce(
+ tbb::blocked_range<uint32_t>(0u, size, GRAIN_SIZE), ColorData(),
+ [&](const tbb::blocked_range<uint32_t>& range, ColorData colorData) -> ColorData {
+ std::vector<uint32_t> neighborhood;
+ bool possible_colors[256];
+
+ for (uint32_t pidx = range.begin(); pidx != range.end(); ++pidx) {
+ uint32_t i = perm[pidx];
+
+ neighborhood.clear();
+ neighborhood.push_back(i);
+ // for (const Link *link = adj[i]; link != adj[i + 1]; ++link)
+ for (auto& link : adj[i]) neighborhood.push_back(link.id);
+ std::sort(neighborhood.begin(), neighborhood.end());
+ for (uint32_t j : neighborhood) mutex[j].lock();
+
+ std::fill(possible_colors, possible_colors + colorData.nColors, true);
+
+ // for (const Link *link = adj[i]; link != adj[i + 1]; ++link) {
+ for (auto& link : adj[i]) {
+ uint8_t c = color[link.id];
+ if (c != INVALID_COLOR) {
+ while (c >= colorData.nColors) {
+ possible_colors[colorData.nColors] = true;
+ colorData.nNodes[colorData.nColors] = 0;
+ colorData.nColors++;
+ }
+ possible_colors[c] = false;
+ }
+ }
+
+ uint8_t chosen_color = INVALID_COLOR;
+ for (uint8_t j = 0; j < colorData.nColors; ++j) {
+ if (possible_colors[j]) {
+ chosen_color = j;
+ break;
+ }
+ }
+ if (chosen_color == INVALID_COLOR) {
+ if (colorData.nColors == INVALID_COLOR - 1)
+ throw std::runtime_error(
+ "Ran out of colors during graph coloring! "
+ "The input mesh is very likely corrupt.");
+ colorData.nNodes[colorData.nColors] = 1;
+ color[i] = colorData.nColors++;
+ } else {
+ colorData.nNodes[chosen_color]++;
+ color[i] = chosen_color;
+ }
+
+ for (uint32_t j : neighborhood) mutex[j].unlock();
+ }
+ return colorData;
+ },
+ [](ColorData c1, ColorData c2) -> ColorData {
+ ColorData result;
+ result.nColors = std::max(c1.nColors, c2.nColors);
+ memset(result.nNodes, 0, sizeof(uint32_t) * result.nColors);
+ for (uint8_t i = 0; i < c1.nColors; ++i) result.nNodes[i] += c1.nNodes[i];
+ for (uint8_t i = 0; i < c2.nColors; ++i) result.nNodes[i] += c2.nNodes[i];
+ return result;
+ });
+
+ phases.resize(colorData.nColors);
+ for (int i = 0; i < colorData.nColors; ++i) phases[i].reserve(colorData.nNodes[i]);
+
+ for (uint32_t i = 0; i < size; ++i) phases[color[i]].push_back(i);
+}
+#else
+void Hierarchy::generate_graph_coloring_deterministic(const AdjacentMatrix& adj, int size,
+ std::vector<std::vector<int>>& phases) {
+ phases.clear();
+
+ std::vector<uint32_t> perm(size);
+ for (uint32_t i = 0; i < size; ++i) perm[i] = i;
+ pcg32 rng;
+ rng.shuffle(perm.begin(), perm.end());
+
+ std::vector<int> color(size, -1);
+ std::vector<uint8_t> possible_colors;
+ std::vector<int> size_per_color;
+ int ncolors = 0;
+
+ for (uint32_t i = 0; i < size; ++i) {
+ uint32_t ip = perm[i];
+
+ std::fill(possible_colors.begin(), possible_colors.end(), 1);
+
+ for (auto& link : adj[ip]) {
+ int c = color[link.id];
+ if (c >= 0) possible_colors[c] = 0;
+ }
+
+ int chosen_color = -1;
+ for (uint32_t j = 0; j < possible_colors.size(); ++j) {
+ if (possible_colors[j]) {
+ chosen_color = j;
+ break;
+ }
+ }
+
+ if (chosen_color < 0) {
+ chosen_color = ncolors++;
+ possible_colors.resize(ncolors);
+ size_per_color.push_back(0);
+ }
+
+ color[ip] = chosen_color;
+ size_per_color[chosen_color]++;
+ }
+ phases.resize(ncolors);
+ for (int i = 0; i < ncolors; ++i) phases[i].reserve(size_per_color[i]);
+ for (uint32_t i = 0; i < size; ++i) phases[color[i]].push_back(i);
+}
+#endif
+
+void Hierarchy::DownsampleGraph(const AdjacentMatrix adj, const MatrixXd& V, const MatrixXd& N,
+ const VectorXd& A, MatrixXd& V_p, MatrixXd& N_p, VectorXd& A_p,
+ MatrixXi& to_upper, VectorXi& to_lower, AdjacentMatrix& adj_p) {
+ struct Entry {
+ int i, j;
+ double order;
+ inline Entry() { i = j = -1; };
+ inline Entry(int i, int j, double order) : i(i), j(j), order(order) {}
+ inline bool operator<(const Entry& e) const { return order > e.order; }
+ inline bool operator==(const Entry& e) const { return order == e.order; }
+ };
+
+ int nLinks = 0;
+ for (auto& adj_i : adj) nLinks += adj_i.size();
+ std::vector<Entry> entries(nLinks);
+ std::vector<int> bases(adj.size());
+ for (int i = 1; i < bases.size(); ++i) {
+ bases[i] = bases[i - 1] + adj[i - 1].size();
+ }
+
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V.cols(); ++i) {
+ int base = bases[i];
+ auto& ad = adj[i];
+ auto entry_it = entries.begin() + base;
+ for (auto it = ad.begin(); it != ad.end(); ++it, ++entry_it) {
+ int k = it->id;
+ double dp = N.col(i).dot(N.col(k));
+ double ratio = A[i] > A[k] ? (A[i] / A[k]) : (A[k] / A[i]);
+ *entry_it = Entry(i, k, dp * ratio);
+ }
+ }
+
+#ifdef WITH_TBB
+ pss::parallel_stable_sort(entries.begin(), entries.end(), std::less<Entry>());
+#else
+ std::stable_sort(entries.begin(), entries.end(), std::less<Entry>());
+#endif
+
+ std::vector<bool> mergeFlag(V.cols(), false);
+
+ int nCollapsed = 0;
+ for (int i = 0; i < nLinks; ++i) {
+ const Entry& e = entries[i];
+ if (mergeFlag[e.i] || mergeFlag[e.j]) continue;
+ mergeFlag[e.i] = mergeFlag[e.j] = true;
+ entries[nCollapsed++] = entries[i];
+ }
+ int vertexCount = V.cols() - nCollapsed;
+
+ // Allocate memory for coarsened graph
+ V_p.resize(3, vertexCount);
+ N_p.resize(3, vertexCount);
+ A_p.resize(vertexCount);
+ to_upper.resize(2, vertexCount);
+ to_lower.resize(V.cols());
+
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < nCollapsed; ++i) {
+ const Entry& e = entries[i];
+ const double area1 = A[e.i], area2 = A[e.j], surfaceArea = area1 + area2;
+ if (surfaceArea > RCPOVERFLOW)
+ V_p.col(i) = (V.col(e.i) * area1 + V.col(e.j) * area2) / surfaceArea;
+ else
+ V_p.col(i) = (V.col(e.i) + V.col(e.j)) * 0.5f;
+ Vector3d normal = N.col(e.i) * area1 + N.col(e.j) * area2;
+ double norm = normal.norm();
+ N_p.col(i) = norm > RCPOVERFLOW ? Vector3d(normal / norm) : Vector3d::UnitX();
+ A_p[i] = surfaceArea;
+ to_upper.col(i) << e.i, e.j;
+ to_lower[e.i] = i;
+ to_lower[e.j] = i;
+ }
+
+ int offset = nCollapsed;
+
+ for (int i = 0; i < V.cols(); ++i) {
+ if (!mergeFlag[i]) {
+ int idx = offset++;
+ V_p.col(idx) = V.col(i);
+ N_p.col(idx) = N.col(i);
+ A_p[idx] = A[i];
+ to_upper.col(idx) << i, -1;
+ to_lower[i] = idx;
+ }
+ }
+
+ adj_p.resize(V_p.cols());
+ std::vector<int> capacity(V_p.cols());
+ std::vector<std::vector<Link>> scratches(V_p.cols());
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V_p.cols(); ++i) {
+ int t = 0;
+ for (int j = 0; j < 2; ++j) {
+ int upper = to_upper(j, i);
+ if (upper == -1) continue;
+ t += adj[upper].size();
+ }
+ scratches[i].reserve(t);
+ adj_p[i].reserve(t);
+ }
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V_p.cols(); ++i) {
+ auto& scratch = scratches[i];
+ for (int j = 0; j < 2; ++j) {
+ int upper = to_upper(j, i);
+ if (upper == -1) continue;
+ auto& ad = adj[upper];
+ for (auto& link : ad) scratch.push_back(Link(to_lower[link.id], link.weight));
+ }
+ std::sort(scratch.begin(), scratch.end());
+ int id = -1;
+ auto& ad = adj_p[i];
+ for (auto& link : scratch) {
+ if (link.id != i) {
+ if (id != link.id) {
+ ad.push_back(link);
+ id = link.id;
+ } else {
+ ad.back().weight += link.weight;
+ }
+ }
+ }
+ }
+}
+
+void Hierarchy::SaveToFile(FILE* fp) {
+ Save(fp, mScale);
+ Save(fp, mF);
+ Save(fp, mE2E);
+ Save(fp, mAdj);
+ Save(fp, mV);
+ Save(fp, mN);
+ Save(fp, mA);
+ Save(fp, mToLower);
+ Save(fp, mToUpper);
+ Save(fp, mQ);
+ Save(fp, mO);
+ Save(fp, mS);
+ Save(fp, mK);
+ Save(fp, this->mPhases);
+}
+
+void Hierarchy::LoadFromFile(FILE* fp) {
+ Read(fp, mScale);
+ Read(fp, mF);
+ Read(fp, mE2E);
+ Read(fp, mAdj);
+ Read(fp, mV);
+ Read(fp, mN);
+ Read(fp, mA);
+ Read(fp, mToLower);
+ Read(fp, mToUpper);
+ Read(fp, mQ);
+ Read(fp, mO);
+ Read(fp, mS);
+ Read(fp, mK);
+ Read(fp, this->mPhases);
+}
+
+void Hierarchy::UpdateGraphValue(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E,
+ std::vector<Vector2i>& edge_diff) {
+ FQ = std::move(mFQ[0]);
+ F2E = std::move(mF2E[0]);
+ edge_diff = std::move(mEdgeDiff[0]);
+}
+
+void Hierarchy::DownsampleEdgeGraph(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E,
+ std::vector<Vector2i>& edge_diff,
+ std::vector<int>& allow_changes, int level) {
+ std::vector<Vector2i> E2F(edge_diff.size(), Vector2i(-1, -1));
+ for (int i = 0; i < F2E.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int e = F2E[i][j];
+ if (E2F[e][0] == -1)
+ E2F[e][0] = i;
+ else
+ E2F[e][1] = i;
+ }
+ }
+ int levels = (level == -1) ? 100 : level;
+ mFQ.resize(levels);
+ mF2E.resize(levels);
+ mE2F.resize(levels);
+ mEdgeDiff.resize(levels);
+ mAllowChanges.resize(levels);
+ mSing.resize(levels);
+ mToUpperEdges.resize(levels - 1);
+ mToUpperOrients.resize(levels - 1);
+ for (int i = 0; i < FQ.size(); ++i) {
+ Vector2i diff(0, 0);
+ for (int j = 0; j < 3; ++j) {
+ diff += rshift90(edge_diff[F2E[i][j]], FQ[i][j]);
+ }
+ if (diff != Vector2i::Zero()) {
+ mSing[0].push_back(i);
+ }
+ }
+ mAllowChanges[0] = allow_changes;
+ mFQ[0] = std::move(FQ);
+ mF2E[0] = std::move(F2E);
+ mE2F[0] = std::move(E2F);
+ mEdgeDiff[0] = std::move(edge_diff);
+ for (int l = 0; l < levels - 1; ++l) {
+ auto& FQ = mFQ[l];
+ auto& E2F = mE2F[l];
+ auto& F2E = mF2E[l];
+ auto& Allow = mAllowChanges[l];
+ auto& EdgeDiff = mEdgeDiff[l];
+ auto& Sing = mSing[l];
+ std::vector<int> fixed_faces(F2E.size(), 0);
+ for (auto& s : Sing) {
+ fixed_faces[s] = 1;
+ }
+
+ auto& toUpper = mToUpperEdges[l];
+ auto& toUpperOrients = mToUpperOrients[l];
+ toUpper.resize(E2F.size(), -1);
+ toUpperOrients.resize(E2F.size(), 0);
+
+ auto& nFQ = mFQ[l + 1];
+ auto& nE2F = mE2F[l + 1];
+ auto& nF2E = mF2E[l + 1];
+ auto& nAllow = mAllowChanges[l + 1];
+ auto& nEdgeDiff = mEdgeDiff[l + 1];
+ auto& nSing = mSing[l + 1];
+
+ for (int i = 0; i < E2F.size(); ++i) {
+ if (EdgeDiff[i] != Vector2i::Zero()) continue;
+ if ((E2F[i][0] >= 0 && fixed_faces[E2F[i][0]]) ||
+ (E2F[i][1] >= 0 && fixed_faces[E2F[i][1]])) {
+ continue;
+ }
+ for (int j = 0; j < 2; ++j) {
+ int f = E2F[i][j];
+ if (f < 0)
+ continue;
+ for (int k = 0; k < 3; ++k) {
+ int neighbor_e = F2E[f][k];
+ for (int m = 0; m < 2; ++m) {
+ int neighbor_f = E2F[neighbor_e][m];
+ if (neighbor_f < 0)
+ continue;
+ if (fixed_faces[neighbor_f] == 0) fixed_faces[neighbor_f] = 1;
+ }
+ }
+ }
+ if (E2F[i][0] >= 0)
+ fixed_faces[E2F[i][0]] = 2;
+ if (E2F[i][1] >= 0)
+ fixed_faces[E2F[i][1]] = 2;
+ toUpper[i] = -2;
+ }
+ for (int i = 0; i < E2F.size(); ++i) {
+ if (toUpper[i] == -2) continue;
+ if ((E2F[i][0] < 0 || fixed_faces[E2F[i][0]] == 2) && (E2F[i][1] < 0 || fixed_faces[E2F[i][1]] == 2)) {
+ toUpper[i] = -3;
+ continue;
+ }
+ }
+ int numE = 0;
+ for (int i = 0; i < toUpper.size(); ++i) {
+ if (toUpper[i] == -1) {
+ if ((E2F[i][0] < 0 || fixed_faces[E2F[i][0]] < 2) && (E2F[i][1] < 0 || fixed_faces[E2F[i][1]] < 2)) {
+ nE2F.push_back(E2F[i]);
+ toUpperOrients[i] = 0;
+ toUpper[i] = numE++;
+ continue;
+ }
+ int f0 = (E2F[i][1] < 0 || fixed_faces[E2F[i][0]] < 2) ? E2F[i][0] : E2F[i][1];
+ int e = i;
+ int f = f0;
+ std::vector<std::pair<int, int>> paths;
+ paths.push_back(std::make_pair(i, 0));
+ while (true) {
+ if (E2F[e][0] == f)
+ f = E2F[e][1];
+ else if (E2F[e][1] == f)
+ f = E2F[e][0];
+ if (f < 0 || fixed_faces[f] < 2) {
+ for (int j = 0; j < paths.size(); ++j) {
+ auto& p = paths[j];
+ toUpper[p.first] = numE;
+ int orient = p.second;
+ if (j > 0) orient = (orient + toUpperOrients[paths[j - 1].first]) % 4;
+ toUpperOrients[p.first] = orient;
+ }
+ nE2F.push_back(Vector2i(f0, f));
+ numE += 1;
+ break;
+ }
+ int ind0 = -1, ind1 = -1;
+ int e0 = e;
+ for (int j = 0; j < 3; ++j) {
+ if (F2E[f][j] == e) {
+ ind0 = j;
+ break;
+ }
+ }
+ for (int j = 0; j < 3; ++j) {
+ int e1 = F2E[f][j];
+ if (e1 != e && toUpper[e1] != -2) {
+ e = e1;
+ ind1 = j;
+ break;
+ }
+ }
+
+ if (ind1 != -1) {
+ paths.push_back(std::make_pair(e, (FQ[f][ind1] - FQ[f][ind0] + 6) % 4));
+ } else {
+ if (EdgeDiff[e] != Vector2i::Zero()) {
+ printf("Unsatisfied !!!...\n");
+ printf("%d %d %d: %d %d\n", F2E[f][0], F2E[f][1], F2E[f][2], e0, e);
+ exit(0);
+ }
+ for (auto& p : paths) {
+ toUpper[p.first] = numE;
+ toUpperOrients[p.first] = 0;
+ }
+ numE += 1;
+ nE2F.push_back(Vector2i(f0, f0));
+ break;
+ }
+ }
+ }
+ }
+ nEdgeDiff.resize(numE);
+ nAllow.resize(numE * 2, 1);
+ for (int i = 0; i < toUpper.size(); ++i) {
+ if (toUpper[i] >= 0 && toUpperOrients[i] == 0) {
+ nEdgeDiff[toUpper[i]] = EdgeDiff[i];
+ }
+ if (toUpper[i] >= 0) {
+ int dimension = toUpperOrients[i] % 2;
+ if (Allow[i * 2 + dimension] == 0)
+ nAllow[toUpper[i] * 2] = 0;
+ else if (Allow[i * 2 + dimension] == 2)
+ nAllow[toUpper[i] * 2] = 2;
+ if (Allow[i * 2 + 1 - dimension] == 0)
+ nAllow[toUpper[i] * 2 + 1] = 0;
+ else if (Allow[i * 2 + 1 - dimension] == 2)
+ nAllow[toUpper[i] * 2 + 1] = 2;
+ }
+ }
+ std::vector<int> upperface(F2E.size(), -1);
+
+ for (int i = 0; i < F2E.size(); ++i) {
+ Vector3i eid;
+ for (int j = 0; j < 3; ++j) {
+ eid[j] = toUpper[F2E[i][j]];
+ }
+ if (eid[0] >= 0 && eid[1] >= 0 && eid[2] >= 0) {
+ Vector3i eid_orient;
+ for (int j = 0; j < 3; ++j) {
+ eid_orient[j] = (FQ[i][j] + 4 - toUpperOrients[F2E[i][j]]) % 4;
+ }
+ upperface[i] = nF2E.size();
+ nF2E.push_back(eid);
+ nFQ.push_back(eid_orient);
+ }
+ }
+ for (int i = 0; i < nE2F.size(); ++i) {
+ for (int j = 0; j < 2; ++j) {
+ if (nE2F[i][j] >= 0)
+ nE2F[i][j] = upperface[nE2F[i][j]];
+ }
+ }
+
+ for (auto& s : Sing) {
+ if (upperface[s] >= 0) nSing.push_back(upperface[s]);
+ }
+ mToUpperFaces.push_back(std::move(upperface));
+
+ if (nEdgeDiff.size() == EdgeDiff.size()) {
+ levels = l + 1;
+ break;
+ }
+ }
+
+ mFQ.resize(levels);
+ mF2E.resize(levels);
+ mAllowChanges.resize(levels);
+ mE2F.resize(levels);
+ mEdgeDiff.resize(levels);
+ mSing.resize(levels);
+ mToUpperEdges.resize(levels - 1);
+ mToUpperOrients.resize(levels - 1);
+}
+
+int Hierarchy::FixFlipSat(int depth, int threshold) {
+ if (system("which minisat > /dev/null 2>&1")) {
+ printf("minisat not found, \"-sat\" will not be used!\n");
+ return 0;
+ }
+ if (system("which timeout > /dev/null 2>&1")) {
+ printf("timeout not found, \"-sat\" will not be used!\n");
+ return 0;
+ }
+
+ auto& F2E = mF2E[depth];
+ auto& E2F = mE2F[depth];
+ auto& FQ = mFQ[depth];
+ auto& EdgeDiff = mEdgeDiff[depth];
+ auto& AllowChanges = mAllowChanges[depth];
+
+ // build E2E
+ std::vector<int> E2E(F2E.size() * 3, -1);
+ for (int i = 0; i < E2F.size(); ++i) {
+ int f1 = E2F[i][0];
+ int f2 = E2F[i][1];
+ int t1 = 0;
+ int t2 = 2;
+ if (f1 != -1) while (F2E[f1][t1] != i) t1 += 1;
+ if (f2 != -1) while (F2E[f2][t2] != i) t2 -= 1;
+ t1 += f1 * 3;
+ t2 += f2 * 3;
+ if (f1 != -1) E2E[t1] = (f2 == -1) ? -1 : t2;
+ if (f2 != -1) E2E[t2] = (f1 == -1) ? -1 : t1;
+ }
+
+ auto IntegerArea = [&](int f) {
+ Vector2i diff1 = rshift90(EdgeDiff[F2E[f][0]], FQ[f][0]);
+ Vector2i diff2 = rshift90(EdgeDiff[F2E[f][1]], FQ[f][1]);
+ return diff1[0] * diff2[1] - diff1[1] * diff2[0];
+ };
+
+ std::deque<std::pair<int, int>> Q;
+ std::vector<bool> mark_dedges(F2E.size() * 3, false);
+ for (int f = 0; f < F2E.size(); ++f) {
+ if (IntegerArea(f) < 0) {
+ for (int j = 0; j < 3; ++j) {
+ if (mark_dedges[f * 3 + j]) continue;
+ Q.push_back(std::make_pair(f * 3 + j, 0));
+ mark_dedges[f * 3 + j] = true;
+ }
+ }
+ }
+
+ int mark_count = 0;
+ while (!Q.empty()) {
+ int e0 = Q.front().first;
+ int depth = Q.front().second;
+ Q.pop_front();
+ mark_count++;
+
+ int e = e0, e1;
+ do {
+ e1 = E2E[e];
+ if (e1 == -1) break;
+ int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum();
+ if (length == 0 && !mark_dedges[e1]) {
+ mark_dedges[e1] = true;
+ Q.push_front(std::make_pair(e1, depth));
+ }
+ e = (e1 / 3) * 3 + (e1 + 1) % 3;
+ mark_dedges[e] = true;
+ } while (e != e0);
+ if (e1 == -1) {
+ do {
+ e1 = E2E[e];
+ if (e1 == -1) break;
+ int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum();
+ if (length == 0 && !mark_dedges[e1]) {
+ mark_dedges[e1] = true;
+ Q.push_front(std::make_pair(e1, depth));
+ }
+ e = (e1 / 3) * 3 + (e1 + 2) % 3;
+ mark_dedges[e] = true;
+ } while (e != e0);
+ }
+
+ do {
+ e1 = E2E[e];
+ if (e1 == -1) break;
+ int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum();
+ if (length > 0 && depth + length <= threshold && !mark_dedges[e1]) {
+ mark_dedges[e1] = true;
+ Q.push_back(std::make_pair(e1, depth + length));
+ }
+ e = e1 / 3 * 3 + (e1 + 1) % 3;
+ mark_dedges[e] = true;
+ } while (e != e0);
+ if (e1 == -1) {
+ do {
+ e1 = E2E[e];
+ if (e1 == -1) break;
+ int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum();
+ if (length > 0 && depth + length <= threshold && !mark_dedges[e1]) {
+ mark_dedges[e1] = true;
+ Q.push_back(std::make_pair(e1, depth + length));
+ }
+ e = e1 / 3 * 3 + (e1 + 2) % 3;
+ mark_dedges[e] = true;
+ } while (e != e0);
+ }
+ }
+ lprintf("[FlipH] Depth %2d: marked = %d\n", depth, mark_count);
+
+ std::vector<bool> flexible(EdgeDiff.size(), false);
+ for (int i = 0; i < F2E.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int dedge = i * 3 + j;
+ int edgeid = F2E[i][j];
+ if (mark_dedges[dedge]) {
+ flexible[edgeid] = true;
+ }
+ }
+ }
+ for (int i = 0; i < flexible.size(); ++i) {
+ if (E2F[i][0] == E2F[i][1]) flexible[i] = false;
+ if (AllowChanges[i] == 0) flexible[i] = false;
+ }
+
+ // Reindexing and solve
+ int num_group = 0;
+ std::vector<int> groups(EdgeDiff.size(), -1);
+ std::vector<int> indices(EdgeDiff.size(), -1);
+ for (int i = 0; i < EdgeDiff.size(); ++i) {
+ if (groups[i] == -1 && flexible[i]) {
+ // group it
+ std::queue<int> q;
+ q.push(i);
+ groups[i] = num_group;
+ while (!q.empty()) {
+ int e = q.front();
+ q.pop();
+ int f[] = {E2F[e][0], E2F[e][1]};
+ for (int j = 0; j < 2; ++j) {
+ if (f[j] == -1) continue;
+ for (int k = 0; k < 3; ++k) {
+ int e1 = F2E[f[j]][k];
+ if (flexible[e1] && groups[e1] == -1) {
+ groups[e1] = num_group;
+ q.push(e1);
+ }
+ }
+ }
+ }
+ num_group += 1;
+ }
+ }
+
+ std::vector<int> num_edges(num_group);
+ std::vector<int> num_flips(num_group);
+ std::vector<std::vector<int>> values(num_group);
+ std::vector<std::vector<Vector3i>> variable_eq(num_group);
+ std::vector<std::vector<Vector3i>> constant_eq(num_group);
+ std::vector<std::vector<Vector4i>> variable_ge(num_group);
+ std::vector<std::vector<Vector2i>> constant_ge(num_group);
+ for (int i = 0; i < groups.size(); ++i) {
+ if (groups[i] != -1) {
+ indices[i] = num_edges[groups[i]]++;
+ values[groups[i]].push_back(EdgeDiff[i][0]);
+ values[groups[i]].push_back(EdgeDiff[i][1]);
+ }
+ }
+ std::vector<int> num_edges_flexible = num_edges;
+ std::map<std::pair<int, int>, int> fixed_variables;
+ for (int i = 0; i < F2E.size(); ++i) {
+ Vector2i var[3];
+ Vector2i cst[3];
+ int gind = 0;
+ while (gind < 3 && groups[F2E[i][gind]] == -1) gind += 1;
+ if (gind == 3) continue;
+ int group = groups[F2E[i][gind]];
+ int ind[3] = {-1, -1, -1};
+ for (int j = 0; j < 3; ++j) {
+ int g = groups[F2E[i][j]];
+ if (g != group) {
+ if (g == -1) {
+ auto key = std::make_pair(F2E[i][j], group);
+ auto it = fixed_variables.find(key);
+ if (it == fixed_variables.end()) {
+ ind[j] = num_edges[group];
+ values[group].push_back(EdgeDiff[F2E[i][j]][0]);
+ values[group].push_back(EdgeDiff[F2E[i][j]][1]);
+ fixed_variables[key] = num_edges[group]++;
+ } else {
+ ind[j] = it->second;
+ }
+ }
+ } else {
+ ind[j] = indices[F2E[i][j]];
+ }
+ }
+ for (int j = 0; j < 3; ++j) assert(ind[j] != -1);
+ for (int j = 0; j < 3; ++j) {
+ var[j] = rshift90(Vector2i(ind[j] * 2 + 1, ind[j] * 2 + 2), FQ[i][j]);
+ cst[j] = var[j].array().sign();
+ var[j] = var[j].array().abs() - 1;
+ }
+
+ num_flips[group] += IntegerArea(i) < 0;
+ variable_eq[group].push_back(Vector3i(var[0][0], var[1][0], var[2][0]));
+ constant_eq[group].push_back(Vector3i(cst[0][0], cst[1][0], cst[2][0]));
+ variable_eq[group].push_back(Vector3i(var[0][1], var[1][1], var[2][1]));
+ constant_eq[group].push_back(Vector3i(cst[0][1], cst[1][1], cst[2][1]));
+
+ variable_ge[group].push_back(Vector4i(var[0][0], var[1][1], var[0][1], var[1][0]));
+ constant_ge[group].push_back(Vector2i(cst[0][0] * cst[1][1], cst[0][1] * cst[1][0]));
+ }
+ int flip_before = 0, flip_after = 0;
+ for (int i = 0; i < F2E.size(); ++i) {
+ int area = IntegerArea(i);
+ if (area < 0) flip_before++;
+ }
+
+ for (int i = 0; i < num_group; ++i) {
+ std::vector<bool> flexible(values[i].size(), true);
+ for (int j = num_edges_flexible[i] * 2; j < flexible.size(); ++j) {
+ flexible[j] = false;
+ }
+ SolveSatProblem(values[i].size(), values[i], flexible, variable_eq[i], constant_eq[i],
+ variable_ge[i], constant_ge[i]);
+ }
+
+ for (int i = 0; i < EdgeDiff.size(); ++i) {
+ int group = groups[i];
+ if (group == -1) continue;
+ EdgeDiff[i][0] = values[group][2 * indices[i] + 0];
+ EdgeDiff[i][1] = values[group][2 * indices[i] + 1];
+ }
+ for (int i = 0; i < F2E.size(); ++i) {
+ Vector2i diff(0, 0);
+ for (int j = 0; j < 3; ++j) {
+ diff += rshift90(EdgeDiff[F2E[i][j]], FQ[i][j]);
+ }
+ assert(diff == Vector2i::Zero());
+
+ int area = IntegerArea(i);
+ if (area < 0) flip_after++;
+ }
+
+ lprintf("[FlipH] FlipArea, Before: %d After %d\n", flip_before, flip_after);
+ return flip_after;
+}
+
+void Hierarchy::PushDownwardFlip(int depth) {
+ auto& EdgeDiff = mEdgeDiff[depth];
+ auto& nEdgeDiff = mEdgeDiff[depth - 1];
+ auto& toUpper = mToUpperEdges[depth - 1];
+ auto& toUpperOrients = mToUpperOrients[depth - 1];
+ auto& toUpperFaces = mToUpperFaces[depth - 1];
+ for (int i = 0; i < toUpper.size(); ++i) {
+ if (toUpper[i] >= 0) {
+ int orient = (4 - toUpperOrients[i]) % 4;
+ nEdgeDiff[i] = rshift90(EdgeDiff[toUpper[i]], orient);
+ } else {
+ nEdgeDiff[i] = Vector2i(0, 0);
+ }
+ }
+ auto& nF2E = mF2E[depth - 1];
+ auto& nFQ = mFQ[depth - 1];
+ for (int i = 0; i < nF2E.size(); ++i) {
+ Vector2i diff(0, 0);
+ for (int j = 0; j < 3; ++j) {
+ diff += rshift90(nEdgeDiff[nF2E[i][j]], nFQ[i][j]);
+ }
+ if (diff != Vector2i::Zero()) {
+ printf("Fail!!!!!!! %d\n", i);
+ for (int j = 0; j < 3; ++j) {
+ Vector2i d = rshift90(nEdgeDiff[nF2E[i][j]], nFQ[i][j]);
+ printf("<%d %d %d>\n", nF2E[i][j], nFQ[i][j], toUpperOrients[nF2E[i][j]]);
+ printf("%d %d\n", d[0], d[1]);
+ printf("%d -> %d\n", nF2E[i][j], toUpper[nF2E[i][j]]);
+ }
+ printf("%d -> %d\n", i, toUpperFaces[i]);
+ exit(1);
+ }
+ }
+}
+
+void Hierarchy::FixFlip() {
+ int l = mF2E.size() - 1;
+ auto& F2E = mF2E[l];
+ auto& E2F = mE2F[l];
+ auto& FQ = mFQ[l];
+ auto& EdgeDiff = mEdgeDiff[l];
+ auto& AllowChange = mAllowChanges[l];
+
+ // build E2E
+ std::vector<int> E2E(F2E.size() * 3, -1);
+ for (int i = 0; i < E2F.size(); ++i) {
+ int v1 = E2F[i][0];
+ int v2 = E2F[i][1];
+ int t1 = 0;
+ int t2 = 2;
+ if (v1 != -1)
+ while (F2E[v1][t1] != i) t1 += 1;
+ if (v2 != -1)
+ while (F2E[v2][t2] != i) t2 -= 1;
+ t1 += v1 * 3;
+ t2 += v2 * 3;
+ if (v1 != -1)
+ E2E[t1] = (v2 == -1) ? -1 : t2;
+ if (v2 != -1)
+ E2E[t2] = (v1 == -1) ? -1 : t1;
+ }
+
+ auto Area = [&](int f) {
+ Vector2i diff1 = rshift90(EdgeDiff[F2E[f][0]], FQ[f][0]);
+ Vector2i diff2 = rshift90(EdgeDiff[F2E[f][1]], FQ[f][1]);
+ return diff1[0] * diff2[1] - diff1[1] * diff2[0];
+ };
+ std::vector<int> valences(F2E.size() * 3, -10000); // comment this line
+ auto CheckShrink = [&](int deid, int allowed_edge_length) {
+ // Check if we want shrink direct edge deid so that all edge length is smaller than
+ // allowed_edge_length
+ if (deid == -1) {
+ return false;
+ }
+ std::vector<int> corresponding_faces;
+ std::vector<int> corresponding_edges;
+ std::vector<Vector2i> corresponding_diff;
+ int deid0 = deid;
+ while (deid != -1) {
+ deid = deid / 3 * 3 + (deid + 2) % 3;
+ if (E2E[deid] == -1)
+ break;
+ deid = E2E[deid];
+ if (deid == deid0)
+ break;
+ }
+ Vector2i diff = EdgeDiff[F2E[deid / 3][deid % 3]];
+ do {
+ corresponding_diff.push_back(diff);
+ corresponding_edges.push_back(deid);
+ corresponding_faces.push_back(deid / 3);
+
+ // transform to the next face
+ deid = E2E[deid];
+ if (deid == -1) {
+ return false;
+ }
+ // transform for the target incremental diff
+ diff = -rshift90(diff, FQ[deid / 3][deid % 3]);
+ deid = deid / 3 * 3 + (deid + 1) % 3;
+ // transform to local
+ diff = rshift90(diff, (4 - FQ[deid / 3][deid % 3]) % 4);
+ } while (deid != corresponding_edges.front());
+ // check diff
+ if (deid != -1 && diff != corresponding_diff.front()) {
+ return false;
+ }
+ std::unordered_map<int, Vector2i> new_values;
+ for (int i = 0; i < corresponding_diff.size(); ++i) {
+ int deid = corresponding_edges[i];
+ int eid = F2E[deid / 3][deid % 3];
+ new_values[eid] = EdgeDiff[eid];
+ }
+ for (int i = 0; i < corresponding_diff.size(); ++i) {
+ int deid = corresponding_edges[i];
+ int eid = F2E[deid / 3][deid % 3];
+ for (int j = 0; j < 2; ++j) {
+ if (corresponding_diff[i][j] != 0 && AllowChange[eid * 2 + j] == 0) return false;
+ }
+ auto& res = new_values[eid];
+ res -= corresponding_diff[i];
+ int edge_thres = allowed_edge_length;
+ if (abs(res[0]) > edge_thres || abs(res[1]) > edge_thres) {
+ return false;
+ }
+ if ((abs(res[0]) > 1 && abs(res[1]) != 0) || (abs(res[1]) > 1 && abs(res[0]) != 0))
+ return false;
+ }
+ int prev_area = 0, current_area = 0;
+ for (int f = 0; f < corresponding_faces.size(); ++f) {
+ int area = Area(corresponding_faces[f]);
+ if (area < 0) prev_area += 1;
+ }
+ for (auto& p : new_values) {
+ std::swap(EdgeDiff[p.first], p.second);
+ }
+ for (int f = 0; f < corresponding_faces.size(); ++f) {
+ int area = Area(corresponding_faces[f]);
+ if (area < 0) {
+ current_area += 1;
+ }
+ }
+ if (current_area < prev_area) {
+ return true;
+ }
+ for (auto& p : new_values) {
+ std::swap(EdgeDiff[p.first], p.second);
+ }
+ return false;
+ };
+
+ std::queue<int> flipped;
+ for (int i = 0; i < F2E.size(); ++i) {
+ int area = Area(i);
+ if (area < 0) {
+ flipped.push(i);
+ }
+ }
+
+ bool update = false;
+ int max_len = 1;
+ while (!update && max_len <= 2) {
+ while (!flipped.empty()) {
+ int f = flipped.front();
+ if (Area(f) >= 0) {
+ flipped.pop();
+ continue;
+ }
+ for (int i = 0; i < 3; ++i) {
+ if (CheckShrink(f * 3 + i, max_len) || CheckShrink(E2E[f * 3 + i], max_len)) {
+ update = true;
+ break;
+ }
+ }
+ flipped.pop();
+ }
+ max_len += 1;
+ }
+ if (update) {
+ Hierarchy flip_hierarchy;
+ flip_hierarchy.DownsampleEdgeGraph(mFQ.back(), mF2E.back(), mEdgeDiff.back(),
+ mAllowChanges.back(), -1);
+ flip_hierarchy.FixFlip();
+ flip_hierarchy.UpdateGraphValue(mFQ.back(), mF2E.back(), mEdgeDiff.back());
+ }
+ PropagateEdge();
+}
+
+void Hierarchy::PropagateEdge() {
+ for (int level = mToUpperEdges.size(); level > 0; --level) {
+ auto& EdgeDiff = mEdgeDiff[level];
+ auto& nEdgeDiff = mEdgeDiff[level - 1];
+ auto& FQ = mFQ[level];
+ auto& nFQ = mFQ[level - 1];
+ auto& F2E = mF2E[level - 1];
+ auto& toUpper = mToUpperEdges[level - 1];
+ auto& toUpperFace = mToUpperFaces[level - 1];
+ auto& toUpperOrients = mToUpperOrients[level - 1];
+ for (int i = 0; i < toUpper.size(); ++i) {
+ if (toUpper[i] >= 0) {
+ int orient = (4 - toUpperOrients[i]) % 4;
+ nEdgeDiff[i] = rshift90(EdgeDiff[toUpper[i]], orient);
+ } else {
+ nEdgeDiff[i] = Vector2i(0, 0);
+ }
+ }
+ for (int i = 0; i < toUpperFace.size(); ++i) {
+ if (toUpperFace[i] == -1) continue;
+ Vector3i eid_orient = FQ[toUpperFace[i]];
+ for (int j = 0; j < 3; ++j) {
+ nFQ[i][j] = (eid_orient[j] + toUpperOrients[F2E[i][j]]) % 4;
+ }
+ }
+ }
+}
+
+void Hierarchy::clearConstraints() {
+ int levels = mV.size();
+ if (levels == 0) return;
+ for (int i = 0; i < levels; ++i) {
+ int size = mV[i].cols();
+ mCQ[i].resize(3, size);
+ mCO[i].resize(3, size);
+ mCQw[i].resize(size);
+ mCOw[i].resize(size);
+ mCQw[i].setZero();
+ mCOw[i].setZero();
+ }
+}
+
+void Hierarchy::propagateConstraints() {
+ int levels = mV.size();
+ if (levels == 0) return;
+
+ for (int l = 0; l < levels - 1; ++l) {
+ auto& N = mN[l];
+ auto& N_next = mN[l + 1];
+ auto& V = mV[l];
+ auto& V_next = mV[l + 1];
+ auto& CQ = mCQ[l];
+ auto& CQ_next = mCQ[l + 1];
+ auto& CQw = mCQw[l];
+ auto& CQw_next = mCQw[l + 1];
+ auto& CO = mCO[l];
+ auto& CO_next = mCO[l + 1];
+ auto& COw = mCOw[l];
+ auto& COw_next = mCOw[l + 1];
+ auto& toUpper = mToUpper[l];
+ MatrixXd& S = mS[l];
+
+ for (uint32_t i = 0; i != mV[l + 1].cols(); ++i) {
+ Vector2i upper = toUpper.col(i);
+ Vector3d cq = Vector3d::Zero(), co = Vector3d::Zero();
+ float cqw = 0.0f, cow = 0.0f;
+
+ bool has_cq0 = CQw[upper[0]] != 0;
+ bool has_cq1 = upper[1] != -1 && CQw[upper[1]] != 0;
+ bool has_co0 = COw[upper[0]] != 0;
+ bool has_co1 = upper[1] != -1 && COw[upper[1]] != 0;
+
+ if (has_cq0 && !has_cq1) {
+ cq = CQ.col(upper[0]);
+ cqw = CQw[upper[0]];
+ } else if (has_cq1 && !has_cq0) {
+ cq = CQ.col(upper[1]);
+ cqw = CQw[upper[1]];
+ } else if (has_cq1 && has_cq0) {
+ Vector3d q_i = CQ.col(upper[0]);
+ Vector3d n_i = CQ.col(upper[0]);
+ Vector3d q_j = CQ.col(upper[1]);
+ Vector3d n_j = CQ.col(upper[1]);
+ auto result = compat_orientation_extrinsic_4(q_i, n_i, q_j, n_j);
+ cq = result.first * CQw[upper[0]] + result.second * CQw[upper[1]];
+ cqw = (CQw[upper[0]] + CQw[upper[1]]);
+ }
+ if (cq != Vector3d::Zero()) {
+ Vector3d n = N_next.col(i);
+ cq -= n.dot(cq) * n;
+ if (cq.squaredNorm() > RCPOVERFLOW) cq.normalize();
+ }
+
+ if (has_co0 && !has_co1) {
+ co = CO.col(upper[0]);
+ cow = COw[upper[0]];
+ } else if (has_co1 && !has_co0) {
+ co = CO.col(upper[1]);
+ cow = COw[upper[1]];
+ } else if (has_co1 && has_co0) {
+ double scale_x = mScale;
+ double scale_y = mScale;
+ if (with_scale) {
+ // FIXME
+ // scale_x *= S(0, i);
+ // scale_y *= S(1, i);
+ }
+ double inv_scale_x = 1.0f / scale_x;
+ double inv_scale_y = 1.0f / scale_y;
+
+ double scale_x_1 = mScale;
+ double scale_y_1 = mScale;
+ if (with_scale) {
+ // FIXME
+ // scale_x_1 *= S(0, j);
+ // scale_y_1 *= S(1, j);
+ }
+ double inv_scale_x_1 = 1.0f / scale_x_1;
+ double inv_scale_y_1 = 1.0f / scale_y_1;
+ auto result = compat_position_extrinsic_4(
+ V.col(upper[0]), N.col(upper[0]), CQ.col(upper[0]), CO.col(upper[0]),
+ V.col(upper[1]), N.col(upper[1]), CQ.col(upper[1]), CO.col(upper[1]), scale_x,
+ scale_y, inv_scale_x, inv_scale_y, scale_x_1, scale_y_1, inv_scale_x_1,
+ inv_scale_y_1);
+ cow = COw[upper[0]] + COw[upper[1]];
+ co = (result.first * COw[upper[0]] + result.second * COw[upper[1]]) / cow;
+ }
+ if (co != Vector3d::Zero()) {
+ Vector3d n = N_next.col(i), v = V_next.col(i);
+ co -= n.dot(cq - v) * n;
+ }
+#if 0
+ cqw *= 0.5f;
+ cow *= 0.5f;
+#else
+ if (cqw > 0) cqw = 1;
+ if (cow > 0) cow = 1;
+#endif
+
+ CQw_next[i] = cqw;
+ COw_next[i] = cow;
+ CQ_next.col(i) = cq;
+ CO_next.col(i) = co;
+ }
+ }
+}
+#ifdef WITH_CUDA
+#include <cuda_runtime.h>
+
+void Hierarchy::CopyToDevice() {
+ if (cudaAdj.empty()) {
+ cudaAdj.resize(mAdj.size());
+ cudaAdjOffset.resize(mAdj.size());
+ for (int i = 0; i < mAdj.size(); ++i) {
+ std::vector<int> offset(mAdj[i].size() + 1, 0);
+ for (int j = 0; j < mAdj[i].size(); ++j) {
+ offset[j + 1] = offset[j] + mAdj[i][j].size();
+ }
+ cudaMalloc(&cudaAdjOffset[i], sizeof(int) * (mAdj[i].size() + 1));
+ cudaMemcpy(cudaAdjOffset[i], offset.data(), sizeof(int) * (mAdj[i].size() + 1),
+ cudaMemcpyHostToDevice);
+ // cudaAdjOffset[i] = (int*)malloc(sizeof(int) * (mAdj[i].size() + 1));
+ // memcpy(cudaAdjOffset[i], offset.data(), sizeof(int) * (mAdj[i].size() +
+ // 1));
+
+ cudaMalloc(&cudaAdj[i], sizeof(Link) * offset.back());
+ // cudaAdj[i] = (Link*)malloc(sizeof(Link) * offset.back());
+ std::vector<Link> plainlink(offset.back());
+ for (int j = 0; j < mAdj[i].size(); ++j) {
+ memcpy(plainlink.data() + offset[j], mAdj[i][j].data(),
+ mAdj[i][j].size() * sizeof(Link));
+ }
+ cudaMemcpy(cudaAdj[i], plainlink.data(), plainlink.size() * sizeof(Link),
+ cudaMemcpyHostToDevice);
+ }
+ }
+
+ if (cudaN.empty()) {
+ cudaN.resize(mN.size());
+ for (int i = 0; i < mN.size(); ++i) {
+ cudaMalloc(&cudaN[i], sizeof(glm::dvec3) * mN[i].cols());
+ // cudaN[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mN[i].cols());
+ }
+ }
+ for (int i = 0; i < mN.size(); ++i) {
+ cudaMemcpy(cudaN[i], mN[i].data(), sizeof(glm::dvec3) * mN[i].cols(),
+ cudaMemcpyHostToDevice);
+ // memcpy(cudaN[i], mN[i].data(), sizeof(glm::dvec3) * mN[i].cols());
+ }
+
+ if (cudaV.empty()) {
+ cudaV.resize(mV.size());
+ for (int i = 0; i < mV.size(); ++i) {
+ cudaMalloc(&cudaV[i], sizeof(glm::dvec3) * mV[i].cols());
+ // cudaV[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mV[i].cols());
+ }
+ }
+ for (int i = 0; i < mV.size(); ++i) {
+ cudaMemcpy(cudaV[i], mV[i].data(), sizeof(glm::dvec3) * mV[i].cols(),
+ cudaMemcpyHostToDevice);
+ // memcpy(cudaV[i], mV[i].data(), sizeof(glm::dvec3) * mV[i].cols());
+ }
+
+ if (cudaQ.empty()) {
+ cudaQ.resize(mQ.size());
+ for (int i = 0; i < mQ.size(); ++i) {
+ cudaMalloc(&cudaQ[i], sizeof(glm::dvec3) * mQ[i].cols());
+ // cudaQ[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mQ[i].cols());
+ }
+ }
+ for (int i = 0; i < mQ.size(); ++i) {
+ cudaMemcpy(cudaQ[i], mQ[i].data(), sizeof(glm::dvec3) * mQ[i].cols(),
+ cudaMemcpyHostToDevice);
+ // memcpy(cudaQ[i], mQ[i].data(), sizeof(glm::dvec3) * mQ[i].cols());
+ }
+ if (cudaO.empty()) {
+ cudaO.resize(mO.size());
+ for (int i = 0; i < mO.size(); ++i) {
+ cudaMalloc(&cudaO[i], sizeof(glm::dvec3) * mO[i].cols());
+ // cudaO[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mO[i].cols());
+ }
+ }
+ for (int i = 0; i < mO.size(); ++i) {
+ cudaMemcpy(cudaO[i], mO[i].data(), sizeof(glm::dvec3) * mO[i].cols(),
+ cudaMemcpyHostToDevice);
+ // memcpy(cudaO[i], mO[i].data(), sizeof(glm::dvec3) * mO[i].cols());
+ }
+ if (cudaPhases.empty()) {
+ cudaPhases.resize(mPhases.size());
+ for (int i = 0; i < mPhases.size(); ++i) {
+ cudaPhases[i].resize(mPhases[i].size());
+ for (int j = 0; j < mPhases[i].size(); ++j) {
+ cudaMalloc(&cudaPhases[i][j], sizeof(int) * mPhases[i][j].size());
+ // cudaPhases[i][j] = (int*)malloc(sizeof(int) *
+ // mPhases[i][j].size());
+ }
+ }
+ }
+ for (int i = 0; i < mPhases.size(); ++i) {
+ for (int j = 0; j < mPhases[i].size(); ++j) {
+ cudaMemcpy(cudaPhases[i][j], mPhases[i][j].data(), sizeof(int) * mPhases[i][j].size(),
+ cudaMemcpyHostToDevice);
+ // memcpy(cudaPhases[i][j], mPhases[i][j].data(), sizeof(int) *
+ // mPhases[i][j].size());
+ }
+ }
+ if (cudaToUpper.empty()) {
+ cudaToUpper.resize(mToUpper.size());
+ for (int i = 0; i < mToUpper.size(); ++i) {
+ cudaMalloc(&cudaToUpper[i], mToUpper[i].cols() * sizeof(glm::ivec2));
+ // cudaToUpper[i] = (glm::ivec2*)malloc(mToUpper[i].cols() *
+ // sizeof(glm::ivec2));
+ }
+ }
+ for (int i = 0; i < mToUpper.size(); ++i) {
+ cudaMemcpy(cudaToUpper[i], mToUpper[i].data(), sizeof(glm::ivec2) * mToUpper[i].cols(),
+ cudaMemcpyHostToDevice);
+ // memcpy(cudaToUpper[i], mToUpper[i].data(), sizeof(glm::ivec2) *
+ // mToUpper[i].cols());
+ }
+ cudaDeviceSynchronize();
+}
+
+void Hierarchy::CopyToHost() {}
+
+#endif
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/hierarchy.hpp b/extern/quadriflow/src/hierarchy.hpp
new file mode 100644
index 00000000000..8403038de4f
--- /dev/null
+++ b/extern/quadriflow/src/hierarchy.hpp
@@ -0,0 +1,99 @@
+#ifndef HIERARCHY_H_
+#define HIERARCHY_H_
+
+#ifdef WITH_CUDA
+# include <glm/glm.hpp>
+#endif
+
+#include <map>
+#include <vector>
+#include "adjacent-matrix.hpp"
+#include "config.hpp"
+#include "serialize.hpp"
+#define RCPOVERFLOW 2.93873587705571876e-39f
+
+using namespace Eigen;
+
+namespace qflow {
+
+class Hierarchy {
+ public:
+ Hierarchy();
+ void Initialize(double scale, int with_scale = 0);
+ void DownsampleGraph(const AdjacentMatrix adj, const MatrixXd& V, const MatrixXd& N,
+ const VectorXd& A, MatrixXd& V_p, MatrixXd& N_p, VectorXd& A_p,
+ MatrixXi& to_upper, VectorXi& to_lower, AdjacentMatrix& adj_p);
+ void generate_graph_coloring_deterministic(const AdjacentMatrix& adj, int size,
+ std::vector<std::vector<int>>& phases);
+ void FixFlip();
+ int FixFlipSat(int depth, int threshold = 0);
+ void PushDownwardFlip(int depth);
+ void PropagateEdge();
+ void DownsampleEdgeGraph(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E,
+ std::vector<Vector2i>& edge_diff,
+ std::vector<int>& allow_changes, int level);
+ void UpdateGraphValue(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E,
+ std::vector<Vector2i>& edge_diff);
+
+ enum { MAX_DEPTH = 25 };
+
+ void SaveToFile(FILE* fp);
+ void LoadFromFile(FILE* fp);
+
+ void clearConstraints();
+ void propagateConstraints();
+
+ double mScale;
+ int rng_seed;
+
+ MatrixXi mF; // mF(i, j) i \in [0, 3) ith index in face j
+ VectorXi mE2E; // inverse edge
+ std::vector<AdjacentMatrix> mAdj;
+ std::vector<MatrixXd> mV;
+ std::vector<MatrixXd> mN;
+ std::vector<VectorXd> mA;
+ std::vector<std::vector<std::vector<int>>> mPhases;
+ // parameters
+ std::vector<MatrixXd> mQ;
+ std::vector<MatrixXd> mO;
+ std::vector<VectorXi> mToLower;
+ std::vector<MatrixXi> mToUpper; // mToUpper[h](i, j) \in V; i \in [0, 2); j \in V
+ std::vector<MatrixXd> mS;
+ std::vector<MatrixXd> mK;
+
+ // constraints
+ std::vector<MatrixXd> mCQ;
+ std::vector<MatrixXd> mCO;
+ std::vector<VectorXd> mCQw;
+ std::vector<VectorXd> mCOw;
+
+ int with_scale;
+
+ // upper: fine to coarse
+ std::vector<std::vector<int>> mToUpperFaces; // face correspondance
+ std::vector<std::vector<int>> mSing;
+ std::vector<std::vector<int>> mToUpperEdges; // edge correspondance
+ std::vector<std::vector<int>> mToUpperOrients; // rotation of edges from fine to coarse
+ std::vector<std::vector<Vector3i>> mFQ; // face_edgeOrients
+ std::vector<std::vector<Vector3i>> mF2E; // face_edgeIds
+ std::vector<std::vector<Vector2i>> mE2F; // undirect edges to face ID
+ std::vector<std::vector<int> > mAllowChanges;
+ std::vector<std::vector<Vector2i>> mEdgeDiff; // face_edgeDiff
+
+#ifdef WITH_CUDA
+ std::vector<Link*> cudaAdj;
+ std::vector<int*> cudaAdjOffset;
+ std::vector<glm::dvec3*> cudaN;
+ std::vector<glm::dvec3*> cudaV;
+ std::vector<glm::dvec3*> cudaQ;
+ std::vector<glm::dvec3*> cudaO;
+ std::vector<std::vector<int*>> cudaPhases;
+ std::vector<glm::ivec2*> cudaToUpper;
+ void CopyToDevice();
+ void CopyToHost();
+#endif
+};
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/loader.cpp b/extern/quadriflow/src/loader.cpp
new file mode 100644
index 00000000000..aa27066e6e4
--- /dev/null
+++ b/extern/quadriflow/src/loader.cpp
@@ -0,0 +1,159 @@
+//
+// loader.cpp
+// Loop
+//
+// Created by Jingwei on 10/22/17.
+// Copyright © 2017 Jingwei. All rights reserved.
+//
+
+#include "loader.hpp"
+
+#include <fstream>
+#include <unordered_map>
+
+namespace qflow {
+
+inline std::vector<std::string> &str_tokenize(const std::string &s, char delim, std::vector<std::string> &elems, bool include_empty = false) {
+ std::stringstream ss(s);
+ std::string item;
+ while (std::getline(ss, item, delim))
+ if (!item.empty() || include_empty)
+ elems.push_back(item);
+ return elems;
+}
+
+inline std::vector<std::string> str_tokenize(const std::string &s, char delim, bool include_empty) {
+ std::vector<std::string> elems;
+ str_tokenize(s, delim, elems, include_empty);
+ return elems;
+}
+
+inline uint32_t str_to_uint32_t(const std::string &str) {
+ char *end_ptr = nullptr;
+ uint32_t result = (uint32_t)strtoul(str.c_str(), &end_ptr, 10);
+ if (*end_ptr != '\0')
+ throw std::runtime_error("Could not parse unsigned integer \"" + str + "\"");
+ return result;
+}
+
+void load(const char* filename, MatrixXd& V, MatrixXi& F)
+{
+ /// Vertex indices used by the OBJ format
+ struct obj_vertex {
+ uint32_t p = (uint32_t)-1;
+ uint32_t n = (uint32_t)-1;
+ uint32_t uv = (uint32_t)-1;
+
+ inline obj_vertex() { }
+
+ inline obj_vertex(const std::string &string) {
+ std::vector<std::string> tokens = str_tokenize(string, '/', true);
+
+ if (tokens.size() < 1 || tokens.size() > 3)
+ throw std::runtime_error("Invalid vertex data: \"" + string + "\"");
+
+ p = str_to_uint32_t(tokens[0]);
+
+#if 0
+ if (tokens.size() >= 2 && !tokens[1].empty())
+ uv = str_to_uint32_t(tokens[1]);
+
+ if (tokens.size() >= 3 && !tokens[2].empty())
+ n = str_to_uint32_t(tokens[2]);
+#endif
+ }
+
+ inline bool operator==(const obj_vertex &v) const {
+ return v.p == p && v.n == n && v.uv == uv;
+ }
+ };
+
+ /// Hash function for obj_vertex
+ struct obj_vertexHash : std::unary_function<obj_vertex, size_t> {
+ std::size_t operator()(const obj_vertex &v) const {
+ size_t hash = std::hash<uint32_t>()(v.p);
+ hash = hash * 37 + std::hash<uint32_t>()(v.uv);
+ hash = hash * 37 + std::hash<uint32_t>()(v.n);
+ return hash;
+ }
+ };
+
+ typedef std::unordered_map<obj_vertex, uint32_t, obj_vertexHash> VertexMap;
+
+ std::ifstream is(filename);
+
+ std::vector<Vector3d> positions;
+ //std::vector<Vector2d> texcoords;
+ //std::vector<Vector3d> normals;
+ std::vector<uint32_t> indices;
+ std::vector<obj_vertex> vertices;
+ VertexMap vertexMap;
+
+ std::string line_str;
+ while (std::getline(is, line_str)) {
+ std::istringstream line(line_str);
+
+ std::string prefix;
+ line >> prefix;
+
+ if (prefix == "v") {
+ Vector3d p;
+ line >> p.x() >> p.y() >> p.z();
+ positions.push_back(p);
+ }
+ else if (prefix == "vt") {
+ /*
+ Vector2d tc;
+ line >> tc.x() >> tc.y();
+ texcoords.push_back(tc);
+ */
+ }
+ else if (prefix == "vn") {
+ /*
+ Vector3d n;
+ line >> n.x() >> n.y() >> n.z();
+ normals.push_back(n);
+ */
+ }
+ else if (prefix == "f") {
+ std::string v1, v2, v3, v4;
+ line >> v1 >> v2 >> v3 >> v4;
+ obj_vertex tri[6];
+ int nVertices = 3;
+
+ tri[0] = obj_vertex(v1);
+ tri[1] = obj_vertex(v2);
+ tri[2] = obj_vertex(v3);
+
+ if (!v4.empty()) {
+ /* This is a quad, split into two triangles */
+ tri[3] = obj_vertex(v4);
+ tri[4] = tri[0];
+ tri[5] = tri[2];
+ nVertices = 6;
+ }
+ /* Convert to an indexed vertex list */
+ for (int i = 0; i<nVertices; ++i) {
+ const obj_vertex &v = tri[i];
+ VertexMap::const_iterator it = vertexMap.find(v);
+ if (it == vertexMap.end()) {
+ vertexMap[v] = (uint32_t)vertices.size();
+ indices.push_back((uint32_t)vertices.size());
+ vertices.push_back(v);
+ }
+ else {
+ indices.push_back(it->second);
+ }
+ }
+ }
+ }
+
+ F.resize(3, indices.size() / 3);
+ memcpy(F.data(), indices.data(), sizeof(uint32_t)*indices.size());
+
+ V.resize(3, vertices.size());
+ for (uint32_t i = 0; i<vertices.size(); ++i)
+ V.col(i) = positions.at(vertices[i].p - 1);
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/loader.hpp b/extern/quadriflow/src/loader.hpp
new file mode 100644
index 00000000000..33b5e55b5e5
--- /dev/null
+++ b/extern/quadriflow/src/loader.hpp
@@ -0,0 +1,15 @@
+#ifndef __LOADER_H
+#define __LOADER_H
+
+#include <Eigen/Core>
+#include <vector>
+
+namespace qflow {
+
+using namespace Eigen;
+
+void load(const char* filename, MatrixXd& V, MatrixXi& F);
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/localsat.cpp b/extern/quadriflow/src/localsat.cpp
new file mode 100644
index 00000000000..1ab665175da
--- /dev/null
+++ b/extern/quadriflow/src/localsat.cpp
@@ -0,0 +1,295 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include "localsat.hpp"
+#include "config.hpp"
+#include "dedge.hpp"
+#include "field-math.hpp"
+
+#include <Eigen/Core>
+
+#include <deque>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace qflow {
+
+const int max_depth = 0;
+
+using namespace Eigen;
+
+SolverStatus RunCNF(const std::string &fin_name, int n_variable, int timeout,
+ const std::vector<std::vector<int>> &sat_clause, std::vector<int> &value) {
+ int n_sat_variable = 3 * n_variable;
+ auto fout_name = fin_name + ".result.txt";
+
+ FILE *fout = fopen(fin_name.c_str(), "w");
+ fprintf(fout, "p cnf %d %d\n", n_sat_variable, (int)sat_clause.size());
+ for (auto &c : sat_clause) {
+ for (auto e : c) fprintf(fout, "%d ", e);
+ fputs("0\n", fout);
+ }
+ fclose(fout);
+
+ char cmd[100];
+ snprintf(cmd, 99, "rm %s > /dev/null 2>&1", fout_name.c_str());
+ system(cmd);
+ snprintf(cmd, 99, "timeout %d minisat %s %s > /dev/null 2>&1", timeout, fin_name.c_str(),
+ fout_name.c_str());
+ int exit_code = system(cmd);
+
+ FILE *fin = fopen(fout_name.c_str(), "r");
+ char buf[16] = {0};
+ fscanf(fin, "%15s", buf);
+ lprintf(" MiniSAT:");
+ if (strcmp(buf, "SAT") != 0) {
+ fclose(fin);
+
+ if (exit_code == 124) {
+ lprintf(" Timeout! ");
+ return SolverStatus::Timeout;
+ }
+ lprintf(" Unsatisfiable! ");
+ return SolverStatus::Unsat;
+ };
+
+ lprintf(" Satisfiable! ");
+ for (int i = 0; i < n_variable; ++i) {
+ int sign[3];
+ fscanf(fin, "%d %d %d", sign + 0, sign + 1, sign + 2);
+
+ int nvalue = -2;
+ for (int j = 0; j < 3; ++j) {
+ assert(abs(sign[j]) == 3 * i + j + 1);
+ if ((sign[j] > 0) == (value[i] != j - 1)) {
+ assert(nvalue == -2);
+ nvalue = j - 1;
+ }
+ }
+ value[i] = nvalue;
+ }
+ fclose(fin);
+
+ return SolverStatus::Sat;
+}
+
+SolverStatus SolveSatProblem(int n_variable, std::vector<int> &value,
+ const std::vector<bool> flexible, // NOQA
+ const std::vector<Vector3i> &variable_eq,
+ const std::vector<Vector3i> &constant_eq,
+ const std::vector<Vector4i> &variable_ge,
+ const std::vector<Vector2i> &constant_ge,
+ int timeout) {
+ for (int v : value) assert(-1 <= v && v <= +1);
+
+ auto VAR = [&](int i, int v) {
+ int index = 1 + 3 * i + v + 1;
+ // We initialize the SAT problem by setting all the variable to false.
+ // This is because minisat by default will try false first.
+ if (v == value[i]) index = -index;
+ return index;
+ };
+
+ int n_flexible = 0;
+ std::vector<std::vector<int>> sat_clause;
+ std::vector<bool> sat_ishard;
+
+ auto add_clause = [&](const std::vector<int> &clause, bool hard) {
+ sat_clause.push_back(clause);
+ sat_ishard.push_back(hard);
+ };
+
+ for (int i = 0; i < n_variable; ++i) {
+ add_clause({-VAR(i, -1), -VAR(i, 0)}, true);
+ add_clause({-VAR(i, +1), -VAR(i, 0)}, true);
+ add_clause({-VAR(i, -1), -VAR(i, +1)}, true);
+ add_clause({VAR(i, -1), VAR(i, 0), VAR(i, +1)}, true);
+ if (!flexible[i]) {
+ add_clause({VAR(i, value[i])}, true);
+ } else {
+ ++n_flexible;
+ }
+ }
+
+ for (int i = 0; i < (int)variable_eq.size(); ++i) {
+ auto &var = variable_eq[i];
+ auto &cst = constant_eq[i];
+ for (int v0 = -1; v0 <= 1; ++v0)
+ for (int v1 = -1; v1 <= 1; ++v1)
+ for (int v2 = -1; v2 <= 1; ++v2)
+ if (cst[0] * v0 + cst[1] * v1 + cst[2] * v2 != 0) {
+ add_clause({-VAR(var[0], v0), -VAR(var[1], v1), -VAR(var[2], v2)}, true);
+ }
+ }
+
+ for (int i = 0; i < (int)variable_ge.size(); ++i) {
+ auto &var = variable_ge[i];
+ auto &cst = constant_ge[i];
+ for (int v0 = -1; v0 <= 1; ++v0)
+ for (int v1 = -1; v1 <= 1; ++v1)
+ for (int v2 = -1; v2 <= 1; ++v2)
+ for (int v3 = -1; v3 <= 1; ++v3)
+ if (cst[0] * v0 * v1 - cst[1] * v2 * v3 < 0) {
+ add_clause({-VAR(var[0], v0), -VAR(var[1], v1), -VAR(var[2], v2),
+ -VAR(var[3], v3)},
+ false);
+ }
+ }
+
+ int nflip_before = 0, nflip_after = 0;
+ for (int i = 0; i < (int)variable_ge.size(); ++i) {
+ auto &var = variable_ge[i];
+ auto &cst = constant_ge[i];
+ if (value[var[0]] * value[var[1]] * cst[0] - value[var[2]] * value[var[3]] * cst[1] < 0)
+ nflip_before++;
+ }
+
+ lprintf(" [SAT] nvar: %6d nflip: %3d ", n_flexible * 2, nflip_before);
+ auto rcnf = RunCNF("test.out", n_variable, timeout, sat_clause, value);
+
+ for (int i = 0; i < (int)variable_eq.size(); ++i) {
+ auto &var = variable_eq[i];
+ auto &cst = constant_eq[i];
+ assert(cst[0] * value[var[0]] + cst[1] * value[var[1]] + cst[2] * value[var[2]] == 0);
+ }
+ for (int i = 0; i < (int)variable_ge.size(); ++i) {
+ auto &var = variable_ge[i];
+ auto &cst = constant_ge[i];
+ int area = value[var[0]] * value[var[1]] * cst[0] - value[var[2]] * value[var[3]] * cst[1];
+ if (area < 0) ++nflip_after;
+ }
+ lprintf("nflip: %3d\n", nflip_after);
+ return rcnf;
+}
+
+void ExportLocalSat(std::vector<Vector2i> &edge_diff, const std::vector<Vector3i> &face_edgeIds,
+ const std::vector<Vector3i> &face_edgeOrients, const MatrixXi &F,
+ const VectorXi &V2E, const VectorXi &E2E) {
+ int flip_count = 0;
+ int flip_count1 = 0;
+
+ std::vector<int> value(2 * edge_diff.size());
+ for (int i = 0; i < (int)edge_diff.size(); ++i) {
+ value[2 * i + 0] = edge_diff[i][0];
+ value[2 * i + 1] = edge_diff[i][1];
+ }
+
+ std::deque<std::pair<int, int>> Q;
+ std::vector<bool> mark_vertex(V2E.size(), false);
+
+ assert(F.cols() == (int)face_edgeIds.size());
+ std::vector<Vector3i> variable_eq(face_edgeIds.size() * 2);
+ std::vector<Vector3i> constant_eq(face_edgeIds.size() * 2);
+ std::vector<Vector4i> variable_ge(face_edgeIds.size());
+ std::vector<Vector2i> constant_ge(face_edgeIds.size());
+
+ VectorXd face_area(F.cols());
+
+ for (int i = 0; i < (int)face_edgeIds.size(); ++i) {
+ Vector2i diff[3];
+ Vector2i var[3];
+ Vector2i cst[3];
+ for (int j = 0; j < 3; ++j) {
+ int edgeid = face_edgeIds[i][j];
+ diff[j] = rshift90(edge_diff[edgeid], face_edgeOrients[i][j]);
+ var[j] = rshift90(Vector2i(edgeid * 2 + 1, edgeid * 2 + 2), face_edgeOrients[i][j]);
+ cst[j] = var[j].array().sign();
+ var[j] = var[j].array().abs() - 1;
+ }
+
+ assert(diff[0] + diff[1] + diff[2] == Vector2i::Zero());
+ variable_eq[2 * i + 0] = Vector3i(var[0][0], var[1][0], var[2][0]);
+ constant_eq[2 * i + 0] = Vector3i(cst[0][0], cst[1][0], cst[2][0]);
+ variable_eq[2 * i + 1] = Vector3i(var[0][1], var[1][1], var[2][1]);
+ constant_eq[2 * i + 1] = Vector3i(cst[0][1], cst[1][1], cst[2][1]);
+
+ face_area[i] = diff[0][0] * diff[1][1] - diff[0][1] * diff[1][0];
+ if (face_area[i] < 0) {
+ printf("[SAT] Face %d's area < 0\n", i);
+ for (int j = 0; j < 3; ++j) {
+ int v = F(j, i);
+ if (mark_vertex[v]) continue;
+ Q.push_back(std::make_pair(v, 0));
+ mark_vertex[v] = true;
+ }
+ flip_count += 1;
+ }
+ variable_ge[i] = Vector4i(var[0][0], var[1][1], var[0][1], var[1][0]);
+ constant_ge[i] = Vector2i(cst[0][0] * cst[1][1], cst[0][1] * cst[1][0]);
+ }
+ for (int i = 0; i < (int)variable_eq.size(); ++i) {
+ auto &var = variable_eq[i];
+ auto &cst = constant_eq[i];
+ assert((0 <= var.array()).all());
+ assert((var.array() < value.size()).all());
+ assert(cst[0] * value[var[0]] + cst[1] * value[var[1]] + cst[2] * value[var[2]] == 0);
+ }
+
+ for (int i = 0; i < (int)variable_ge.size(); ++i) {
+ auto &var = variable_ge[i];
+ auto &cst = constant_ge[i];
+ assert((0 <= variable_ge[i].array()).all());
+ assert((variable_ge[i].array() < value.size()).all());
+ if (value[var[0]] * value[var[1]] * cst[0] - value[var[2]] * value[var[3]] * cst[1] < 0) {
+ assert(face_area[i] < 0);
+ flip_count1++;
+ }
+ }
+ assert(flip_count == flip_count1);
+
+ // BFS
+ printf("[SAT] Start BFS: Q.size() = %d\n", (int)Q.size());
+
+ int mark_count = Q.size();
+ while (!Q.empty()) {
+ int vertex = Q.front().first;
+ int depth = Q.front().second;
+ Q.pop_front();
+ mark_count++;
+ int e0 = V2E(vertex);
+
+ for (int e = e0;;) {
+ int v = F((e + 1) % 3, e / 3);
+ if (!mark_vertex[v]) {
+ int undirected_edge_id = face_edgeIds[e / 3][e % 3];
+ int undirected_edge_length = edge_diff[undirected_edge_id].array().abs().sum() > 0;
+ int ndepth = depth + undirected_edge_length;
+ if (ndepth <= max_depth) {
+ if (undirected_edge_length == 0)
+ Q.push_front(std::make_pair(v, ndepth));
+ else
+ Q.push_back(std::make_pair(v, ndepth));
+ mark_vertex[v] = true;
+ }
+ }
+ e = dedge_next_3(E2E(e));
+ if (e == e0) break;
+ }
+ }
+ printf("[SAT] Mark %d vertices out of %d\n", mark_count, (int)V2E.size());
+
+ std::vector<bool> flexible(value.size(), false);
+ for (int i = 0; i < (int)face_edgeIds.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int edgeid = face_edgeIds[i][j];
+ if (mark_vertex[F(j, i)] || mark_vertex[F((j + 1) % 3, i)]) {
+ flexible[edgeid * 2 + 0] = true;
+ flexible[edgeid * 2 + 1] = true;
+ } else {
+ assert(face_area[i] >= 0);
+ }
+ }
+ }
+
+ SolveSatProblem(value.size(), value, flexible, variable_eq, constant_eq, variable_ge,
+ constant_ge);
+
+ for (int i = 0; i < edge_diff.size(); ++i) {
+ edge_diff[i][0] = value[2 * i + 0];
+ edge_diff[i][1] = value[2 * i + 1];
+ }
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/localsat.hpp b/extern/quadriflow/src/localsat.hpp
new file mode 100644
index 00000000000..af952320ebb
--- /dev/null
+++ b/extern/quadriflow/src/localsat.hpp
@@ -0,0 +1,31 @@
+#ifndef __LOCAL_SAT_H
+#define __LOCAL_SAT_H
+
+#include <Eigen/Core>
+#include <vector>
+
+namespace qflow {
+
+using namespace Eigen;
+
+enum class SolverStatus {
+ Sat,
+ Unsat,
+ Timeout,
+};
+
+SolverStatus SolveSatProblem(int n_variable, std::vector<int> &value,
+ const std::vector<bool> flexible, // NOQA
+ const std::vector<Vector3i> &variable_eq,
+ const std::vector<Vector3i> &constant_eq,
+ const std::vector<Vector4i> &variable_ge,
+ const std::vector<Vector2i> &constant_ge,
+ int timeout = 8);
+
+void ExportLocalSat(std::vector<Vector2i> &edge_diff, const std::vector<Vector3i> &face_edgeIds,
+ const std::vector<Vector3i> &face_edgeOrients, const MatrixXi &F,
+ const VectorXi &V2E, const VectorXi &E2E);
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/main.cpp b/extern/quadriflow/src/main.cpp
new file mode 100644
index 00000000000..18bc4063c42
--- /dev/null
+++ b/extern/quadriflow/src/main.cpp
@@ -0,0 +1,127 @@
+#include "config.hpp"
+#include "field-math.hpp"
+#include "optimizer.hpp"
+#include "parametrizer.hpp"
+#include <stdlib.h>
+
+#ifdef WITH_CUDA
+#include <cuda_runtime.h>
+#endif
+
+using namespace qflow;
+
+Parametrizer field;
+
+int main(int argc, char** argv) {
+ setbuf(stdout, NULL);
+
+#ifdef WITH_CUDA
+ cudaFree(0);
+#endif
+ int t1, t2;
+ std::string input_obj, output_obj;
+ int faces = -1;
+ for (int i = 0; i < argc; ++i) {
+ if (strcmp(argv[i], "-f") == 0) {
+ sscanf(argv[i + 1], "%d", &faces);
+ } else if (strcmp(argv[i], "-i") == 0) {
+ input_obj = argv[i + 1];
+ } else if (strcmp(argv[i], "-o") == 0) {
+ output_obj = argv[i + 1];
+ } else if (strcmp(argv[i], "-sharp") == 0) {
+ field.flag_preserve_sharp = 1;
+ } else if (strcmp(argv[i], "-boundary") == 0) {
+ field.flag_preserve_boundary = 1;
+ } else if (strcmp(argv[i], "-adaptive") == 0) {
+ field.flag_adaptive_scale = 1;
+ } else if (strcmp(argv[i], "-mcf") == 0) {
+ field.flag_minimum_cost_flow = 1;
+ } else if (strcmp(argv[i], "-sat") == 0) {
+ field.flag_aggresive_sat = 1;
+ } else if (strcmp(argv[i], "-seed") == 0) {
+ field.hierarchy.rng_seed = atoi(argv[i + 1]);
+ }
+ }
+ printf("%d %s %s\n", faces, input_obj.c_str(), output_obj.c_str());
+ if (input_obj.size() >= 1) {
+ field.Load(input_obj.c_str());
+ } else {
+ assert(0);
+ // field.Load((std::string(DATA_PATH) + "/fertility.obj").c_str());
+ }
+
+ printf("Initialize...\n");
+ t1 = GetCurrentTime64();
+ field.Initialize(faces);
+ t2 = GetCurrentTime64();
+ printf("Use %lf seconds\n", (t2 - t1) * 1e-3);
+
+ if (field.flag_preserve_boundary) {
+ printf("Add boundary constrains...\n");
+ Hierarchy& mRes = field.hierarchy;
+ mRes.clearConstraints();
+ for (uint32_t i = 0; i < 3 * mRes.mF.cols(); ++i) {
+ if (mRes.mE2E[i] == -1) {
+ uint32_t i0 = mRes.mF(i % 3, i / 3);
+ uint32_t i1 = mRes.mF((i + 1) % 3, i / 3);
+ Vector3d p0 = mRes.mV[0].col(i0), p1 = mRes.mV[0].col(i1);
+ Vector3d edge = p1 - p0;
+ if (edge.squaredNorm() > 0) {
+ edge.normalize();
+ mRes.mCO[0].col(i0) = p0;
+ mRes.mCO[0].col(i1) = p1;
+ mRes.mCQ[0].col(i0) = mRes.mCQ[0].col(i1) = edge;
+ mRes.mCQw[0][i0] = mRes.mCQw[0][i1] = mRes.mCOw[0][i0] = mRes.mCOw[0][i1] =
+ 1.0;
+ }
+ }
+ }
+ mRes.propagateConstraints();
+ }
+
+ printf("Solve Orientation Field...\n");
+ t1 = GetCurrentTime64();
+
+ Optimizer::optimize_orientations(field.hierarchy);
+ field.ComputeOrientationSingularities();
+ t2 = GetCurrentTime64();
+ printf("Use %lf seconds\n", (t2 - t1) * 1e-3);
+
+ if (field.flag_adaptive_scale == 1) {
+ printf("Estimate Slop...\n");
+ t1 = GetCurrentTime64();
+ field.EstimateSlope();
+ t2 = GetCurrentTime64();
+ printf("Use %lf seconds\n", (t2 - t1) * 1e-3);
+ }
+ printf("Solve for scale...\n");
+ t1 = GetCurrentTime64();
+ Optimizer::optimize_scale(field.hierarchy, field.rho, field.flag_adaptive_scale);
+ field.flag_adaptive_scale = 1;
+ t2 = GetCurrentTime64();
+ printf("Use %lf seconds\n", (t2 - t1) * 1e-3);
+
+ printf("Solve for position field...\n");
+ t1 = GetCurrentTime64();
+ Optimizer::optimize_positions(field.hierarchy, field.flag_adaptive_scale);
+
+ field.ComputePositionSingularities();
+ t2 = GetCurrentTime64();
+ printf("Use %lf seconds\n", (t2 - t1) * 1e-3);
+ t1 = GetCurrentTime64();
+ printf("Solve index map...\n");
+ field.ComputeIndexMap();
+ t2 = GetCurrentTime64();
+ printf("Indexmap Use %lf seconds\n", (t2 - t1) * 1e-3);
+ printf("Writing the file...\n");
+
+ if (output_obj.size() < 1) {
+ assert(0);
+ // field.OutputMesh((std::string(DATA_PATH) + "/result.obj").c_str());
+ } else {
+ field.OutputMesh(output_obj.c_str());
+ }
+ printf("finish...\n");
+ // field.LoopFace(2);
+ return 0;
+}
diff --git a/extern/quadriflow/src/merge-vertex.cpp b/extern/quadriflow/src/merge-vertex.cpp
new file mode 100644
index 00000000000..4c7b0a2bb9b
--- /dev/null
+++ b/extern/quadriflow/src/merge-vertex.cpp
@@ -0,0 +1,44 @@
+#include "merge-vertex.hpp"
+
+#include "compare-key.hpp"
+
+#include <map>
+#include <vector>
+
+namespace qflow {
+
+void merge_close(MatrixXd& V, MatrixXi& F, double threshold)
+{
+ std::map<Key3f, int> vid_maps;
+ std::vector<int> vid_compress(V.cols());
+ for (int i = 0; i < V.cols(); ++i) {
+ Key3f key(V(0, i), V(1, i), V(2, i), threshold);
+ if (vid_maps.count(key)) {
+ vid_compress[i] = vid_maps[key];
+ }
+ else {
+ V.col(vid_maps.size()) = V.col(i);
+ vid_compress[i] = vid_maps.size();
+ vid_maps[key] = vid_compress[i];
+ }
+ }
+ printf("Compress Vertex from %d to %d...\n", (int)V.cols(), (int)vid_maps.size());
+ MatrixXd newV(3, vid_maps.size());
+ memcpy(newV.data(), V.data(), sizeof(double) * 3 * vid_maps.size());
+ V = std::move(newV);
+ int f_num = 0;
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ F(j, f_num) = vid_compress[F(j, i)];
+ }
+ if (F(0, f_num) != F(1, f_num) && F(0, f_num) != F(2, f_num) && F(1, f_num) != F(2, f_num)) {
+ f_num++;
+ }
+ }
+ printf("Compress Face from %d to %d...\n", (int)F.cols(), f_num);
+ MatrixXi newF(3, f_num);
+ memcpy(newF.data(), F.data(), sizeof(int) * 3 * f_num);
+ F = std::move(newF);
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/merge-vertex.hpp b/extern/quadriflow/src/merge-vertex.hpp
new file mode 100644
index 00000000000..8bcea3d8e4d
--- /dev/null
+++ b/extern/quadriflow/src/merge-vertex.hpp
@@ -0,0 +1,14 @@
+#ifndef MERGE_VERTEX_H_
+#define MERGE_VERTEX_H_
+
+#include <Eigen/Core>
+
+namespace qflow {
+
+using namespace Eigen;
+
+void merge_close(MatrixXd& V, MatrixXi& F, double threshold);
+
+} // namespace qflow
+
+#endif \ No newline at end of file
diff --git a/extern/quadriflow/src/optimizer.cpp b/extern/quadriflow/src/optimizer.cpp
new file mode 100644
index 00000000000..1c59ad0f70c
--- /dev/null
+++ b/extern/quadriflow/src/optimizer.cpp
@@ -0,0 +1,1419 @@
+#include "optimizer.hpp"
+
+#include <Eigen/Sparse>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <queue>
+#include <unordered_map>
+
+#include "config.hpp"
+#include "field-math.hpp"
+#include "flow.hpp"
+#include "parametrizer.hpp"
+
+namespace qflow {
+
+#ifdef WITH_CUDA
+# include <cuda_runtime.h>
+#endif
+
+#ifndef EIGEN_MPL2_ONLY
+template<class T>
+using LinearSolver = Eigen::SimplicialLLT<T>;
+#else
+template<class T>
+using LinearSolver = Eigen::SparseLU<T>;
+#endif
+
+Optimizer::Optimizer() {}
+
+void Optimizer::optimize_orientations(Hierarchy& mRes) {
+#ifdef WITH_CUDA
+ optimize_orientations_cuda(mRes);
+ printf("%s\n", cudaGetErrorString(cudaDeviceSynchronize()));
+ cudaMemcpy(mRes.mQ[0].data(), mRes.cudaQ[0], sizeof(glm::dvec3) * mRes.mQ[0].cols(),
+ cudaMemcpyDeviceToHost);
+
+#else
+
+ int levelIterations = 6;
+ for (int level = mRes.mN.size() - 1; level >= 0; --level) {
+ AdjacentMatrix& adj = mRes.mAdj[level];
+ const MatrixXd& N = mRes.mN[level];
+ const MatrixXd& CQ = mRes.mCQ[level];
+ const VectorXd& CQw = mRes.mCQw[level];
+ MatrixXd& Q = mRes.mQ[level];
+ auto& phases = mRes.mPhases[level];
+ for (int iter = 0; iter < levelIterations; ++iter) {
+ for (int phase = 0; phase < phases.size(); ++phase) {
+ auto& p = phases[phase];
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int pi = 0; pi < p.size(); ++pi) {
+ int i = p[pi];
+ const Vector3d n_i = N.col(i);
+ double weight_sum = 0.0f;
+ Vector3d sum = Q.col(i);
+ for (auto& link : adj[i]) {
+ const int j = link.id;
+ const double weight = link.weight;
+ if (weight == 0) continue;
+ const Vector3d n_j = N.col(j);
+ Vector3d q_j = Q.col(j);
+ std::pair<Vector3d, Vector3d> value =
+ compat_orientation_extrinsic_4(sum, n_i, q_j, n_j);
+ sum = value.first * weight_sum + value.second * weight;
+ sum -= n_i * n_i.dot(sum);
+ weight_sum += weight;
+ double norm = sum.norm();
+ if (norm > RCPOVERFLOW) sum /= norm;
+ }
+
+ if (CQw.size() > 0) {
+ float cw = CQw[i];
+ if (cw != 0) {
+ std::pair<Vector3d, Vector3d> value =
+ compat_orientation_extrinsic_4(sum, n_i, CQ.col(i), n_i);
+ sum = value.first * (1 - cw) + value.second * cw;
+ sum -= n_i * n_i.dot(sum);
+
+ float norm = sum.norm();
+ if (norm > RCPOVERFLOW) sum /= norm;
+ }
+ }
+
+ if (weight_sum > 0) {
+ Q.col(i) = sum;
+ }
+ }
+ }
+ }
+ if (level > 0) {
+ const MatrixXd& srcField = mRes.mQ[level];
+ const MatrixXi& toUpper = mRes.mToUpper[level - 1];
+ MatrixXd& destField = mRes.mQ[level - 1];
+ const MatrixXd& N = mRes.mN[level - 1];
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < srcField.cols(); ++i) {
+ for (int k = 0; k < 2; ++k) {
+ int dest = toUpper(k, i);
+ if (dest == -1) continue;
+ Vector3d q = srcField.col(i), n = N.col(dest);
+ destField.col(dest) = q - n * n.dot(q);
+ }
+ }
+ }
+ }
+
+ for (int l = 0; l < mRes.mN.size() - 1; ++l) {
+ const MatrixXd& N = mRes.mN[l];
+ const MatrixXd& N_next = mRes.mN[l + 1];
+ const MatrixXd& Q = mRes.mQ[l];
+ MatrixXd& Q_next = mRes.mQ[l + 1];
+ auto& toUpper = mRes.mToUpper[l];
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < toUpper.cols(); ++i) {
+ Vector2i upper = toUpper.col(i);
+ Vector3d q0 = Q.col(upper[0]);
+ Vector3d n0 = N.col(upper[0]);
+ Vector3d q;
+
+ if (upper[1] != -1) {
+ Vector3d q1 = Q.col(upper[1]);
+ Vector3d n1 = N.col(upper[1]);
+ auto result = compat_orientation_extrinsic_4(q0, n0, q1, n1);
+ q = result.first + result.second;
+ } else {
+ q = q0;
+ }
+ Vector3d n = N_next.col(i);
+ q -= n.dot(q) * n;
+ if (q.squaredNorm() > RCPOVERFLOW) q.normalize();
+
+ Q_next.col(i) = q;
+ }
+ }
+
+#endif
+}
+
+void Optimizer::optimize_scale(Hierarchy& mRes, VectorXd& rho, int adaptive) {
+ const MatrixXd& N = mRes.mN[0];
+ MatrixXd& Q = mRes.mQ[0];
+ MatrixXd& V = mRes.mV[0];
+ MatrixXd& S = mRes.mS[0];
+ MatrixXd& K = mRes.mK[0];
+ MatrixXi& F = mRes.mF;
+
+ if (adaptive) {
+ std::vector<Eigen::Triplet<double>> lhsTriplets;
+
+ lhsTriplets.reserve(F.cols() * 6);
+ for (int i = 0; i < V.cols(); ++i) {
+ for (int j = 0; j < 2; ++j) {
+ S(j, i) = 1.0;
+ double sc1 = std::max(0.75 * S(j, i), rho[i] * 1.0 / mRes.mScale);
+ S(j, i) = std::min(S(j, i), sc1);
+ }
+ }
+
+ std::vector<std::map<int, double>> entries(V.cols() * 2);
+ double lambda = 1;
+ for (int i = 0; i < entries.size(); ++i) {
+ entries[i][i] = lambda;
+ }
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int v1 = F(j, i);
+ int v2 = F((j + 1) % 3, i);
+ Vector3d diff = V.col(v2) - V.col(v1);
+ Vector3d q_1 = Q.col(v1);
+ Vector3d q_2 = Q.col(v2);
+ Vector3d n_1 = N.col(v1);
+ Vector3d n_2 = N.col(v2);
+ Vector3d q_1_y = n_1.cross(q_1);
+ auto index = compat_orientation_extrinsic_index_4(q_1, n_1, q_2, n_2);
+ int v1_x = v1 * 2, v1_y = v1 * 2 + 1, v2_x = v2 * 2, v2_y = v2 * 2 + 1;
+
+ double dx = diff.dot(q_1);
+ double dy = diff.dot(q_1_y);
+
+ double kx_g = K(0, v1);
+ double ky_g = K(1, v1);
+
+ if (index.first % 2 != index.second % 2) {
+ std::swap(v2_x, v2_y);
+ }
+ double scale_x = (fmin(fmax(1 + kx_g * dy, 0.3), 3));
+ double scale_y = (fmin(fmax(1 + ky_g * dx, 0.3), 3));
+ // (v2_x - scale_x * v1_x)^2 = 0
+ // x^2 - 2s xy + s^2 y^2
+ entries[v2_x][v2_x] += 1;
+ entries[v1_x][v1_x] += scale_x * scale_x;
+ entries[v2_y][v2_y] += 1;
+ entries[v1_y][v1_y] += scale_y * scale_y;
+ auto it = entries[v1_x].find(v2_x);
+ if (it == entries[v1_x].end()) {
+ entries[v1_x][v2_x] = -scale_x;
+ entries[v2_x][v1_x] = -scale_x;
+ entries[v1_y][v2_y] = -scale_y;
+ entries[v2_y][v1_y] = -scale_y;
+ } else {
+ it->second -= scale_x;
+ entries[v2_x][v1_x] -= scale_x;
+ entries[v1_y][v2_y] -= scale_y;
+ entries[v2_y][v1_y] -= scale_y;
+ }
+ }
+ }
+
+ Eigen::SparseMatrix<double> A(V.cols() * 2, V.cols() * 2);
+ VectorXd rhs(V.cols() * 2);
+ rhs.setZero();
+ for (int i = 0; i < entries.size(); ++i) {
+ rhs(i) = lambda * S(i % 2, i / 2);
+ for (auto& rec : entries[i]) {
+ lhsTriplets.push_back(Eigen::Triplet<double>(i, rec.first, rec.second));
+ }
+ }
+ A.setFromTriplets(lhsTriplets.begin(), lhsTriplets.end());
+ LinearSolver<Eigen::SparseMatrix<double>> solver;
+ solver.analyzePattern(A);
+
+ solver.factorize(A);
+
+ VectorXd result = solver.solve(rhs);
+
+ double total_area = 0;
+ for (int i = 0; i < V.cols(); ++i) {
+ S(0, i) = (result(i * 2));
+ S(1, i) = (result(i * 2 + 1));
+ total_area += S(0, i) * S(1, i);
+ }
+ total_area = sqrt(V.cols() / total_area);
+ for (int i = 0; i < V.cols(); ++i) {
+ // S(0, i) *= total_area;
+ // S(1, i) *= total_area;
+ }
+ } else {
+ for (int i = 0; i < V.cols(); ++i) {
+ S(0, i) = 1;
+ S(1, i) = 1;
+ }
+ }
+
+ for (int l = 0; l < mRes.mS.size() - 1; ++l) {
+ const MatrixXd& S = mRes.mS[l];
+ MatrixXd& S_next = mRes.mS[l + 1];
+ auto& toUpper = mRes.mToUpper[l];
+ for (int i = 0; i < toUpper.cols(); ++i) {
+ Vector2i upper = toUpper.col(i);
+ Vector2d q0 = S.col(upper[0]);
+
+ if (upper[1] != -1) {
+ q0 = (q0 + S.col(upper[1])) * 0.5;
+ }
+ S_next.col(i) = q0;
+ }
+ }
+}
+
+void Optimizer::optimize_positions(Hierarchy& mRes, int with_scale) {
+ int levelIterations = 6;
+#ifdef WITH_CUDA
+ optimize_positions_cuda(mRes);
+ cudaMemcpy(mRes.mO[0].data(), mRes.cudaO[0], sizeof(glm::dvec3) * mRes.mO[0].cols(),
+ cudaMemcpyDeviceToHost);
+#else
+ for (int level = mRes.mAdj.size() - 1; level >= 0; --level) {
+ for (int iter = 0; iter < levelIterations; ++iter) {
+ AdjacentMatrix& adj = mRes.mAdj[level];
+ const MatrixXd &N = mRes.mN[level], &Q = mRes.mQ[level], &V = mRes.mV[level];
+ const MatrixXd& CQ = mRes.mCQ[level];
+ const MatrixXd& CO = mRes.mCO[level];
+ const VectorXd& COw = mRes.mCOw[level];
+ MatrixXd& O = mRes.mO[level];
+ MatrixXd& S = mRes.mS[level];
+ auto& phases = mRes.mPhases[level];
+ for (int phase = 0; phase < phases.size(); ++phase) {
+ auto& p = phases[phase];
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int pi = 0; pi < p.size(); ++pi) {
+ int i = p[pi];
+ double scale_x = mRes.mScale;
+ double scale_y = mRes.mScale;
+ if (with_scale) {
+ scale_x *= S(0, i);
+ scale_y *= S(1, i);
+ }
+ double inv_scale_x = 1.0f / scale_x;
+ double inv_scale_y = 1.0f / scale_y;
+ const Vector3d n_i = N.col(i), v_i = V.col(i);
+ Vector3d q_i = Q.col(i);
+
+ Vector3d sum = O.col(i);
+ double weight_sum = 0.0f;
+
+ q_i.normalize();
+ for (auto& link : adj[i]) {
+ const int j = link.id;
+ const double weight = link.weight;
+ if (weight == 0) continue;
+ double scale_x_1 = mRes.mScale;
+ double scale_y_1 = mRes.mScale;
+ if (with_scale) {
+ scale_x_1 *= S(0, j);
+ scale_y_1 *= S(1, j);
+ }
+ double inv_scale_x_1 = 1.0f / scale_x_1;
+ double inv_scale_y_1 = 1.0f / scale_y_1;
+
+ const Vector3d n_j = N.col(j), v_j = V.col(j);
+ Vector3d q_j = Q.col(j), o_j = O.col(j);
+
+ q_j.normalize();
+
+ std::pair<Vector3d, Vector3d> value = compat_position_extrinsic_4(
+ v_i, n_i, q_i, sum, v_j, n_j, q_j, o_j, scale_x, scale_y, inv_scale_x,
+ inv_scale_y, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1);
+
+ sum = value.first * weight_sum + value.second * weight;
+ weight_sum += weight;
+ if (weight_sum > RCPOVERFLOW) sum /= weight_sum;
+ sum -= n_i.dot(sum - v_i) * n_i;
+ }
+
+ if (COw.size() > 0) {
+ float cw = COw[i];
+ if (cw != 0) {
+ Vector3d co = CO.col(i), cq = CQ.col(i);
+ Vector3d d = co - sum;
+ d -= cq.dot(d) * cq;
+ sum += cw * d;
+ sum -= n_i.dot(sum - v_i) * n_i;
+ }
+ }
+
+ if (weight_sum > 0) {
+ O.col(i) = position_round_4(sum, q_i, n_i, v_i, scale_x, scale_y,
+ inv_scale_x, inv_scale_y);
+ }
+ }
+ }
+ }
+ if (level > 0) {
+ const MatrixXd& srcField = mRes.mO[level];
+ const MatrixXi& toUpper = mRes.mToUpper[level - 1];
+ MatrixXd& destField = mRes.mO[level - 1];
+ const MatrixXd& N = mRes.mN[level - 1];
+ const MatrixXd& V = mRes.mV[level - 1];
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < srcField.cols(); ++i) {
+ for (int k = 0; k < 2; ++k) {
+ int dest = toUpper(k, i);
+ if (dest == -1) continue;
+ Vector3d o = srcField.col(i), n = N.col(dest), v = V.col(dest);
+ o -= n * n.dot(o - v);
+ destField.col(dest) = o;
+ }
+ }
+ }
+ }
+#endif
+}
+
+void Optimizer::optimize_positions_dynamic(
+ MatrixXi& F, MatrixXd& V, MatrixXd& N, MatrixXd& Q, std::vector<std::vector<int>>& Vset,
+ std::vector<Vector3d>& O_compact, std::vector<Vector4i>& F_compact,
+ std::vector<int>& V2E_compact, std::vector<int>& E2E_compact, double mScale,
+ std::vector<Vector3d>& diffs, std::vector<int>& diff_count,
+ std::map<std::pair<int, int>, int>& o2e, std::vector<int>& sharp_o,
+ std::map<int, std::pair<Vector3d, Vector3d>>& compact_sharp_constraints, int with_scale) {
+ std::set<int> uncertain;
+ for (auto& info : o2e) {
+ if (diff_count[info.second] == 0) {
+ uncertain.insert(info.first.first);
+ uncertain.insert(info.first.second);
+ }
+ }
+ std::vector<int> Vind(O_compact.size(), -1);
+ std::vector<std::list<int>> links(O_compact.size());
+ std::vector<std::list<int>> dedges(O_compact.size());
+ std::vector<std::vector<int>> adj(V.cols());
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int v1 = F(j, i);
+ int v2 = F((j + 1) % 3, i);
+ adj[v1].push_back(v2);
+ }
+ }
+ auto FindNearest = [&]() {
+ for (int i = 0; i < O_compact.size(); ++i) {
+ if (Vind[i] == -1) {
+ double min_dis = 1e30;
+ int min_ind = -1;
+ for (auto v : Vset[i]) {
+ double dis = (V.col(v) - O_compact[i]).squaredNorm();
+ if (dis < min_dis) {
+ min_dis = dis;
+ min_ind = v;
+ }
+ }
+ if (min_ind > -1) {
+ Vind[i] = min_ind;
+ double x = (O_compact[i] - V.col(min_ind)).dot(N.col(min_ind));
+ O_compact[i] -= x * N.col(min_ind);
+ }
+ } else {
+ int current_v = Vind[i];
+ Vector3d n = N.col(current_v);
+ double current_dis = (O_compact[i] - V.col(current_v)).squaredNorm();
+ while (true) {
+ int next_v = -1;
+ for (auto& v : adj[current_v]) {
+ if (N.col(v).dot(n) < cos(10.0 / 180.0 * 3.141592654)) continue;
+ double dis = (O_compact[i] - V.col(v)).squaredNorm();
+ if (dis < current_dis) {
+ current_dis = dis;
+ next_v = v;
+ }
+ }
+ if (next_v == -1) break;
+ // rotate ideal distance
+ Vector3d n1 = N.col(current_v);
+ Vector3d n2 = N.col(next_v);
+ Vector3d axis = n1.cross(n2);
+ double len = axis.norm();
+ double angle = atan2(len, n1.dot(n2));
+ axis.normalized();
+ Matrix3d m = AngleAxisd(angle, axis).toRotationMatrix();
+ for (auto e : dedges[i]) {
+ Vector3d& d = diffs[e];
+ d = m * d;
+ }
+ current_v = next_v;
+ }
+ Vind[i] = current_v;
+ }
+ }
+ };
+
+ auto BuildConnection = [&]() {
+ for (int i = 0; i < links.size(); ++i) {
+ int deid0 = V2E_compact[i];
+ if (deid0 != -1) {
+ std::list<int>& connection = links[i];
+ std::list<int>& dedge = dedges[i];
+ int deid = deid0;
+ do {
+ connection.push_back(F_compact[deid / 4][(deid + 1) % 4]);
+ dedge.push_back(deid);
+ deid = E2E_compact[deid / 4 * 4 + (deid + 3) % 4];
+ } while (deid != -1 && deid != deid0);
+ if (deid == -1) {
+ deid = deid0;
+ do {
+ deid = E2E_compact[deid];
+ if (deid == -1) break;
+ deid = deid / 4 * 4 + (deid + 1) % 4;
+ connection.push_front(F_compact[deid / 4][(deid + 1) % 4]);
+ dedge.push_front(deid);
+ } while (true);
+ }
+ }
+ }
+ };
+
+ std::vector<Vector3d> lines;
+ auto ComputeDistance = [&]() {
+ std::set<int> unobserved;
+ for (auto& info : o2e) {
+ if (diff_count[info.second] == 0) {
+ unobserved.insert(info.first.first);
+ }
+ }
+ while (true) {
+ bool update = false;
+ std::set<int> observed;
+ for (auto& p : unobserved) {
+ std::vector<int> observations, edges;
+ int count = 0;
+ for (auto& e : dedges[p]) {
+ edges.push_back(e);
+ if (diff_count[e]) {
+ count += 1;
+ observations.push_back(1);
+ } else {
+ observations.push_back(0);
+ }
+ }
+ if (count <= 1) continue;
+ update = true;
+ observed.insert(p);
+ for (int i = 0; i < observations.size(); ++i) {
+ if (observations[i] == 1) continue;
+ int j = i;
+ std::list<int> interp;
+ while (observations[j] == 0) {
+ interp.push_front(j);
+ j -= 1;
+ if (j < 0) j = edges.size() - 1;
+ }
+ j = (i + 1) % edges.size();
+ while (observations[j] == 0) {
+ interp.push_back(j);
+ j += 1;
+ if (j == edges.size()) j = 0;
+ }
+ Vector3d dl = diffs[edges[(interp.front() + edges.size() - 1) % edges.size()]];
+ double lenl = dl.norm();
+ Vector3d dr = diffs[edges[(interp.back() + 1) % edges.size()]];
+ double lenr = dr.norm();
+ dl /= lenl;
+ dr /= lenr;
+ Vector3d n = dl.cross(dr).normalized();
+ double angle = atan2(dl.cross(dr).norm(), dl.dot(dr));
+ if (angle < 0) angle += 2 * 3.141592654;
+ Vector3d nc = N.col(Vind[p]);
+ if (n.dot(nc) < 0) {
+ n = -n;
+ angle = 2 * 3.141592654 - angle;
+ }
+ double step = (lenr - lenl) / (interp.size() + 1);
+ angle /= interp.size() + 1;
+ Vector3d dlp = nc.cross(dl).normalized();
+ int t = 0;
+ for (auto q : interp) {
+ t += 1;
+ observations[q] = 1;
+ double ad = angle * t;
+ int e = edges[q];
+ int re = E2E_compact[e];
+ diff_count[e] = 2;
+ diffs[e] = (cos(ad) * dl + sin(ad) * dlp) * (lenl + step * t);
+ if (re != -1) {
+ diff_count[re] = 2;
+ diffs[re] = -diffs[e];
+ }
+ }
+ for (int i = 0; i < edges.size(); ++i) {
+ lines.push_back(O_compact[p]);
+ lines.push_back(O_compact[p] + diffs[edges[i]]);
+ }
+ }
+ }
+ if (!update) break;
+ for (auto& p : observed) unobserved.erase(p);
+ }
+ };
+
+ BuildConnection();
+ int max_iter = 10;
+ for (int iter = 0; iter < max_iter; ++iter) {
+ FindNearest();
+ ComputeDistance();
+
+ std::vector<std::unordered_map<int, double>> entries(O_compact.size() * 2);
+ std::vector<int> fixed_dim(O_compact.size() * 2, 0);
+ for (auto& info : compact_sharp_constraints) {
+ fixed_dim[info.first * 2 + 1] = 1;
+ if (info.second.second.norm() < 0.5) fixed_dim[info.first * 2] = 1;
+ }
+ std::vector<double> b(O_compact.size() * 2);
+ std::vector<double> x(O_compact.size() * 2);
+ std::vector<Vector3d> Q_compact(O_compact.size());
+ std::vector<Vector3d> N_compact(O_compact.size());
+ std::vector<Vector3d> V_compact(O_compact.size());
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < O_compact.size(); ++i) {
+ Q_compact[i] = Q.col(Vind[i]);
+ N_compact[i] = N.col(Vind[i]);
+ V_compact[i] = V.col(Vind[i]);
+ if (fixed_dim[i * 2 + 1] && !fixed_dim[i * 2]) {
+ Q_compact[i] = compact_sharp_constraints[i].second;
+ V_compact[i] = compact_sharp_constraints[i].first;
+ }
+ }
+ for (int i = 0; i < O_compact.size(); ++i) {
+ Vector3d q = Q_compact[i];
+ Vector3d n = N_compact[i];
+ Vector3d q_y = n.cross(q);
+ auto Vi = V_compact[i];
+ x[i * 2] = (O_compact[i] - Vi).dot(q);
+ x[i * 2 + 1] = (O_compact[i] - Vi).dot(q_y);
+ }
+ for (int i = 0; i < O_compact.size(); ++i) {
+ Vector3d qx = Q_compact[i];
+ Vector3d qy = N_compact[i];
+ qy = qy.cross(qx);
+ auto dedge_it = dedges[i].begin();
+ for (auto it = links[i].begin(); it != links[i].end(); ++it, ++dedge_it) {
+ int j = *it;
+ Vector3d qx2 = Q_compact[j];
+ Vector3d qy2 = N_compact[j];
+ qy2 = qy2.cross(qx2);
+
+ int de = o2e[std::make_pair(i, j)];
+ double lambda = (diff_count[de] == 1) ? 1 : 1;
+ Vector3d target_offset = diffs[de];
+
+ auto Vi = V_compact[i];
+ auto Vj = V_compact[j];
+
+ Vector3d offset = Vj - Vi;
+
+ // target_offset.normalize();
+ // target_offset *= mScale;
+ Vector3d C = target_offset - offset;
+ int vid[] = {j * 2, j * 2 + 1, i * 2, i * 2 + 1};
+ Vector3d weights[] = {qx2, qy2, -qx, -qy};
+ for (int ii = 0; ii < 4; ++ii) {
+ for (int jj = 0; jj < 4; ++jj) {
+ auto it = entries[vid[ii]].find(vid[jj]);
+ if (it == entries[vid[ii]].end()) {
+ entries[vid[ii]][vid[jj]] = lambda * weights[ii].dot(weights[jj]);
+ } else {
+ entries[vid[ii]][vid[jj]] += lambda * weights[ii].dot(weights[jj]);
+ }
+ }
+ b[vid[ii]] += lambda * weights[ii].dot(C);
+ }
+ }
+ }
+
+ // fix sharp edges
+ for (int i = 0; i < entries.size(); ++i) {
+ if (entries[i].size() == 0) {
+ entries[i][i] = 1;
+ b[i] = x[i];
+ }
+ if (fixed_dim[i]) {
+ b[i] = x[i];
+ entries[i].clear();
+ entries[i][i] = 1;
+ } else {
+ std::unordered_map<int, double> newmap;
+ for (auto& rec : entries[i]) {
+ if (fixed_dim[rec.first]) {
+ b[i] -= rec.second * x[rec.first];
+ } else {
+ newmap[rec.first] = rec.second;
+ }
+ }
+ std::swap(entries[i], newmap);
+ }
+ }
+ std::vector<Eigen::Triplet<double>> lhsTriplets;
+ lhsTriplets.reserve(F_compact.size() * 8);
+ Eigen::SparseMatrix<double> A(O_compact.size() * 2, O_compact.size() * 2);
+ VectorXd rhs(O_compact.size() * 2);
+ rhs.setZero();
+ for (int i = 0; i < entries.size(); ++i) {
+ rhs(i) = b[i];
+ for (auto& rec : entries[i]) {
+ lhsTriplets.push_back(Eigen::Triplet<double>(i, rec.first, rec.second));
+ }
+ }
+
+ A.setFromTriplets(lhsTriplets.begin(), lhsTriplets.end());
+
+#ifdef LOG_OUTPUT
+ int t1 = GetCurrentTime64();
+#endif
+
+ // FIXME: IncompleteCholesky Preconditioner will fail here so I fallback to Diagonal one.
+ // I suspected either there is a implementation bug in IncompleteCholesky Preconditioner
+ // or there is a memory corruption somewhere. However, g++'s address sanitizer does not
+ // report anything useful.
+ LinearSolver<Eigen::SparseMatrix<double>> solver;
+ solver.analyzePattern(A);
+ solver.factorize(A);
+ // Eigen::setNbThreads(1);
+ // ConjugateGradient<SparseMatrix<double>, Lower | Upper> solver;
+ // VectorXd x0 = VectorXd::Map(x.data(), x.size());
+ // solver.setMaxIterations(40);
+
+ // solver.compute(A);
+ VectorXd x_new = solver.solve(rhs); // solver.solveWithGuess(rhs, x0);
+
+#ifdef LOG_OUTPUT
+ // std::cout << "[LSQ] n_iteration:" << solver.iterations() << std::endl;
+ // std::cout << "[LSQ] estimated error:" << solver.error() << std::endl;
+ int t2 = GetCurrentTime64();
+ printf("[LSQ] Linear solver uses %lf seconds.\n", (t2 - t1) * 1e-3);
+#endif
+ for (int i = 0; i < O_compact.size(); ++i) {
+ // Vector3d q = Q.col(Vind[i]);
+ Vector3d q = Q_compact[i];
+ // Vector3d n = N.col(Vind[i]);
+ Vector3d n = N_compact[i];
+ Vector3d q_y = n.cross(q);
+ auto Vi = V_compact[i];
+ O_compact[i] = Vi + q * x_new[i * 2] + q_y * x_new[i * 2 + 1];
+ }
+
+ // forgive my hack...
+ if (iter + 1 == max_iter) {
+ for (int iter = 0; iter < 5; ++iter) {
+ for (int i = 0; i < O_compact.size(); ++i) {
+ if (sharp_o[i]) continue;
+ if (dedges[i].size() != 4 || uncertain.count(i)) {
+ Vector3d n(0, 0, 0), v(0, 0, 0);
+ Vector3d v0 = O_compact[i];
+ for (auto e : dedges[i]) {
+ Vector3d v1 = O_compact[F_compact[e / 4][(e + 1) % 4]];
+ Vector3d v2 = O_compact[F_compact[e / 4][(e + 3) % 4]];
+ n += (v1 - v0).cross(v2 - v0);
+ v += v1;
+ }
+ n.normalize();
+ Vector3d offset = v / dedges[i].size() - v0;
+ offset -= offset.dot(n) * n;
+ O_compact[i] += offset;
+ }
+ }
+ }
+ }
+ }
+}
+
+void Optimizer::optimize_positions_sharp(
+ Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff,
+ std::vector<int>& sharp_edges, std::set<int>& sharp_vertices,
+ std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, int with_scale) {
+ auto& V = mRes.mV[0];
+ auto& F = mRes.mF;
+ auto& Q = mRes.mQ[0];
+ auto& N = mRes.mN[0];
+ auto& O = mRes.mO[0];
+ auto& S = mRes.mS[0];
+
+ DisajointTree tree(V.cols());
+ for (int i = 0; i < edge_diff.size(); ++i) {
+ if (edge_diff[i].array().abs().sum() == 0) {
+ tree.Merge(edge_values[i].x, edge_values[i].y);
+ }
+ }
+ tree.BuildCompactParent();
+ std::map<int, int> compact_sharp_indices;
+ std::set<DEdge> compact_sharp_edges;
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i] == 1) {
+ int v1 = tree.Index(F(i % 3, i / 3));
+ int v2 = tree.Index(F((i + 1) % 3, i / 3));
+ compact_sharp_edges.insert(DEdge(v1, v2));
+ }
+ }
+ for (auto& v : sharp_vertices) {
+ int p = tree.Index(v);
+ if (compact_sharp_indices.count(p) == 0) {
+ int s = compact_sharp_indices.size();
+ compact_sharp_indices[p] = s;
+ }
+ }
+ std::map<int, std::set<int>> sharp_vertices_links;
+ std::set<DEdge> sharp_dedges;
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i]) {
+ int v1 = F(i % 3, i / 3);
+ int v2 = F((i + 1) % 3, i / 3);
+ if (sharp_vertices_links.count(v1) == 0) sharp_vertices_links[v1] = std::set<int>();
+ sharp_vertices_links[v1].insert(v2);
+ sharp_dedges.insert(DEdge(v1, v2));
+ }
+ }
+ std::vector<std::vector<int>> sharp_to_original_indices(compact_sharp_indices.size());
+ for (auto& v : sharp_vertices_links) {
+ if (v.second.size() == 2) continue;
+ int p = tree.Index(v.first);
+ sharp_to_original_indices[compact_sharp_indices[p]].push_back(v.first);
+ }
+ for (auto& v : sharp_vertices_links) {
+ if (v.second.size() != 2) continue;
+ int p = tree.Index(v.first);
+ sharp_to_original_indices[compact_sharp_indices[p]].push_back(v.first);
+ }
+
+ for (int i = 0; i < V.cols(); ++i) {
+ if (sharp_vertices.count(i)) continue;
+ int p = tree.Index(i);
+ if (compact_sharp_indices.count(p))
+ sharp_to_original_indices[compact_sharp_indices[p]].push_back(i);
+ }
+
+ int num = sharp_to_original_indices.size();
+ std::vector<std::set<int>> links(sharp_to_original_indices.size());
+ for (int e = 0; e < edge_diff.size(); ++e) {
+ int v1 = edge_values[e].x;
+ int v2 = edge_values[e].y;
+ int p1 = tree.Index(v1);
+ int p2 = tree.Index(v2);
+ if (p1 == p2 || compact_sharp_edges.count(DEdge(p1, p2)) == 0) continue;
+ p1 = compact_sharp_indices[p1];
+ p2 = compact_sharp_indices[p2];
+
+ links[p1].insert(p2);
+ links[p2].insert(p1);
+ }
+
+ std::vector<int> hash(links.size(), 0);
+ std::vector<std::vector<Vector3d>> loops;
+ for (int i = 0; i < num; ++i) {
+ if (hash[i] == 1) continue;
+ if (links[i].size() == 2) {
+ std::vector<int> q;
+ q.push_back(i);
+ hash[i] = 1;
+ int v = i;
+ int prev_v = -1;
+ bool is_loop = false;
+ while (links[v].size() == 2) {
+ int next_v = -1;
+ for (auto nv : links[v])
+ if (nv != prev_v) next_v = nv;
+ if (hash[next_v]) {
+ is_loop = true;
+ break;
+ }
+ if (links[next_v].size() == 2) hash[next_v] = true;
+ q.push_back(next_v);
+ prev_v = v;
+ v = next_v;
+ }
+ if (!is_loop && q.size() >= 2) {
+ std::vector<int> q1;
+ int v = i;
+ int prev_v = q[1];
+ while (links[v].size() == 2) {
+ int next_v = -1;
+ for (auto nv : links[v])
+ if (nv != prev_v) next_v = nv;
+ if (hash[next_v]) {
+ is_loop = true;
+ break;
+ }
+ if (links[next_v].size() == 2) hash[next_v] = true;
+ q1.push_back(next_v);
+ prev_v = v;
+ v = next_v;
+ }
+ std::reverse(q1.begin(), q1.end());
+ q1.insert(q1.end(), q.begin(), q.end());
+ std::swap(q1, q);
+ }
+ if (q.size() < 3) continue;
+ if (is_loop) q.push_back(q.front());
+ double len = 0, scale = 0;
+ std::vector<Vector3d> o(q.size()), new_o(q.size());
+ std::vector<double> sc(q.size());
+
+ for (int i = 0; i < q.size() - 1; ++i) {
+ int v1 = q[i];
+ int v2 = q[i + 1];
+ auto it = links[v1].find(v2);
+ if (it == links[v1].end()) {
+ printf("Non exist!\n");
+ exit(0);
+ }
+ }
+
+ for (int i = 0; i < q.size(); ++i) {
+ if (sharp_to_original_indices[q[i]].size() == 0) {
+ continue;
+ }
+ o[i] = O.col(sharp_to_original_indices[q[i]][0]);
+ Vector3d qx = Q.col(sharp_to_original_indices[q[i]][0]);
+ Vector3d qy = Vector3d(N.col(sharp_to_original_indices[q[i]][0])).cross(qx);
+ int fst = sharp_to_original_indices[q[1]][0];
+ Vector3d dis = (i == 0) ? (Vector3d(O.col(fst)) - o[i]) : o[i] - o[i - 1];
+ if (with_scale)
+ sc[i] = (abs(qx.dot(dis)) > abs(qy.dot(dis)))
+ ? S(0, sharp_to_original_indices[q[i]][0])
+ : S(1, sharp_to_original_indices[q[i]][0]);
+ else
+ sc[i] = 1;
+ new_o[i] = o[i];
+ }
+
+ if (is_loop) {
+ for (int i = 0; i < q.size(); ++i) {
+ Vector3d dir =
+ (o[(i + 1) % q.size()] - o[(i + q.size() - 1) % q.size()]).normalized();
+ for (auto& ind : sharp_to_original_indices[q[i]]) {
+ sharp_constraints[ind] = std::make_pair(o[i], dir);
+ }
+ }
+ } else {
+ for (int i = 0; i < q.size(); ++i) {
+ Vector3d dir(0, 0, 0);
+ if (i != 0 && i + 1 != q.size())
+ dir = (o[i + 1] - o[i - 1]).normalized();
+ else if (links[q[i]].size() == 1) {
+ if (i == 0)
+ dir = (o[i + 1] - o[i]).normalized();
+ else
+ dir = (o[i] - o[i - 1]).normalized();
+ }
+ for (auto& ind : sharp_to_original_indices[q[i]]) {
+ sharp_constraints[ind] = std::make_pair(o[i], dir);
+ }
+ }
+ }
+
+ for (int i = 0; i < q.size() - 1; ++i) {
+ len += (o[i + 1] - o[i]).norm();
+ scale += sc[i];
+ }
+
+ int next_m = q.size() - 1;
+
+ double left_norm = len * sc[0] / scale;
+ int current_v = 0;
+ double current_norm = (o[1] - o[0]).norm();
+ for (int i = 1; i < next_m; ++i) {
+ while (left_norm >= current_norm) {
+ left_norm -= current_norm;
+ current_v += 1;
+ current_norm = (o[current_v + 1] - o[current_v]).norm();
+ }
+ new_o[i] =
+ (o[current_v + 1] * left_norm + o[current_v] * (current_norm - left_norm)) /
+ current_norm;
+ o[current_v] = new_o[i];
+ current_norm -= left_norm;
+ left_norm = len * sc[current_v] / scale;
+ }
+
+ for (int i = 0; i < q.size(); ++i) {
+ for (auto v : sharp_to_original_indices[q[i]]) {
+ O.col(v) = new_o[i];
+ }
+ }
+
+ loops.push_back(new_o);
+ }
+ }
+ return;
+ std::ofstream os("/Users/jingwei/Desktop/sharp.obj");
+ for (int i = 0; i < loops.size(); ++i) {
+ for (auto& v : loops[i]) {
+ os << "v " << v[0] << " " << v[1] << " " << v[2] << "\n";
+ }
+ }
+ int offset = 1;
+ for (int i = 0; i < loops.size(); ++i) {
+ for (int j = 0; j < loops[i].size() - 1; ++j) {
+ os << "l " << offset + j << " " << offset + j + 1 << "\n";
+ }
+ offset += loops[i].size();
+ }
+ os.close();
+ exit(0);
+}
+
+void Optimizer::optimize_positions_fixed(
+ Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff,
+ std::set<int>& sharp_vertices, std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints,
+ int with_scale) {
+ auto& V = mRes.mV[0];
+ auto& F = mRes.mF;
+ auto& Q = mRes.mQ[0];
+ auto& N = mRes.mN[0];
+ auto& O = mRes.mO[0];
+ auto& S = mRes.mS[0];
+
+ DisajointTree tree(V.cols());
+ for (int i = 0; i < edge_diff.size(); ++i) {
+ if (edge_diff[i].array().abs().sum() == 0) {
+ tree.Merge(edge_values[i].x, edge_values[i].y);
+ }
+ }
+ tree.BuildCompactParent();
+ int num = tree.CompactNum();
+
+ // Find the most descriptive vertex
+ std::vector<Vector3d> v_positions(num, Vector3d(0, 0, 0));
+ std::vector<int> v_count(num);
+ std::vector<double> v_distance(num, 1e30);
+ std::vector<int> v_index(num, -1);
+
+ for (int i = 0; i < V.cols(); ++i) {
+ v_positions[tree.Index(i)] += O.col(i);
+ v_count[tree.Index(i)] += 1;
+ }
+ for (int i = 0; i < num; ++i) {
+ if (v_count[i] > 0) v_positions[i] /= v_count[i];
+ }
+ for (int i = 0; i < V.cols(); ++i) {
+ int p = tree.Index(i);
+ double dis = (v_positions[p] - V.col(i)).squaredNorm();
+ if (dis < v_distance[p]) {
+ v_distance[p] = dis;
+ v_index[p] = i;
+ }
+ }
+
+ std::set<int> compact_sharp_vertices;
+ for (auto& v : sharp_vertices) {
+ v_positions[tree.Index(v)] = O.col(v);
+ v_index[tree.Index(v)] = v;
+ V.col(v) = O.col(v);
+ compact_sharp_vertices.insert(tree.Index(v));
+ }
+ std::vector<std::map<int, std::pair<int, Vector3d>>> ideal_distances(tree.CompactNum());
+ for (int e = 0; e < edge_diff.size(); ++e) {
+ int v1 = edge_values[e].x;
+ int v2 = edge_values[e].y;
+
+ int p1 = tree.Index(v1);
+ int p2 = tree.Index(v2);
+ int q1 = v_index[p1];
+ int q2 = v_index[p2];
+
+ Vector3d q_1 = Q.col(v1);
+ Vector3d q_2 = Q.col(v2);
+
+ Vector3d n_1 = N.col(v1);
+ Vector3d n_2 = N.col(v2);
+ Vector3d q_1_y = n_1.cross(q_1);
+ Vector3d q_2_y = n_2.cross(q_2);
+ auto index = compat_orientation_extrinsic_index_4(q_1, n_1, q_2, n_2);
+ double s_x1 = S(0, v1), s_y1 = S(1, v1);
+ double s_x2 = S(0, v2), s_y2 = S(1, v2);
+ int rank_diff = (index.second + 4 - index.first) % 4;
+ if (rank_diff % 2 == 1) std::swap(s_x2, s_y2);
+ Vector3d qd_x = 0.5 * (rotate90_by(q_2, n_2, rank_diff) + q_1);
+ Vector3d qd_y = 0.5 * (rotate90_by(q_2_y, n_2, rank_diff) + q_1_y);
+ double scale_x = (with_scale ? 0.5 * (s_x1 + s_x2) : 1) * mRes.mScale;
+ double scale_y = (with_scale ? 0.5 * (s_y1 + s_y2) : 1) * mRes.mScale;
+ Vector2i diff = edge_diff[e];
+
+ Vector3d origin1 =
+ /*(sharp_constraints.count(q1)) ? sharp_constraints[q1].first : */ V.col(q1);
+ Vector3d origin2 =
+ /*(sharp_constraints.count(q2)) ? sharp_constraints[q2].first : */ V.col(q2);
+ Vector3d C = diff[0] * scale_x * qd_x + diff[1] * scale_y * qd_y + origin1 - origin2;
+ auto it = ideal_distances[p1].find(p2);
+ if (it == ideal_distances[p1].end()) {
+ ideal_distances[p1][p2] = std::make_pair(1, C);
+ } else {
+ it->second.first += 1;
+ it->second.second += C;
+ }
+ }
+
+ std::vector<std::unordered_map<int, double>> entries(num * 2);
+ std::vector<double> b(num * 2);
+
+ for (int m = 0; m < num; ++m) {
+ int v1 = v_index[m];
+ for (auto& info : ideal_distances[m]) {
+ int v2 = v_index[info.first];
+ Vector3d q_1 = Q.col(v1);
+ Vector3d q_2 = Q.col(v2);
+ if (sharp_constraints.count(v1)) {
+ Vector3d d = sharp_constraints[v1].second;
+ if (d != Vector3d::Zero()) q_1 = d;
+ }
+ if (sharp_constraints.count(v2)) {
+ Vector3d d = sharp_constraints[v2].second;
+ if (d != Vector3d::Zero()) q_2 = d;
+ }
+
+ Vector3d n_1 = N.col(v1);
+ Vector3d n_2 = N.col(v2);
+ Vector3d q_1_y = n_1.cross(q_1);
+ Vector3d q_2_y = n_2.cross(q_2);
+ Vector3d weights[] = {q_2, q_2_y, -q_1, -q_1_y};
+ int vid[] = {info.first * 2, info.first * 2 + 1, m * 2, m * 2 + 1};
+ Vector3d dis = info.second.second / info.second.first;
+ double lambda = 1;
+ if (sharp_vertices.count(v1) && sharp_vertices.count(v2)) lambda = 1;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ auto it = entries[vid[i]].find(vid[j]);
+ if (it == entries[vid[i]].end()) {
+ entries[vid[i]][vid[j]] = weights[i].dot(weights[j]) * lambda;
+ } else {
+ entries[vid[i]][vid[j]] += weights[i].dot(weights[j]) * lambda;
+ }
+ }
+ b[vid[i]] += weights[i].dot(dis) * lambda;
+ }
+ }
+ }
+
+ std::vector<int> fixed_dim(num * 2, 0);
+ std::vector<double> x(num * 2);
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < num; ++i) {
+ int p = v_index[i];
+ Vector3d q = Q.col(p);
+
+ if (sharp_constraints.count(p)) {
+ Vector3d dir = sharp_constraints[p].second;
+ fixed_dim[i * 2 + 1] = 1;
+ if (dir != Vector3d::Zero()) {
+ q = dir;
+ } else
+ fixed_dim[i * 2] = 1;
+ }
+ Vector3d n = N.col(p);
+ Vector3d q_y = n.cross(q);
+ x[i * 2] = (v_positions[i] - V.col(p)).dot(q);
+ x[i * 2 + 1] = (v_positions[i] - V.col(p)).dot(q_y);
+ }
+
+ // fix sharp edges
+ for (int i = 0; i < entries.size(); ++i) {
+ if (fixed_dim[i]) {
+ b[i] = x[i];
+ entries[i].clear();
+ entries[i][i] = 1;
+ } else {
+ std::unordered_map<int, double> newmap;
+ for (auto& rec : entries[i]) {
+ if (fixed_dim[rec.first]) {
+ b[i] -= rec.second * x[rec.first];
+ } else {
+ newmap[rec.first] = rec.second;
+ }
+ }
+ std::swap(entries[i], newmap);
+ }
+ }
+ for (int i = 0; i < entries.size(); ++i) {
+ if (entries[i].size() == 0) {
+ entries[i][i] = 1;
+ }
+ }
+
+ std::vector<Eigen::Triplet<double>> lhsTriplets;
+ lhsTriplets.reserve(F.cols() * 6);
+ Eigen::SparseMatrix<double> A(num * 2, num * 2);
+ VectorXd rhs(num * 2);
+ rhs.setZero();
+ for (int i = 0; i < entries.size(); ++i) {
+ rhs(i) = b[i];
+ if (std::isnan(b[i])) {
+ printf("Equation has nan!\n");
+ exit(0);
+ }
+ for (auto& rec : entries[i]) {
+ lhsTriplets.push_back(Eigen::Triplet<double>(i, rec.first, rec.second));
+ if (std::isnan(rec.second)) {
+ printf("Equation has nan!\n");
+ exit(0);
+ }
+ }
+ }
+ A.setFromTriplets(lhsTriplets.begin(), lhsTriplets.end());
+
+#ifdef LOG_OUTPUT
+ int t1 = GetCurrentTime64();
+#endif
+ /*
+ Eigen::setNbThreads(1);
+ ConjugateGradient<SparseMatrix<double>, Lower | Upper> solver;
+ VectorXd x0 = VectorXd::Map(x.data(), x.size());
+ solver.setMaxIterations(40);
+
+ solver.compute(A);
+ */
+ LinearSolver<Eigen::SparseMatrix<double>> solver;
+ solver.analyzePattern(A);
+ solver.factorize(A);
+
+ VectorXd x_new = solver.solve(rhs);
+#ifdef LOG_OUTPUT
+ // std::cout << "[LSQ] n_iteration:" << solver.iterations() << std::endl;
+ // std::cout << "[LSQ] estimated error:" << solver.error() << std::endl;
+ int t2 = GetCurrentTime64();
+ printf("[LSQ] Linear solver uses %lf seconds.\n", (t2 - t1) * 1e-3);
+#endif
+
+ for (int i = 0; i < x.size(); ++i) {
+ if (!std::isnan(x_new[i])) {
+ if (!fixed_dim[i / 2 * 2 + 1]) {
+ double total = 0;
+ for (auto& res : entries[i]) {
+ double t = x_new[res.first];
+ if (std::isnan(t)) t = 0;
+ total += t * res.second;
+ }
+ }
+ x[i] = x_new[i];
+ }
+ }
+
+ for (int i = 0; i < O.cols(); ++i) {
+ int p = tree.Index(i);
+ int c = v_index[p];
+ Vector3d q = Q.col(c);
+ if (fixed_dim[p * 2 + 1]) {
+ Vector3d dir = sharp_constraints[c].second;
+ if (dir != Vector3d::Zero()) q = dir;
+ }
+ Vector3d n = N.col(c);
+ Vector3d q_y = n.cross(q);
+ O.col(i) = V.col(c) + q * x[p * 2] + q_y * x[p * 2 + 1];
+ }
+}
+
+void Optimizer::optimize_integer_constraints(Hierarchy& mRes, std::map<int, int>& singularities,
+ bool use_minimum_cost_flow) {
+ int edge_capacity = 2;
+ bool fullFlow = false;
+ std::vector<std::vector<int>>& AllowChange = mRes.mAllowChanges;
+ for (int level = mRes.mToUpperEdges.size(); level >= 0; --level) {
+ auto& EdgeDiff = mRes.mEdgeDiff[level];
+ auto& FQ = mRes.mFQ[level];
+ auto& F2E = mRes.mF2E[level];
+ auto& E2F = mRes.mE2F[level];
+
+ int iter = 0;
+ while (!fullFlow) {
+ std::vector<Vector4i> edge_to_constraints(E2F.size() * 2, Vector4i(-1, 0, -1, 0));
+ std::vector<int> initial(F2E.size() * 2, 0);
+ for (int i = 0; i < F2E.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int e = F2E[i][j];
+ Vector2i index = rshift90(Vector2i(e * 2 + 1, e * 2 + 2), FQ[i][j]);
+ for (int k = 0; k < 2; ++k) {
+ int l = abs(index[k]);
+ int s = index[k] / l;
+ int ind = l - 1;
+ int equationID = i * 2 + k;
+ if (edge_to_constraints[ind][0] == -1) {
+ edge_to_constraints[ind][0] = equationID;
+ edge_to_constraints[ind][1] = s;
+ } else {
+ edge_to_constraints[ind][2] = equationID;
+ edge_to_constraints[ind][3] = s;
+ }
+ initial[equationID] += s * EdgeDiff[ind / 2][ind % 2];
+ }
+ }
+ }
+ std::vector<std::pair<Vector2i, int>> arcs;
+ std::vector<int> arc_ids;
+ for (int i = 0; i < edge_to_constraints.size(); ++i) {
+ if (AllowChange[level][i] == 0) continue;
+ if (edge_to_constraints[i][0] == -1 || edge_to_constraints[i][2] == -1) continue;
+ if (edge_to_constraints[i][1] == -edge_to_constraints[i][3]) {
+ int v1 = edge_to_constraints[i][0];
+ int v2 = edge_to_constraints[i][2];
+ if (edge_to_constraints[i][1] < 0) std::swap(v1, v2);
+ int current_v = EdgeDiff[i / 2][i % 2];
+ arcs.push_back(std::make_pair(Vector2i(v1, v2), current_v));
+ if (AllowChange[level][i] == 1)
+ arc_ids.push_back(i + 1);
+ else {
+ arc_ids.push_back(-(i + 1));
+ }
+ }
+ }
+ int supply = 0;
+ int demand = 0;
+ for (int i = 0; i < initial.size(); ++i) {
+ int init_val = initial[i];
+ if (init_val > 0) {
+ arcs.push_back(std::make_pair(Vector2i(-1, i), initial[i]));
+ supply += init_val;
+ } else if (init_val < 0) {
+ demand -= init_val;
+ arcs.push_back(std::make_pair(Vector2i(i, initial.size()), -init_val));
+ }
+ }
+
+ std::unique_ptr<MaxFlowHelper> solver = nullptr;
+ if (use_minimum_cost_flow && level == mRes.mToUpperEdges.size()) {
+ lprintf("network simplex MCF is used\n");
+ solver = std::make_unique<NetworkSimplexFlowHelper>();
+ } else if (supply < 20) {
+ solver = std::make_unique<ECMaxFlowHelper>();
+ } else {
+ solver = std::make_unique<BoykovMaxFlowHelper>();
+ }
+
+#ifdef WITH_GUROBI
+ if (use_minimum_cost_flow && level == mRes.mToUpperEdges.size()) {
+ solver = std::make_unique<GurobiFlowHelper>();
+ }
+#endif
+ solver->resize(initial.size() + 2, arc_ids.size());
+
+ std::set<int> ids;
+ for (int i = 0; i < arcs.size(); ++i) {
+ int v1 = arcs[i].first[0] + 1;
+ int v2 = arcs[i].first[1] + 1;
+ int c = arcs[i].second;
+ if (v1 == 0 || v2 == initial.size() + 1) {
+ solver->addEdge(v1, v2, c, 0, -1);
+ } else {
+ if (arc_ids[i] > 0)
+ solver->addEdge(v1, v2, std::max(0, c + edge_capacity),
+ std::max(0, -c + edge_capacity), arc_ids[i] - 1);
+ else {
+ if (c > 0)
+ solver->addEdge(v1, v2, std::max(0, c - 1),
+ std::max(0, -c + edge_capacity), -1 - arc_ids[i]);
+ else
+ solver->addEdge(v1, v2, std::max(0, c + edge_capacity),
+ std::max(0, -c - 1), -1 - arc_ids[i]);
+ }
+ }
+ }
+ int flow_count = solver->compute();
+
+ solver->applyTo(EdgeDiff);
+
+ lprintf("flow_count = %d, supply = %d\n", flow_count, supply);
+ if (flow_count == supply) fullFlow = true;
+ if (level != 0 || fullFlow) break;
+ edge_capacity += 1;
+ iter++;
+ if (iter == 10) {
+ /* Probably won't converge. */
+ break;
+ }
+ lprintf("Not full flow, edge_capacity += 1\n");
+ }
+
+ if (level != 0) {
+ auto& nEdgeDiff = mRes.mEdgeDiff[level - 1];
+ auto& toUpper = mRes.mToUpperEdges[level - 1];
+ auto& toUpperOrients = mRes.mToUpperOrients[level - 1];
+ for (int i = 0; i < toUpper.size(); ++i) {
+ if (toUpper[i] >= 0) {
+ int orient = (4 - toUpperOrients[i]) % 4;
+ nEdgeDiff[i] = rshift90(EdgeDiff[toUpper[i]], orient);
+ }
+ }
+ }
+ }
+}
+
+#ifdef WITH_CUDA
+
+void Optimizer::optimize_orientations_cuda(Hierarchy& mRes) {
+ int levelIterations = 6;
+ for (int level = mRes.mN.size() - 1; level >= 0; --level) {
+ Link* adj = mRes.cudaAdj[level];
+ int* adjOffset = mRes.cudaAdjOffset[level];
+ glm::dvec3* N = mRes.cudaN[level];
+ glm::dvec3* Q = mRes.cudaQ[level];
+ auto& phases = mRes.cudaPhases[level];
+ for (int iter = 0; iter < levelIterations; ++iter) {
+ for (int phase = 0; phase < phases.size(); ++phase) {
+ int* p = phases[phase];
+ UpdateOrientation(p, mRes.mPhases[level][phase].size(), N, Q, adj, adjOffset,
+ mRes.mAdj[level][phase].size());
+ }
+ }
+ if (level > 0) {
+ glm::dvec3* srcField = mRes.cudaQ[level];
+ glm::ivec2* toUpper = mRes.cudaToUpper[level - 1];
+ glm::dvec3* destField = mRes.cudaQ[level - 1];
+ glm::dvec3* N = mRes.cudaN[level - 1];
+ PropagateOrientationUpper(srcField, mRes.mQ[level].cols(), toUpper, N, destField);
+ }
+ }
+
+ for (int l = 0; l < mRes.mN.size() - 1; ++l) {
+ glm::dvec3* N = mRes.cudaN[l];
+ glm::dvec3* N_next = mRes.cudaN[l + 1];
+ glm::dvec3* Q = mRes.cudaQ[l];
+ glm::dvec3* Q_next = mRes.cudaQ[l + 1];
+ glm::ivec2* toUpper = mRes.cudaToUpper[l];
+
+ PropagateOrientationLower(toUpper, Q, N, Q_next, N_next, mRes.mToUpper[l].cols());
+ }
+}
+
+void Optimizer::optimize_positions_cuda(Hierarchy& mRes) {
+ int levelIterations = 6;
+ for (int level = mRes.mAdj.size() - 1; level >= 0; --level) {
+ Link* adj = mRes.cudaAdj[level];
+ int* adjOffset = mRes.cudaAdjOffset[level];
+ glm::dvec3* N = mRes.cudaN[level];
+ glm::dvec3* Q = mRes.cudaQ[level];
+ glm::dvec3* V = mRes.cudaV[level];
+ glm::dvec3* O = mRes.cudaO[level];
+ std::vector<int*> phases = mRes.cudaPhases[level];
+ for (int iter = 0; iter < levelIterations; ++iter) {
+ for (int phase = 0; phase < phases.size(); ++phase) {
+ int* p = phases[phase];
+ UpdatePosition(p, mRes.mPhases[level][phase].size(), N, Q, adj, adjOffset,
+ mRes.mAdj[level][phase].size(), V, O, mRes.mScale);
+ }
+ }
+ if (level > 0) {
+ glm::dvec3* srcField = mRes.cudaO[level];
+ glm::ivec2* toUpper = mRes.cudaToUpper[level - 1];
+ glm::dvec3* destField = mRes.cudaO[level - 1];
+ glm::dvec3* N = mRes.cudaN[level - 1];
+ glm::dvec3* V = mRes.cudaV[level - 1];
+ PropagatePositionUpper(srcField, mRes.mO[level].cols(), toUpper, N, V, destField);
+ }
+ }
+}
+
+#endif
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/optimizer.hpp b/extern/quadriflow/src/optimizer.hpp
new file mode 100644
index 00000000000..ad6fa720a6e
--- /dev/null
+++ b/extern/quadriflow/src/optimizer.hpp
@@ -0,0 +1,56 @@
+#ifndef OPTIMIZER_H_
+#define OPTIMIZER_H_
+#include "config.hpp"
+#include "field-math.hpp"
+#include "hierarchy.hpp"
+
+namespace qflow {
+
+class Optimizer {
+ public:
+ Optimizer();
+ static void optimize_orientations(Hierarchy& mRes);
+ static void optimize_scale(Hierarchy& mRes, VectorXd& rho, int adaptive);
+ static void optimize_positions(Hierarchy& mRes, int with_scale = 0);
+ static void optimize_integer_constraints(Hierarchy& mRes, std::map<int, int>& singularities,
+ bool use_minimum_cost_flow);
+ static void optimize_positions_fixed(
+ Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff,
+ std::set<int>& sharp_vertices,
+ std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, int with_scale = 0);
+ static void optimize_positions_sharp(
+ Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff,
+ std::vector<int>& sharp_edges, std::set<int>& sharp_vertices,
+ std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, int with_scale = 0);
+ static void optimize_positions_dynamic(
+ MatrixXi& F, MatrixXd& V, MatrixXd& N, MatrixXd& Q, std::vector<std::vector<int>>& Vset,
+ std::vector<Vector3d>& O_compact, std::vector<Vector4i>& F_compact,
+ std::vector<int>& V2E_compact, std::vector<int>& E2E_compact, double mScale,
+ std::vector<Vector3d>& diffs, std::vector<int>& diff_count,
+ std::map<std::pair<int, int>, int>& o2e, std::vector<int>& sharp_o,
+ std::map<int, std::pair<Vector3d, Vector3d>>& compact_sharp_constraints, int with_scale);
+#ifdef WITH_CUDA
+ static void optimize_orientations_cuda(Hierarchy& mRes);
+ static void optimize_positions_cuda(Hierarchy& mRes);
+#endif
+};
+
+#ifdef WITH_CUDA
+extern void UpdateOrientation(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj,
+ int* adjOffset, int num_adj);
+extern void PropagateOrientationUpper(glm::dvec3* srcField, int num_orientation,
+ glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* destField);
+extern void PropagateOrientationLower(glm::ivec2* toUpper, glm::dvec3* Q, glm::dvec3* N,
+ glm::dvec3* Q_next, glm::dvec3* N_next, int num_toUpper);
+
+extern void UpdatePosition(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj,
+ int* adjOffset, int num_adj, glm::dvec3* V, glm::dvec3* O,
+ double scale);
+extern void PropagatePositionUpper(glm::dvec3* srcField, int num_position, glm::ivec2* toUpper,
+ glm::dvec3* N, glm::dvec3* V, glm::dvec3* destField);
+
+#endif
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/parametrizer-flip.cpp b/extern/quadriflow/src/parametrizer-flip.cpp
new file mode 100644
index 00000000000..67b6ebbb8e2
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer-flip.cpp
@@ -0,0 +1,583 @@
+#include "dedge.hpp"
+#include "parametrizer.hpp"
+
+#include <algorithm>
+#include <queue>
+#include <unordered_map>
+#include <vector>
+
+namespace qflow {
+
+double Parametrizer::QuadEnergy(std::vector<int>& loop_vertices, std::vector<Vector4i>& res_quads,
+ int level) {
+ if (loop_vertices.size() < 4) return 0;
+ if (loop_vertices.size() == 4) {
+ double energy = 0;
+ for (int j = 0; j < 4; ++j) {
+ int v0 = loop_vertices[j];
+ int v2 = loop_vertices[(j + 1) % 4];
+ int v1 = loop_vertices[(j + 3) % 4];
+ Vector3d pt1 = (O_compact[v1] - O_compact[v0]).normalized();
+ Vector3d pt2 = (O_compact[v2] - O_compact[v0]).normalized();
+ Vector3d n = pt1.cross(pt2);
+ double sina = n.norm();
+ if (n.dot(N_compact[v0]) < 0) sina = -sina;
+ double cosa = pt1.dot(pt2);
+ double angle = atan2(sina, cosa) / 3.141592654 * 180.0;
+ if (angle < 0) angle = 360 + angle;
+ energy += angle * angle;
+ }
+ res_quads.push_back(
+ Vector4i(loop_vertices[0], loop_vertices[3], loop_vertices[2], loop_vertices[1]));
+ return energy;
+ }
+ double max_energy = 1e30;
+ for (int seg1 = 2; seg1 < loop_vertices.size(); seg1 += 2) {
+ for (int seg2 = seg1 + 1; seg2 < loop_vertices.size(); seg2 += 2) {
+ std::vector<Vector4i> quads[4];
+ std::vector<int> vertices = {loop_vertices[0], loop_vertices[1], loop_vertices[seg1],
+ loop_vertices[seg2]};
+ double energy = 0;
+ energy += QuadEnergy(vertices, quads[0], level + 1);
+ if (seg1 > 2) {
+ std::vector<int> vertices(loop_vertices.begin() + 1, loop_vertices.begin() + seg1);
+ vertices.push_back(loop_vertices[seg1]);
+ energy += QuadEnergy(vertices, quads[1], level + 1);
+ }
+ if (seg2 != seg1 + 1) {
+ std::vector<int> vertices(loop_vertices.begin() + seg1,
+ loop_vertices.begin() + seg2);
+ vertices.push_back(loop_vertices[seg2]);
+ energy += QuadEnergy(vertices, quads[2], level + 2);
+ }
+ if (seg2 + 1 != loop_vertices.size()) {
+ std::vector<int> vertices(loop_vertices.begin() + seg2, loop_vertices.end());
+ vertices.push_back(loop_vertices[0]);
+ energy += QuadEnergy(vertices, quads[3], level + 1);
+ }
+ if (max_energy > energy) {
+ max_energy = energy;
+ res_quads.clear();
+ for (int i = 0; i < 4; ++i) {
+ for (auto& v : quads[i]) {
+ res_quads.push_back(v);
+ }
+ }
+ }
+ }
+ }
+ return max_energy;
+}
+
+void Parametrizer::FixHoles(std::vector<int>& loop_vertices) {
+ std::vector<std::vector<int>> loop_vertices_array;
+ std::unordered_map<int, int> map_loops;
+ for (int i = 0; i < loop_vertices.size(); ++i) {
+ if (map_loops.count(loop_vertices[i])) {
+ int j = map_loops[loop_vertices[i]];
+ loop_vertices_array.push_back(std::vector<int>());
+ if (i - j > 3 && (i - j) % 2 == 0) {
+ for (int k = j; k < i; ++k) {
+ if (map_loops.count(loop_vertices[k])) {
+ loop_vertices_array.back().push_back(loop_vertices[k]);
+ map_loops.erase(loop_vertices[k]);
+ }
+ }
+ }
+ }
+ map_loops[loop_vertices[i]] = i;
+ }
+ if (map_loops.size() >= 3) {
+ loop_vertices_array.push_back(std::vector<int>());
+ for (int k = 0; k < loop_vertices.size(); ++k) {
+ if (map_loops.count(loop_vertices[k])) {
+ if (map_loops.count(loop_vertices[k])) {
+ loop_vertices_array.back().push_back(loop_vertices[k]);
+ map_loops.erase(loop_vertices[k]);
+ }
+ }
+ }
+ }
+ for (int i = 0; i < loop_vertices_array.size(); ++i) {
+ auto& loop_vertices = loop_vertices_array[i];
+ if (loop_vertices.size() == 0) return;
+ std::vector<Vector4i> quads;
+#ifdef LOG_OUTPUT
+// printf("Compute energy for loop: %d\n", (int)loop_vertices.size());
+#endif
+ QuadEnergy(loop_vertices, quads, 0);
+#ifdef LOG_OUTPUT
+// printf("quads: %d\n", quads.size());
+#endif
+ for (auto& p : quads) {
+ bool flag = false;
+ for (int j = 0; j < 4; ++j) {
+ int v1 = p[j];
+ int v2 = p[(j + 1) % 4];
+ auto key = std::make_pair(v1, v2);
+ if (Quad_edges.count(key)) {
+ flag = true;
+ break;
+ }
+ }
+ if (!flag) {
+ for (int j = 0; j < 4; ++j) {
+ int v1 = p[j];
+ int v2 = p[(j + 1) % 4];
+ auto key = std::make_pair(v1, v2);
+ Quad_edges.insert(key);
+ }
+ F_compact.push_back(p);
+ }
+ }
+ }
+}
+
+void Parametrizer::FixHoles() {
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 4; ++j) {
+ int v1 = F_compact[i][j];
+ int v2 = F_compact[i][(j + 1) % 4];
+ auto key = std::make_pair(v1, v2);
+ Quad_edges.insert(key);
+ }
+ }
+ std::vector<int> detected_boundary(E2E_compact.size(), 0);
+ for (int i = 0; i < E2E_compact.size(); ++i) {
+ if (detected_boundary[i] != 0 || E2E_compact[i] != -1) continue;
+ std::vector<int> loop_edges;
+ int current_e = i;
+
+ while (detected_boundary[current_e] == 0) {
+ detected_boundary[current_e] = 1;
+ loop_edges.push_back(current_e);
+ current_e = current_e / 4 * 4 + (current_e + 1) % 4;
+ while (E2E_compact[current_e] != -1) {
+ current_e = E2E_compact[current_e];
+ current_e = current_e / 4 * 4 + (current_e + 1) % 4;
+ }
+ }
+ std::vector<int> loop_vertices(loop_edges.size());
+ for (int j = 0; j < loop_edges.size(); ++j) {
+ loop_vertices[j] = F_compact[loop_edges[j] / 4][loop_edges[j] % 4];
+ }
+ if (loop_vertices.size() < 25) FixHoles(loop_vertices);
+ }
+}
+
+void Parametrizer::FixFlipHierarchy() {
+ Hierarchy fh;
+ fh.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, -1);
+ fh.FixFlip();
+ fh.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff);
+}
+
+void Parametrizer::FixFlipSat() {
+#ifdef LOG_OUTPUT
+ printf("Solving SAT!\n");
+#endif
+
+ if (!this->flag_aggresive_sat) return;
+
+ for (int threshold = 1; threshold <= 4; ++threshold) {
+ lprintf("[FixFlipSat] threshold = %d\n", threshold);
+
+ Hierarchy fh;
+ fh.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, -1);
+ int nflip = 0;
+ for (int depth = std::min(5, (int)fh.mFQ.size() - 1); depth >= 0; --depth) {
+ nflip = fh.FixFlipSat(depth, threshold);
+ if (depth > 0) fh.PushDownwardFlip(depth);
+ if (nflip == 0) break;
+ }
+ fh.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff);
+ if (nflip == 0) break;
+ }
+}
+
+void Parametrizer::AdvancedExtractQuad() {
+ Hierarchy fh;
+ fh.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, -1);
+ auto& V = hierarchy.mV[0];
+ auto& F = hierarchy.mF;
+ disajoint_tree = DisajointTree(V.cols());
+ auto& diffs = fh.mEdgeDiff.front();
+ for (int i = 0; i < diffs.size(); ++i) {
+ if (diffs[i] == Vector2i::Zero()) {
+ disajoint_tree.Merge(edge_values[i].x, edge_values[i].y);
+ }
+ }
+ disajoint_tree.BuildCompactParent();
+ auto& F2E = fh.mF2E.back();
+ auto& E2F = fh.mE2F.back();
+ auto& EdgeDiff = fh.mEdgeDiff.back();
+ auto& FQ = fh.mFQ.back();
+
+ std::vector<int> edge(E2F.size());
+ std::vector<int> face(F2E.size());
+ for (int i = 0; i < diffs.size(); ++i) {
+ int t = i;
+ for (int j = 0; j < fh.mToUpperEdges.size(); ++j) {
+ t = fh.mToUpperEdges[j][t];
+ if (t < 0) break;
+ }
+ if (t >= 0) edge[t] = i;
+ }
+ for (int i = 0; i < F.cols(); ++i) {
+ int t = i;
+ for (int j = 0; j < fh.mToUpperFaces.size(); ++j) {
+ t = fh.mToUpperFaces[j][t];
+ if (t < 0) break;
+ }
+ if (t >= 0) face[t] = i;
+ }
+ fh.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff);
+
+ auto& O = hierarchy.mO[0];
+ auto& Q = hierarchy.mQ[0];
+ auto& N = hierarchy.mN[0];
+ int num_v = disajoint_tree.CompactNum();
+ Vset.resize(num_v);
+ O_compact.resize(num_v, Vector3d::Zero());
+ Q_compact.resize(num_v, Vector3d::Zero());
+ N_compact.resize(num_v, Vector3d::Zero());
+ counter.resize(num_v, 0);
+ for (int i = 0; i < O.cols(); ++i) {
+ int compact_v = disajoint_tree.Index(i);
+ Vset[compact_v].push_back(i);
+ O_compact[compact_v] += O.col(i);
+ N_compact[compact_v] = N_compact[compact_v] * counter[compact_v] + N.col(i);
+ N_compact[compact_v].normalize();
+ if (counter[compact_v] == 0)
+ Q_compact[compact_v] = Q.col(i);
+ else {
+ auto pairs = compat_orientation_extrinsic_4(Q_compact[compact_v], N_compact[compact_v],
+ Q.col(i), N.col(i));
+ Q_compact[compact_v] = (pairs.first * counter[compact_v] + pairs.second).normalized();
+ }
+ counter[compact_v] += 1;
+ }
+ for (int i = 0; i < O_compact.size(); ++i) {
+ O_compact[i] /= counter[i];
+ }
+
+ BuildTriangleManifold(disajoint_tree, edge, face, edge_values, F2E, E2F, EdgeDiff, FQ);
+}
+
+void Parametrizer::BuildTriangleManifold(DisajointTree& disajoint_tree, std::vector<int>& edge,
+ std::vector<int>& face, std::vector<DEdge>& edge_values,
+ std::vector<Vector3i>& F2E, std::vector<Vector2i>& E2F,
+ std::vector<Vector2i>& EdgeDiff,
+ std::vector<Vector3i>& FQ) {
+ auto& F = hierarchy.mF;
+ std::vector<int> E2E(F2E.size() * 3, -1);
+ for (int i = 0; i < E2F.size(); ++i) {
+ int v1 = E2F[i][0];
+ int v2 = E2F[i][1];
+ int t1 = 0;
+ int t2 = 2;
+ if (v1 != -1)
+ while (F2E[v1][t1] != i) t1 += 1;
+ if (v2 != -1)
+ while (F2E[v2][t2] != i) t2 -= 1;
+ t1 += v1 * 3;
+ t2 += v2 * 3;
+ if (v1 != -1)
+ E2E[t1] = (v2 == -1) ? -1 : t2;
+ if (v2 != -1)
+ E2E[t2] = (v1 == -1) ? -1 : t1;
+ }
+
+ std::vector<Vector3i> triangle_vertices(F2E.size(), Vector3i(-1, -1, -1));
+ int num_v = 0;
+ std::vector<Vector3d> N, Q, O;
+ std::vector<std::vector<int>> Vs;
+ for (int i = 0; i < F2E.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ if (triangle_vertices[i][j] != -1) continue;
+ int f = face[i];
+ int v = disajoint_tree.Index(F(j, f));
+ Vs.push_back(Vset[v]);
+ Q.push_back(Q_compact[v]);
+ N.push_back(N_compact[v]);
+ O.push_back(O_compact[v]);
+ int deid0 = i * 3 + j;
+ int deid = deid0;
+ do {
+ triangle_vertices[deid / 3][deid % 3] = num_v;
+ deid = E2E[deid / 3 * 3 + (deid + 2) % 3];
+ } while (deid != deid0 && deid != -1);
+ if (deid == -1) {
+ deid = deid0;
+ do {
+ deid = E2E[deid];
+ if (deid == -1)
+ break;
+ deid = deid / 3 * 3 + (deid + 1) % 3;
+ triangle_vertices[deid/3][deid%3] = num_v;
+ } while (deid != -1);
+ }
+ num_v += 1;
+ }
+ }
+
+ int num_v0 = num_v;
+ do {
+ num_v0 = num_v;
+ std::vector<std::vector<int>> vert_to_dedge(num_v);
+ for (int i = 0; i < triangle_vertices.size(); ++i) {
+ Vector3i pt = triangle_vertices[i];
+ if (pt[0] == pt[1] || pt[1] == pt[2] || pt[2] == pt[0]) {
+ for (int j = 0; j < 3; ++j) {
+ int t = E2E[i * 3 + j];
+ if (t != -1) E2E[t] = -1;
+ }
+ for (int j = 0; j < 3; ++j) {
+ E2E[i * 3 + j] = -1;
+ }
+ } else {
+ for (int j = 0; j < 3; ++j)
+ vert_to_dedge[triangle_vertices[i][j]].push_back(i * 3 + j);
+ }
+ }
+ std::vector<int> colors(triangle_vertices.size() * 3, -1),
+ reverse_colors(triangle_vertices.size() * 3, -1);
+ for (int i = 0; i < vert_to_dedge.size(); ++i) {
+ int num_color = 0;
+ for (int j = 0; j < vert_to_dedge[i].size(); ++j) {
+ int deid = vert_to_dedge[i][j];
+ if (colors[deid] != -1) continue;
+ std::list<int> l;
+ int deid0 = deid;
+ do {
+ l.push_back(deid);
+ deid = deid / 3 * 3 + (deid + 2) % 3;
+ deid = E2E[deid];
+ } while (deid != -1 && deid != deid0);
+ if (deid == -1) {
+ deid = deid0;
+ do {
+ deid = E2E[deid];
+ if (deid == -1) break;
+ deid = deid / 3 * 3 + (deid + 1) % 3;
+ if (deid == deid0) break;
+ l.push_front(deid);
+ } while (true);
+ }
+ std::vector<int> dedges;
+ for (auto& e : l) dedges.push_back(e);
+ std::map<std::pair<int, int>, int> loc;
+ std::vector<int> deid_colors(dedges.size(), num_color);
+ num_color += 1;
+ for (int jj = 0; jj < dedges.size(); ++jj) {
+ int deid = dedges[jj];
+ colors[deid] = 0;
+ int v1 = triangle_vertices[deid / 3][deid % 3];
+ int v2 = triangle_vertices[deid / 3][(deid + 1) % 3];
+ std::pair<int, int> pt(v1, v2);
+ if (loc.count(pt)) {
+ int s = loc[pt];
+ for (int k = s; k < jj; ++k) {
+ int deid1 = dedges[k];
+ int v11 = triangle_vertices[deid1 / 3][deid1 % 3];
+ int v12 = triangle_vertices[deid1 / 3][(deid1 + 1) % 3];
+ std::pair<int, int> pt1(v11, v12);
+ loc.erase(pt1);
+ deid_colors[k] = num_color;
+ }
+ num_color += 1;
+ }
+ loc[pt] = jj;
+ }
+ for (int j = 0; j < dedges.size(); ++j) {
+ int deid = dedges[j];
+ int color = deid_colors[j];
+ if (color > 0) {
+ triangle_vertices[deid / 3][deid % 3] = num_v + color - 1;
+ }
+ }
+ }
+ if (num_color > 1) {
+ for (int j = 0; j < num_color - 1; ++j) {
+ Vs.push_back(Vs[i]);
+ O.push_back(O[i]);
+ N.push_back(N[i]);
+ Q.push_back(Q[i]);
+ }
+ num_v += num_color - 1;
+ }
+ }
+ } while (num_v != num_v0);
+ int offset = 0;
+ std::vector<Vector3i> triangle_edges, triangle_orients;
+ for (int i = 0; i < triangle_vertices.size(); ++i) {
+ Vector3i pt = triangle_vertices[i];
+ if (pt[0] == pt[1] || pt[1] == pt[2] || pt[2] == pt[0]) continue;
+ triangle_vertices[offset++] = triangle_vertices[i];
+ triangle_edges.push_back(F2E[i]);
+ triangle_orients.push_back(FQ[i]);
+ }
+ triangle_vertices.resize(offset);
+ std::set<int> flip_vertices;
+ for (int i = 0; i < triangle_vertices.size(); ++i) {
+ Vector2i d1 = rshift90(EdgeDiff[triangle_edges[i][0]], triangle_orients[i][0]);
+ Vector2i d2 = rshift90(EdgeDiff[triangle_edges[i][1]], triangle_orients[i][1]);
+ int area = d1[0] * d2[1] - d1[1] * d2[0];
+ if (area < 0) {
+ for (int j = 0; j < 3; ++j) {
+ flip_vertices.insert(triangle_vertices[i][j]);
+ }
+ }
+ }
+ MatrixXd NV(3, num_v);
+ MatrixXi NF(3, triangle_vertices.size());
+ memcpy(NF.data(), triangle_vertices.data(), sizeof(int) * 3 * triangle_vertices.size());
+ VectorXi NV2E, NE2E, NB, NN;
+ compute_direct_graph(NV, NF, NV2E, NE2E, NB, NN);
+
+ std::map<DEdge, std::pair<Vector3i, Vector3i>> quads;
+ for (int i = 0; i < triangle_vertices.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int e = triangle_edges[i][j];
+ int v1 = triangle_vertices[i][j];
+ int v2 = triangle_vertices[i][(j + 1) % 3];
+ int v3 = triangle_vertices[i][(j + 2) % 3];
+ if (abs(EdgeDiff[e][0]) == 1 && abs(EdgeDiff[e][1]) == 1) {
+ DEdge edge(v1, v2);
+ if (quads.count(edge))
+ quads[edge].second = Vector3i(v1, v2, v3);
+ else
+ quads[edge] = std::make_pair(Vector3i(v1, v2, v3), Vector3i(-1, -1, -1));
+ }
+ }
+ }
+
+ for (auto& p : quads) {
+ if (p.second.second[0] != -1 && p.second.first[2] != p.second.second[2]) {
+ F_compact.push_back(Vector4i(p.second.first[1], p.second.first[2], p.second.first[0],
+ p.second.second[2]));
+ }
+ }
+ std::swap(Vs, Vset);
+ std::swap(O_compact, O);
+ std::swap(N_compact, N);
+ std::swap(Q_compact, Q);
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+
+ while (true) {
+ std::vector<int> erasedF(F_compact.size(), 0);
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ for (int k = j + 1; k < 4; ++k) {
+ if (F_compact[i][j] == F_compact[i][k]) {
+ erasedF[i] = 1;
+ }
+ }
+ }
+ }
+ for (int i = 0; i < O_compact.size(); ++i) {
+ int v = 0;
+ int e0 = V2E_compact[i];
+ if (e0 == -1) continue;
+ std::vector<int> dedges;
+ int e = e0;
+ do {
+ dedges.push_back(e);
+ v += 1;
+ e = e / 4 * 4 + (e + 3) % 4;
+ e = E2E_compact[e];
+ } while (e != e0 && e != -1);
+ if (e == -1) {
+ int e = e0;
+ while (true) {
+ e = E2E_compact[e];
+ if (e == -1) break;
+ e = e / 4 * 4 + (e + 1) % 4;
+ v += 1;
+ dedges.push_back(e);
+ }
+ }
+ if (v == 2) {
+ // erasedF[dedges[1] / 4] = 1;
+ // F_compact[dedges[0]/4][dedges[0]%4] =
+ // F_compact[dedges[1]/4][(dedges[1]+2)%4];
+ }
+ }
+ offset = 0;
+ for (int i = 0; i < F_compact.size(); ++i) {
+ if (erasedF[i] == 0) F_compact[offset++] = F_compact[i];
+ }
+ if (offset == F_compact.size()) break;
+ F_compact.resize(offset);
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+ }
+ FixHoles();
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+
+ /*
+ for (auto& p : flip_vertices) {
+ int deid0 = V2E_compact[p];
+ int deid = deid0;
+ std::list<int> dedges;
+ if (deid0 != -1) {
+ do {
+ dedges.push_back(deid);
+ deid = E2E_compact[deid/4*4 + (deid+3) % 4];
+ } while (deid != -1 && deid != deid0);
+ if (deid == -1) {
+ deid = deid0;
+ do {
+ deid = E2E_compact[deid];
+ if (deid == -1)
+ break;
+ deid = deid/4*4 + (deid +1) % 4;
+ dedges.push_front(deid);
+ } while (deid != -1 && deid != deid0);
+ }
+ std::set<int> eraseF;
+ std::set<int> valid_dedges;
+ std::set<int> boundaries;
+ std::vector<int> loop_vertices;
+ for (auto& dedge : dedges) {
+ int f = dedge / 4;
+ eraseF.insert(f);
+ valid_dedges.insert(E2E_compact[f * 4 + (dedge+1)%4]);
+ valid_dedges.insert(E2E_compact[f * 4 + (dedge+2)%4]);
+ loop_vertices.push_back(F_compact[f][(dedge+1)%4]);
+ loop_vertices.push_back(F_compact[f][(dedge+2)%4]);
+ boundaries.insert(F_compact[f][(dedge+1)%4]);
+ boundaries.insert(F_compact[f][(dedge+2)%4]);
+ }
+ int offset = 0;
+ auto it = eraseF.begin();
+ for (int i = 0; i < F_compact.size(); ++i) {
+ if (it == eraseF.end() || i != *it) {
+ bool need_erase = false;
+ for (int j = 0; j < 4; ++j) {
+ if (valid_dedges.count(i * 4 + j) == 0 && boundaries.count(F_compact[i][j])
+ && boundaries.count(F_compact[i][(j + 1) % 4])) { need_erase = true;
+ }
+ }
+ if (!need_erase)
+ F_compact[offset++] = F_compact[i];
+ } else {
+ it++;
+ }
+ }
+ F_compact.resize(offset);
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact,
+ boundary_compact, nonManifold_compact); std::reverse(loop_vertices.begin(),
+ loop_vertices.end()); FixHoles(loop_vertices); compute_direct_graph_quad(O_compact, F_compact,
+ V2E_compact, E2E_compact, boundary_compact, nonManifold_compact);
+ }
+ }
+ FixHoles();
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+ */
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/parametrizer-int.cpp b/extern/quadriflow/src/parametrizer-int.cpp
new file mode 100644
index 00000000000..08fa36c1baa
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer-int.cpp
@@ -0,0 +1,425 @@
+#include "parametrizer.hpp"
+
+#include <queue>
+#include <unordered_map>
+#include <vector>
+#include <random>
+#include "optimizer.hpp"
+
+namespace qflow {
+
+
+void Parametrizer::BuildEdgeInfo() {
+ auto& F = hierarchy.mF;
+ auto& E2E = hierarchy.mE2E;
+
+ edge_diff.clear();
+ edge_values.clear();
+ face_edgeIds.resize(F.cols(), Vector3i(-1, -1, -1));
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int k1 = j, k2 = (j + 1) % 3;
+ int v1 = F(k1, i);
+ int v2 = F(k2, i);
+ DEdge e2(v1, v2);
+ Vector2i diff2;
+ int rank2;
+ if (v1 > v2) {
+ rank2 = pos_rank(k2, i);
+ diff2 =
+ rshift90(Vector2i(-pos_index(k1 * 2, i), -pos_index(k1 * 2 + 1, i)), rank2);
+ } else {
+ rank2 = pos_rank(k1, i);
+ diff2 = rshift90(Vector2i(pos_index(k1 * 2, i), pos_index(k1 * 2 + 1, i)), rank2);
+ }
+ int current_eid = i * 3 + k1;
+ int eid = E2E[current_eid];
+ int eID1 = face_edgeIds[current_eid / 3][current_eid % 3];
+ int eID2 = -1;
+ if (eID1 == -1) {
+ eID2 = edge_values.size();
+ edge_values.push_back(e2);
+ edge_diff.push_back(diff2);
+ face_edgeIds[i][k1] = eID2;
+ if (eid != -1) face_edgeIds[eid / 3][eid % 3] = eID2;
+ } else if (!singularities.count(i)) {
+ eID2 = face_edgeIds[eid / 3][eid % 3];
+ edge_diff[eID2] = diff2;
+ }
+ }
+ }
+}
+
+void Parametrizer::BuildIntegerConstraints() {
+ auto& F = hierarchy.mF;
+ auto& Q = hierarchy.mQ[0];
+ auto& N = hierarchy.mN[0];
+ face_edgeOrients.resize(F.cols());
+
+ //Random number generator (for shuffling)
+ std::random_device rd;
+ std::mt19937 g(rd());
+ g.seed(hierarchy.rng_seed);
+
+ // undirected edge to direct edge
+ std::vector<std::pair<int, int>> E2D(edge_diff.size(), std::make_pair(-1, -1));
+ for (int i = 0; i < F.cols(); ++i) {
+ int v0 = F(0, i);
+ int v1 = F(1, i);
+ int v2 = F(2, i);
+ DEdge e0(v0, v1), e1(v1, v2), e2(v2, v0);
+ const Vector3i& eid = face_edgeIds[i];
+ Vector2i variable_id[3];
+ for (int i = 0; i < 3; ++i) {
+ variable_id[i] = Vector2i(eid[i] * 2 + 1, eid[i] * 2 + 2);
+ }
+ auto index1 =
+ compat_orientation_extrinsic_index_4(Q.col(v0), N.col(v0), Q.col(v1), N.col(v1));
+ auto index2 =
+ compat_orientation_extrinsic_index_4(Q.col(v0), N.col(v0), Q.col(v2), N.col(v2));
+
+ int rank1 = (index1.first - index1.second + 4) % 4; // v1 -> v0
+ int rank2 = (index2.first - index2.second + 4) % 4; // v2 -> v0
+ int orients[3] = {0}; // == {0, 0, 0}
+ if (v1 < v0) {
+ variable_id[0] = -rshift90(variable_id[0], rank1);
+ orients[0] = (rank1 + 2) % 4;
+ } else {
+ orients[0] = 0;
+ }
+ if (v2 < v1) {
+ variable_id[1] = -rshift90(variable_id[1], rank2);
+ orients[1] = (rank2 + 2) % 4;
+ } else {
+ variable_id[1] = rshift90(variable_id[1], rank1);
+ orients[1] = rank1;
+ }
+ if (v2 < v0) {
+ variable_id[2] = rshift90(variable_id[2], rank2);
+ orients[2] = rank2;
+ } else {
+ variable_id[2] = -variable_id[2];
+ orients[2] = 2;
+ }
+ face_edgeOrients[i] = Vector3i(orients[0], orients[1], orients[2]);
+ for (int j = 0; j < 3; ++j) {
+ int eid = face_edgeIds[i][j];
+ if (E2D[eid].first == -1)
+ E2D[eid].first = i * 3 + j;
+ else
+ E2D[eid].second = i * 3 + j;
+ }
+ }
+
+ // a face disajoint tree
+ DisajointOrientTree disajoint_orient_tree = DisajointOrientTree(F.cols());
+ // merge the whole face graph except for the singularity in which there exists a spanning tree
+ // which contains consistent orientation
+ std::vector<int> sharpUE(E2D.size());
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i]) {
+ sharpUE[face_edgeIds[i / 3][i % 3]] = 1;
+ }
+ }
+
+ for (int i = 0; i < E2D.size(); ++i) {
+ auto& edge_c = E2D[i];
+ int f0 = edge_c.first / 3;
+ int f1 = edge_c.second / 3;
+ if (edge_c.first == -1 || edge_c.second == -1) continue;
+ if (singularities.count(f0) || singularities.count(f1) || sharpUE[i]) continue;
+ int orient1 = face_edgeOrients[f0][edge_c.first % 3];
+ int orient0 = (face_edgeOrients[f1][edge_c.second % 3] + 2) % 4;
+ disajoint_orient_tree.Merge(f0, f1, orient0, orient1);
+ }
+
+ // merge singularity later
+ for (auto& f : singularities) {
+ for (int i = 0; i < 3; ++i) {
+ if (sharpUE[face_edgeIds[f.first][i]]) continue;
+ auto& edge_c = E2D[face_edgeIds[f.first][i]];
+ if (edge_c.first == -1 || edge_c.second == -1) continue;
+ int v0 = edge_c.first / 3;
+ int v1 = edge_c.second / 3;
+ int orient1 = face_edgeOrients[v0][edge_c.first % 3];
+ int orient0 = (face_edgeOrients[v1][edge_c.second % 3] + 2) % 4;
+ disajoint_orient_tree.Merge(v0, v1, orient0, orient1);
+ }
+ }
+
+ for (int i = 0; i < sharpUE.size(); ++i) {
+ if (sharpUE[i] == 0) continue;
+ auto& edge_c = E2D[i];
+ if (edge_c.first == -1 || edge_c.second == -1) continue;
+ int f0 = edge_c.first / 3;
+ int f1 = edge_c.second / 3;
+ int orient1 = face_edgeOrients[f0][edge_c.first % 3];
+ int orient0 = (face_edgeOrients[f1][edge_c.second % 3] + 2) % 4;
+ disajoint_orient_tree.Merge(f0, f1, orient0, orient1);
+ }
+
+ // all the face has the same parent. we rotate every face to the space of that parent.
+ for (int i = 0; i < face_edgeOrients.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ face_edgeOrients[i][j] =
+ (face_edgeOrients[i][j] + disajoint_orient_tree.Orient(i)) % 4;
+ }
+ }
+
+ std::vector<int> sharp_colors(face_edgeIds.size(), -1);
+ int num_sharp_component = 0;
+ // label the connected component connected by non-fixed edges
+ // we need this because we need sink flow (demand) == source flow (supply) for each component
+ // rather than global
+ for (int i = 0; i < sharp_colors.size(); ++i) {
+ if (sharp_colors[i] != -1) continue;
+ sharp_colors[i] = num_sharp_component;
+ std::queue<int> q;
+ q.push(i);
+ int counter = 0;
+ while (!q.empty()) {
+ int v = q.front();
+ q.pop();
+ for (int i = 0; i < 3; ++i) {
+ int e = face_edgeIds[v][i];
+ int deid1 = E2D[e].first;
+ int deid2 = E2D[e].second;
+ if (deid1 == -1 || deid2 == -1) continue;
+ if (abs(face_edgeOrients[deid1 / 3][deid1 % 3] -
+ face_edgeOrients[deid2 / 3][deid2 % 3] + 4) %
+ 4 !=
+ 2 ||
+ sharpUE[e]) {
+ continue;
+ }
+ for (int k = 0; k < 2; ++k) {
+ int f = (k == 0) ? E2D[e].first / 3 : E2D[e].second / 3;
+ if (sharp_colors[f] == -1) {
+ sharp_colors[f] = num_sharp_component;
+ q.push(f);
+ }
+ }
+ }
+ counter += 1;
+ }
+ num_sharp_component += 1;
+ }
+ {
+ std::vector<int> total_flows(num_sharp_component);
+ // check if each component is full-flow
+ for (int i = 0; i < face_edgeIds.size(); ++i) {
+ Vector2i diff(0, 0);
+ for (int j = 0; j < 3; ++j) {
+ int orient = face_edgeOrients[i][j];
+ diff += rshift90(edge_diff[face_edgeIds[i][j]], orient);
+ }
+ total_flows[sharp_colors[i]] += diff[0] + diff[1];
+ }
+
+ // build "variable"
+ variables.resize(edge_diff.size() * 2, std::make_pair(Vector2i(-1, -1), 0));
+ for (int i = 0; i < face_edgeIds.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ Vector2i sign = rshift90(Vector2i(1, 1), face_edgeOrients[i][j]);
+ int eid = face_edgeIds[i][j];
+ Vector2i index = rshift90(Vector2i(eid * 2, eid * 2 + 1), face_edgeOrients[i][j]);
+ for (int k = 0; k < 2; ++k) {
+ auto& p = variables[abs(index[k])];
+ if (p.first[0] == -1)
+ p.first[0] = i * 2 + k;
+ else
+ p.first[1] = i * 2 + k;
+ p.second += sign[k];
+ }
+ }
+ }
+
+ // fixed variable that might be manually modified.
+ // modified_variables[component_od][].first = fixed_variable_id
+ // modified_variables[component_od][].second = 1 if two positive signs -1 if two negative
+ // signs
+ std::vector<std::vector<std::pair<int, int>>> modified_variables[2];
+ for (int i = 0; i < 2; ++i) modified_variables[i].resize(total_flows.size());
+ for (int i = 0; i < variables.size(); ++i) {
+ if ((variables[i].first[1] == -1 || variables[i].second != 0) &&
+ allow_changes[i] == 1) {
+ int find = sharp_colors[variables[i].first[0] / 2];
+ int step = std::abs(variables[i].second) % 2;
+ if (total_flows[find] > 0) {
+ if (variables[i].second > 0 && edge_diff[i / 2][i % 2] > -1) {
+ modified_variables[step][find].push_back(std::make_pair(i, -1));
+ }
+ if (variables[i].second < 0 && edge_diff[i / 2][i % 2] < 1) {
+ modified_variables[step][find].push_back(std::make_pair(i, 1));
+ }
+ } else if (total_flows[find] < 0) {
+ if (variables[i].second < 0 && edge_diff[i / 2][i % 2] > -1) {
+ modified_variables[step][find].push_back(std::make_pair(i, -1));
+ }
+ if (variables[i].second > 0 && edge_diff[i / 2][i % 2] < 1) {
+ modified_variables[step][find].push_back(std::make_pair(i, 1));
+ }
+ }
+ }
+ }
+
+ // uniformly random manually modify variables so that the network has full flow.
+ for (int i = 0; i < 2; ++i)
+ for (auto& modified_var : modified_variables[i])
+ std::shuffle(modified_var.begin(), modified_var.end(), g);
+
+ for (int j = 0; j < total_flows.size(); ++j) {
+ for (int ii = 0; ii < 2; ++ii) {
+ if (total_flows[j] == 0) continue;
+ int max_num;
+ if (ii == 0)
+ max_num =
+ std::min(abs(total_flows[j]) / 2, (int)modified_variables[ii][j].size());
+ else
+ max_num = std::min(abs(total_flows[j]), (int)modified_variables[ii][j].size());
+ int dir = (total_flows[j] > 0) ? -1 : 1;
+ for (int i = 0; i < max_num; ++i) {
+ auto& info = modified_variables[ii][j][i];
+ edge_diff[info.first / 2][info.first % 2] += info.second;
+ if (ii == 0)
+ total_flows[j] += 2 * dir;
+ else
+ total_flows[j] += dir;
+ }
+ }
+ }
+ }
+
+ std::vector<Vector4i> edge_to_constraints(E2D.size() * 2, Vector4i(-1, 0, -1, 0));
+ for (int i = 0; i < face_edgeIds.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int e = face_edgeIds[i][j];
+ Vector2i index = rshift90(Vector2i(e * 2 + 1, e * 2 + 2), face_edgeOrients[i][j]);
+ for (int k = 0; k < 2; ++k) {
+ int l = abs(index[k]);
+ int s = index[k] / l;
+ int ind = l - 1;
+ int equationID = i * 2 + k;
+ if (edge_to_constraints[ind][0] == -1) {
+ edge_to_constraints[ind][0] = equationID;
+ edge_to_constraints[ind][1] = s;
+ } else {
+ edge_to_constraints[ind][2] = equationID;
+ edge_to_constraints[ind][3] = s;
+ }
+ }
+ }
+ }
+ std::vector<std::pair<Vector2i, int>> arcs;
+ std::vector<int> arc_ids;
+ DisajointTree tree(face_edgeIds.size() * 2);
+ for (int i = 0; i < edge_to_constraints.size(); ++i) {
+ if (allow_changes[i] == 0) continue;
+ if (edge_to_constraints[i][0] == -1 || edge_to_constraints[i][2] == -1) continue;
+ if (edge_to_constraints[i][1] == -edge_to_constraints[i][3]) {
+ int v1 = edge_to_constraints[i][0];
+ int v2 = edge_to_constraints[i][2];
+ tree.Merge(v1, v2);
+ if (edge_to_constraints[i][1] < 0) std::swap(v1, v2);
+ int current_v = edge_diff[i / 2][i % 2];
+ arcs.push_back(std::make_pair(Vector2i(v1, v2), current_v));
+ }
+ }
+ tree.BuildCompactParent();
+ std::vector<int> total_flows(tree.CompactNum());
+ // check if each component is full-flow
+ for (int i = 0; i < face_edgeIds.size(); ++i) {
+ Vector2i diff(0, 0);
+ for (int j = 0; j < 3; ++j) {
+ int orient = face_edgeOrients[i][j];
+ diff += rshift90(edge_diff[face_edgeIds[i][j]], orient);
+ }
+ for (int j = 0; j < 2; ++j) {
+ total_flows[tree.Index(i * 2 + j)] += diff[j];
+ }
+ }
+
+ // build "variable"
+ variables.resize(edge_diff.size() * 2);
+ for (int i = 0; i < variables.size(); ++i) {
+ variables[i].first = Vector2i(-1, -1);
+ variables[i].second = 0;
+ }
+ for (int i = 0; i < face_edgeIds.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ Vector2i sign = rshift90(Vector2i(1, 1), face_edgeOrients[i][j]);
+ int eid = face_edgeIds[i][j];
+ Vector2i index = rshift90(Vector2i(eid * 2, eid * 2 + 1), face_edgeOrients[i][j]);
+ for (int k = 0; k < 2; ++k) {
+ auto& p = variables[abs(index[k])];
+ if (p.first[0] == -1)
+ p.first[0] = i * 2 + k;
+ else
+ p.first[1] = i * 2 + k;
+ p.second += sign[k];
+ }
+ }
+ }
+
+ // fixed variable that might be manually modified.
+ // modified_variables[component_od][].first = fixed_variable_id
+ // modified_variables[component_od][].second = 1 if two positive signs -1 if two negative signs
+ std::vector<std::vector<std::pair<int, int>>> modified_variables[2];
+ for (int i = 0; i < 2; ++i) {
+ modified_variables[i].resize(total_flows.size());
+ }
+ for (int i = 0; i < variables.size(); ++i) {
+ if ((variables[i].first[1] == -1 || variables[i].second != 0) && allow_changes[i] == 1) {
+ int find = tree.Index(variables[i].first[0]);
+ int step = abs(variables[i].second) % 2;
+ if (total_flows[find] > 0) {
+ if (variables[i].second > 0 && edge_diff[i / 2][i % 2] > -1) {
+ modified_variables[step][find].push_back(std::make_pair(i, -1));
+ }
+ if (variables[i].second < 0 && edge_diff[i / 2][i % 2] < 1) {
+ modified_variables[step][find].push_back(std::make_pair(i, 1));
+ }
+ } else if (total_flows[find] < 0) {
+ if (variables[i].second < 0 && edge_diff[i / 2][i % 2] > -1) {
+ modified_variables[step][find].push_back(std::make_pair(i, -1));
+ }
+ if (variables[i].second > 0 && edge_diff[i / 2][i % 2] < 1) {
+ modified_variables[step][find].push_back(std::make_pair(i, 1));
+ }
+ }
+ }
+ }
+
+ // uniformly random manually modify variables so that the network has full flow.
+ for (int j = 0; j < 2; ++j) {
+ for (auto& modified_var : modified_variables[j])
+ std::shuffle(modified_var.begin(), modified_var.end(), g);
+ }
+ for (int j = 0; j < total_flows.size(); ++j) {
+ for (int ii = 0; ii < 2; ++ii) {
+ if (total_flows[j] == 0) continue;
+ int max_num;
+ if (ii == 0)
+ max_num = std::min(abs(total_flows[j]) / 2, (int)modified_variables[ii][j].size());
+ else
+ max_num = std::min(abs(total_flows[j]), (int)modified_variables[ii][j].size());
+ int dir = (total_flows[j] > 0) ? -1 : 1;
+ for (int i = 0; i < max_num; ++i) {
+ auto& info = modified_variables[ii][j][i];
+ edge_diff[info.first / 2][info.first % 2] += info.second;
+ if (ii == 0)
+ total_flows[j] += 2 * dir;
+ else
+ total_flows[j] += dir;
+ }
+ }
+ }
+}
+
+void Parametrizer::ComputeMaxFlow() {
+ hierarchy.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, 1);
+ Optimizer::optimize_integer_constraints(hierarchy, singularities, flag_minimum_cost_flow);
+ hierarchy.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff);
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/parametrizer-mesh.cpp b/extern/quadriflow/src/parametrizer-mesh.cpp
new file mode 100644
index 00000000000..19effe92390
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer-mesh.cpp
@@ -0,0 +1,615 @@
+#include "config.hpp"
+#include "dedge.hpp"
+#include "field-math.hpp"
+#include "loader.hpp"
+#include "merge-vertex.hpp"
+#include "parametrizer.hpp"
+#include "subdivide.hpp"
+#include "dedge.hpp"
+#include <queue>
+
+namespace qflow {
+
+void Parametrizer::NormalizeMesh() {
+ double maxV[3] = {-1e30, -1e30, -1e30};
+ double minV[3] = {1e30, 1e30, 1e30};
+
+ for (int i = 0; i < V.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ maxV[j] = std::max(maxV[j], V(j, i));
+ minV[j] = std::min(minV[j], V(j, i));
+ }
+ }
+ double scale =
+ std::max(std::max(maxV[0] - minV[0], maxV[1] - minV[1]), maxV[2] - minV[2]) * 0.5;
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ V(j, i) = (V(j, i) - (maxV[j] + minV[j]) * 0.5) / scale;
+ }
+ }
+#ifdef LOG_OUTPUT
+ printf("vertices size: %d\n", (int)V.cols());
+ printf("faces size: %d\n", (int)F.cols());
+#endif
+ this->normalize_scale = scale;
+ this->normalize_offset = Vector3d(0.5 * (maxV[0] + minV[0]), 0.5 * (maxV[1] + minV[1]), 0.5 * (maxV[2] + minV[2]));
+ // merge_close(V, F, 1e-6);
+}
+
+void Parametrizer::Load(const char* filename) {
+ load(filename, V, F);
+ NormalizeMesh();
+}
+
+void Parametrizer::Initialize(int faces) {
+ ComputeMeshStatus();
+ //ComputeCurvature(V, F, rho);
+ rho.resize(V.cols(), 1);
+ for (int i = 0; i < V.cols(); ++i) {
+ rho[i] = 1;
+ }
+#ifdef PERFORMANCE_TEST
+ scale = sqrt(surface_area / (V.cols() * 10));
+#else
+ if (faces <= 0) {
+ scale = sqrt(surface_area / V.cols());
+ } else {
+ scale = std::sqrt(surface_area / faces);
+ }
+#endif
+ double target_len = std::min(scale / 2, average_edge_length * 2);
+#ifdef PERFORMANCE_TEST
+ scale = sqrt(surface_area / V.cols());
+#endif
+
+ if (target_len < max_edge_length) {
+ while (!compute_direct_graph(V, F, V2E, E2E, boundary, nonManifold))
+ ;
+ subdivide(F, V, rho, V2E, E2E, boundary, nonManifold, target_len);
+ }
+
+ while (!compute_direct_graph(V, F, V2E, E2E, boundary, nonManifold))
+ ;
+ generate_adjacency_matrix_uniform(F, V2E, E2E, nonManifold, adj);
+
+ for (int iter = 0; iter < 5; ++iter) {
+ VectorXd r(rho.size());
+ for (int i = 0; i < rho.size(); ++i) {
+ r[i] = rho[i];
+ for (auto& id : adj[i]) {
+ r[i] = std::min(r[i], rho[id.id]);
+ }
+ }
+ rho = r;
+ }
+ ComputeSharpEdges();
+ ComputeSmoothNormal();
+ ComputeVertexArea();
+
+ if (flag_adaptive_scale)
+ ComputeInverseAffine();
+
+#ifdef LOG_OUTPUT
+ printf("V: %d F: %d\n", (int)V.cols(), (int)F.cols());
+#endif
+ hierarchy.mA[0] = std::move(A);
+ hierarchy.mAdj[0] = std::move(adj);
+ hierarchy.mN[0] = std::move(N);
+ hierarchy.mV[0] = std::move(V);
+ hierarchy.mE2E = std::move(E2E);
+ hierarchy.mF = std::move(F);
+ hierarchy.Initialize(scale, flag_adaptive_scale);
+}
+
+void Parametrizer::ComputeMeshStatus() {
+ surface_area = 0;
+ average_edge_length = 0;
+ max_edge_length = 0;
+ for (int f = 0; f < F.cols(); ++f) {
+ Vector3d v[3] = {V.col(F(0, f)), V.col(F(1, f)), V.col(F(2, f))};
+ double area = 0.5f * (v[1] - v[0]).cross(v[2] - v[0]).norm();
+ surface_area += area;
+ for (int i = 0; i < 3; ++i) {
+ double len = (v[(i + 1) % 3] - v[i]).norm();
+ average_edge_length += len;
+ if (len > max_edge_length) max_edge_length = len;
+ }
+ }
+ average_edge_length /= (F.cols() * 3);
+}
+
+void Parametrizer::ComputeSharpEdges() {
+ sharp_edges.resize(F.cols() * 3, 0);
+
+ if (flag_preserve_boundary) {
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ int re = E2E[i];
+ if (re == -1) {
+ sharp_edges[i] = 1;
+ }
+ }
+ }
+
+ if (flag_preserve_sharp == 0)
+ return;
+
+ std::vector<Vector3d> face_normals(F.cols());
+ for (int i = 0; i < F.cols(); ++i) {
+ Vector3d p1 = V.col(F(0, i));
+ Vector3d p2 = V.col(F(1, i));
+ Vector3d p3 = V.col(F(2, i));
+ face_normals[i] = (p2 - p1).cross(p3 - p1).normalized();
+ }
+
+ double cos_thres = cos(60.0/180.0*3.141592654);
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ int e = i;
+ int re = E2E[e];
+ Vector3d& n1 = face_normals[e/3];
+ Vector3d& n2 = face_normals[re/3];
+ if (n1.dot(n2) < cos_thres) {
+ sharp_edges[i] = 1;
+ }
+ }
+}
+
+void Parametrizer::ComputeSharpO() {
+ auto& F = hierarchy.mF;
+ auto& V = hierarchy.mV[0];
+ auto& O = hierarchy.mO[0];
+ auto& E2E = hierarchy.mE2E;
+ DisajointTree tree(V.cols());
+ for (int i = 0; i < edge_diff.size(); ++i) {
+ if (edge_diff[i][0] == 0 && edge_diff[i][1] == 0) {
+ tree.Merge(edge_values[i].x, edge_values[i].y);
+ }
+ }
+ std::map<DEdge, std::vector<Vector3d> > edge_normals;
+ for (int i = 0; i < F.cols(); ++i) {
+ int pv[] = {tree.Parent(F(0, i)), tree.Parent(F(1, i)), tree.Parent(F(2, i))};
+ if (pv[0] == pv[1] || pv[1] == pv[2] || pv[2] == pv[0])
+ continue;
+ DEdge e[] = {DEdge(pv[0], pv[1]), DEdge(pv[1], pv[2]), DEdge(pv[2], pv[0])};
+ Vector3d d1 = O.col(F(1, i)) - O.col(F(0, i));
+ Vector3d d2 = O.col(F(2, i)) - O.col(F(0, i));
+ Vector3d n = d1.cross(d2).normalized();
+ for (int j = 0; j < 3; ++j) {
+ if (edge_normals.count(e[j]) == 0)
+ edge_normals[e[j]] = std::vector<Vector3d>();
+ edge_normals[e[j]].push_back(n);
+ }
+ }
+ std::map<DEdge, int> sharps;
+ for (auto& info : edge_normals) {
+ auto& normals = info.second;
+ bool sharp = false;
+ for (int i = 0; i < normals.size(); ++i) {
+ for (int j = i + 1; j < normals.size(); ++j) {
+ if (normals[i].dot(normals[j]) < cos(60.0 / 180.0 * 3.141592654)) {
+ sharp = true;
+ break;
+ }
+ }
+ if (sharp)
+ break;
+ }
+ if (sharp) {
+ int s = sharps.size();
+ sharps[info.first] = s;
+ }
+ }
+ for (auto& s : sharp_edges)
+ s = 0;
+ std::vector<int> sharp_hash(sharps.size(), 0);
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int v1 = tree.Parent(F(j, i));
+ int v2 = tree.Parent(F((j + 1) % 3, i));
+ DEdge e(v1, v2);
+ if (sharps.count(e) == 0)
+ continue;
+ int id = sharps[e];
+ if (sharp_hash[id])
+ continue;
+ sharp_hash[id] = 1;
+ sharp_edges[i * 3 + j] = 1;
+ sharp_edges[E2E[i * 3 + j]] = 1;
+ }
+ }
+
+}
+
+void Parametrizer::ComputeSmoothNormal() {
+ /* Compute face normals */
+ Nf.resize(3, F.cols());
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int f = 0; f < F.cols(); ++f) {
+ Vector3d v0 = V.col(F(0, f)), v1 = V.col(F(1, f)), v2 = V.col(F(2, f)),
+ n = (v1 - v0).cross(v2 - v0);
+ double norm = n.norm();
+ if (norm < RCPOVERFLOW) {
+ n = Vector3d::UnitX();
+ } else {
+ n /= norm;
+ }
+ Nf.col(f) = n;
+ }
+
+ N.resize(3, V.cols());
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V2E.rows(); ++i) {
+ int edge = V2E[i];
+ if (nonManifold[i] || edge == -1) {
+ N.col(i) = Vector3d::UnitX();
+ continue;
+ }
+
+
+ int stop = edge;
+ do {
+ if (sharp_edges[edge])
+ break;
+ edge = E2E[edge];
+ if (edge != -1)
+ edge = dedge_next_3(edge);
+ } while (edge != stop && edge != -1);
+ if (edge == -1)
+ edge = stop;
+ else
+ stop = edge;
+ Vector3d normal = Vector3d::Zero();
+ do {
+ int idx = edge % 3;
+
+ Vector3d d0 = V.col(F((idx + 1) % 3, edge / 3)) - V.col(i);
+ Vector3d d1 = V.col(F((idx + 2) % 3, edge / 3)) - V.col(i);
+ double angle = fast_acos(d0.dot(d1) / std::sqrt(d0.squaredNorm() * d1.squaredNorm()));
+
+ /* "Computing Vertex Normals from Polygonal Facets"
+ by Grit Thuermer and Charles A. Wuethrich, JGT 1998, Vol 3 */
+ if (std::isfinite(angle)) normal += Nf.col(edge / 3) * angle;
+
+ int opp = E2E[edge];
+ if (opp == -1) break;
+
+ edge = dedge_next_3(opp);
+ if (sharp_edges[edge])
+ break;
+ } while (edge != stop);
+ double norm = normal.norm();
+ N.col(i) = norm > RCPOVERFLOW ? Vector3d(normal / norm) : Vector3d::UnitX();
+ }
+}
+
+void Parametrizer::ComputeVertexArea() {
+ A.resize(V.cols());
+ A.setZero();
+
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < V2E.size(); ++i) {
+ int edge = V2E[i], stop = edge;
+ if (nonManifold[i] || edge == -1) continue;
+ double vertex_area = 0;
+ do {
+ int ep = dedge_prev_3(edge), en = dedge_next_3(edge);
+
+ Vector3d v = V.col(F(edge % 3, edge / 3));
+ Vector3d vn = V.col(F(en % 3, en / 3));
+ Vector3d vp = V.col(F(ep % 3, ep / 3));
+
+ Vector3d face_center = (v + vp + vn) * (1.0f / 3.0f);
+ Vector3d prev = (v + vp) * 0.5f;
+ Vector3d next = (v + vn) * 0.5f;
+
+ vertex_area += 0.5f * ((v - prev).cross(v - face_center).norm() +
+ (v - next).cross(v - face_center).norm());
+
+ int opp = E2E[edge];
+ if (opp == -1) break;
+ edge = dedge_next_3(opp);
+ } while (edge != stop);
+
+ A[i] = vertex_area;
+ }
+}
+
+void Parametrizer::FixValence()
+{
+ // Remove Valence 2
+ while (true) {
+ bool update = false;
+ std::vector<int> marks(V2E_compact.size(), 0);
+ std::vector<int> erasedF(F_compact.size(), 0);
+ for (int i = 0; i < V2E_compact.size(); ++i) {
+ int deid0 = V2E_compact[i];
+ if (marks[i] || deid0 == -1)
+ continue;
+ int deid = deid0;
+ std::vector<int> dedges;
+ do {
+ dedges.push_back(deid);
+ int deid1 = deid / 4 * 4 + (deid + 3) % 4;
+ deid = E2E_compact[deid1];
+ } while (deid != deid0 && deid != -1);
+ if (dedges.size() == 2) {
+ int v1 = F_compact[dedges[0]/4][(dedges[0] + 1)%4];
+ int v2 = F_compact[dedges[0]/4][(dedges[0] + 2)%4];
+ int v3 = F_compact[dedges[1]/4][(dedges[1] + 1)%4];
+ int v4 = F_compact[dedges[1]/4][(dedges[1] + 2)%4];
+ if (marks[v1] || marks[v2] || marks[v3] || marks[v4])
+ continue;
+ marks[v1] = true;
+ marks[v2] = true;
+ marks[v3] = true;
+ marks[v4] = true;
+ if (v1 == v2 || v1 == v3 || v1 == v4 || v2 == v3 || v2 == v4 || v3 == v4) {
+ erasedF[dedges[0]/4] = 1;
+ } else {
+ F_compact[dedges[0]/4] = Vector4i(v1, v2, v3, v4);
+ }
+ erasedF[dedges[1]/4] = 1;
+ update = true;
+ }
+ }
+ if (update) {
+ int top = 0;
+ for (int i = 0; i < erasedF.size(); ++i) {
+ if (erasedF[i] == 0) {
+ F_compact[top++] = F_compact[i];
+ }
+ }
+ F_compact.resize(top);
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+ } else {
+ break;
+ }
+ }
+ std::vector<std::vector<int> > v_dedges(V2E_compact.size());
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 4; ++j) {
+ v_dedges[F_compact[i][j]].push_back(i * 4 + j);
+ }
+ }
+ int top = V2E_compact.size();
+ for (int i = 0; i < v_dedges.size(); ++i) {
+ std::map<int, int> groups;
+ int group_id = 0;
+ for (int j = 0; j < v_dedges[i].size(); ++j) {
+ int deid = v_dedges[i][j];
+ if (groups.count(deid))
+ continue;
+ int deid0 = deid;
+ do {
+ groups[deid] = group_id;
+ deid = deid / 4 * 4 + (deid + 3) % 4;
+ deid = E2E_compact[deid];
+ } while (deid != deid0 && deid != -1);
+ if (deid == -1) {
+ deid = deid0;
+ while (E2E_compact[deid] != -1) {
+ deid = E2E_compact[deid];
+ deid = deid / 4 * 4 + (deid + 1) % 4;
+ groups[deid] = group_id;
+ }
+ }
+ group_id += 1;
+ }
+ if (group_id > 1) {
+ for (auto& g : groups) {
+ if (g.second >= 1)
+ F_compact[g.first/4][g.first%4] = top - 1 + g.second;
+ }
+ for (int j = 1; j < group_id; ++j) {
+ Vset.push_back(Vset[i]);
+ N_compact.push_back(N_compact[i]);
+ Q_compact.push_back(Q_compact[i]);
+ O_compact.push_back(O_compact[i]);
+ }
+ top = O_compact.size();
+ }
+ }
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+
+ // Decrease Valence
+ while (true) {
+ bool update = false;
+ std::vector<int> marks(V2E_compact.size(), 0);
+ std::vector<int> valences(V2E_compact.size(), 0);
+ for (int i = 0; i < V2E_compact.size(); ++i) {
+ int deid0 = V2E_compact[i];
+ if (deid0 == -1)
+ continue;
+ int deid = deid0;
+ int count = 0;
+ do {
+ count += 1;
+ int deid1 = E2E_compact[deid];
+ if (deid1 == -1) {
+ count += 1;
+ break;
+ }
+ deid = deid1 / 4 * 4 + (deid1 + 1) % 4;
+ } while (deid != deid0 && deid != -1);
+ if (deid == -1)
+ count += 1;
+ valences[i] = count;
+ }
+ std::priority_queue<std::pair<int, int> > prior_queue;
+ for (int i = 0; i < valences.size(); ++i) {
+ if (valences[i] > 5)
+ prior_queue.push(std::make_pair(valences[i], i));
+ }
+ while (!prior_queue.empty()) {
+ auto info = prior_queue.top();
+ prior_queue.pop();
+ if (marks[info.second])
+ continue;
+ int deid0 = V2E_compact[info.second];
+ if (deid0 == -1)
+ continue;
+ int deid = deid0;
+ std::vector<int> loop_vertices, loop_dedges;;
+ bool marked = false;
+ do {
+ int v = F_compact[deid/4][(deid+1)%4];
+ loop_dedges.push_back(deid);
+ loop_vertices.push_back(v);
+ if (marks[v])
+ marked = true;
+ int deid1 = E2E_compact[deid];
+ if (deid1 == -1)
+ break;
+ deid = deid1 / 4 * 4 + (deid1 + 1) % 4;
+ } while (deid != deid0 && deid != -1);
+ if (marked)
+ continue;
+
+ if (deid != -1) {
+ int step = (info.first + 1) / 2;
+ std::pair<int, int> min_val(0x7fffffff, 0x7fffffff);
+ int split_idx = -1;
+ for (int i = 0; i < loop_vertices.size(); ++i) {
+ if (i + step >= loop_vertices.size())
+ continue;
+ int v1 = valences[loop_vertices[i]];
+ int v2 = valences[loop_vertices[i + step]];
+ if (v1 < v2)
+ std::swap(v1, v2);
+ auto key = std::make_pair(v1, v2);
+ if (key < min_val) {
+ min_val = key;
+ split_idx = i + 1;
+ }
+ }
+ if (min_val.first >= info.first)
+ continue;
+ update = true;
+ for (int id = split_idx; id < split_idx + step; ++id) {
+ F_compact[loop_dedges[id]/4][loop_dedges[id]%4] = O_compact.size();
+ }
+ F_compact.push_back(Vector4i(O_compact.size(), loop_vertices[(split_idx+loop_vertices.size()-1)%loop_vertices.size()],info.second, loop_vertices[(split_idx + step - 1 + loop_vertices.size()) % loop_vertices.size()]));
+ } else {
+ for (int id = loop_vertices.size() / 2; id < loop_vertices.size(); ++id) {
+ F_compact[loop_dedges[id]/4][loop_dedges[id]%4] = O_compact.size();
+ }
+ update = true;
+ }
+ marks[info.second] = 1;
+ for (int i = 0; i < loop_vertices.size(); ++i) {
+ marks[loop_vertices[i]] = 1;
+ }
+ Vset.push_back(Vset[info.second]);
+ O_compact.push_back(O_compact[info.second]);
+ N_compact.push_back(N_compact[info.second]);
+ Q_compact.push_back(Q_compact[info.second]);
+ }
+ if (!update) {
+ break;
+ } else {
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+ }
+ }
+ // Remove Zero Valence
+ std::vector<int> valences(V2E_compact.size(), 0);
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 4; ++j) {
+ valences[F_compact[i][j]] = 1;
+ }
+ }
+ top = 0;
+ std::vector<int> compact_indices(valences.size());
+ for (int i = 0; i < valences.size(); ++i) {
+ if (valences[i] == 0)
+ continue;
+ N_compact[top] = N_compact[i];
+ O_compact[top] = O_compact[i];
+ Q_compact[top] = Q_compact[i];
+ Vset[top] = Vset[i];
+ compact_indices[i] = top;
+ top += 1;
+ }
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 4; ++j) {
+ F_compact[i][j] = compact_indices[F_compact[i][j]];
+ }
+ }
+ N_compact.resize(top);
+ O_compact.resize(top);
+ Q_compact.resize(top);
+ Vset.resize(top);
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+ {
+ compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact,
+ nonManifold_compact);
+ std::vector<int> masks(F_compact.size() * 4, 0);
+ for (int i = 0; i < V2E_compact.size(); ++i) {
+ int deid0 = V2E_compact[i];
+ if (deid0 == -1)
+ continue;
+ int deid = deid0;
+ do {
+ masks[deid] = 1;
+ deid = E2E_compact[deid];
+ if (deid == -1) {
+ break;
+ }
+ deid = deid / 4 * 4 + (deid + 1) % 4;
+ } while (deid != deid0 && deid != -1);
+ }
+ std::vector<std::vector<int> > v_dedges(V2E_compact.size());
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 4; ++j) {
+ v_dedges[F_compact[i][j]].push_back(i * 4 + j);
+ }
+ }
+ }
+ std::map<int, int> pts;
+ for (int i = 0; i < V2E_compact.size(); ++i) {
+ int deid0 = V2E_compact[i];
+ if (deid0 == -1)
+ continue;
+ int deid = deid0;
+ int count = 0;
+ do {
+ count += 1;
+ int deid1 = E2E_compact[deid];
+ if (deid1 == -1)
+ break;
+ deid = deid1 / 4 * 4 + (deid1 + 1) % 4;
+ } while (deid != deid0 && deid != -1);
+ if (pts.count(count) == 0)
+ pts[count] = 1;
+ else
+ pts[count] += 1;
+ }
+ return;
+}
+
+void Parametrizer::OutputMesh(const char* obj_name) {
+ std::ofstream os(obj_name);
+ for (int i = 0; i < O_compact.size(); ++i) {
+ auto t = O_compact[i] * this->normalize_scale + this->normalize_offset;
+ os << "v " << t[0] << " " << t[1] << " " << t[2] << "\n";
+ }
+ for (int i = 0; i < F_compact.size(); ++i) {
+ os << "f " << F_compact[i][0]+1 << " " << F_compact[i][1]+1
+ << " " << F_compact[i][2]+1 << " " << F_compact[i][3]+1
+ << "\n";
+ }
+ os.close();
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/parametrizer-scale.cpp b/extern/quadriflow/src/parametrizer-scale.cpp
new file mode 100644
index 00000000000..bbd901d08c9
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer-scale.cpp
@@ -0,0 +1,119 @@
+#include "parametrizer.hpp"
+
+namespace qflow {
+
+void Parametrizer::ComputeInverseAffine()
+{
+ if (flag_adaptive_scale == 0)
+ return;
+ triangle_space.resize(F.cols());
+#ifdef WITH_OMP
+#pragma omp parallel for
+#endif
+ for (int i = 0; i < F.cols(); ++i) {
+ Matrix3d p, q;
+ p.col(0) = V.col(F(1, i)) - V.col(F(0, i));
+ p.col(1) = V.col(F(2, i)) - V.col(F(0, i));
+ p.col(2) = Nf.col(i);
+ q = p.inverse();
+ triangle_space[i].resize(2, 3);
+ for (int j = 0; j < 2; ++j) {
+ for (int k = 0; k < 3; ++k) {
+ triangle_space[i](j, k) = q(j, k);
+ }
+ }
+ }
+}
+
+void Parametrizer::EstimateSlope() {
+ auto& mF = hierarchy.mF;
+ auto& mQ = hierarchy.mQ[0];
+ auto& mN = hierarchy.mN[0];
+ auto& mV = hierarchy.mV[0];
+ FS.resize(2, mF.cols());
+ FQ.resize(3, mF.cols());
+ for (int i = 0; i < mF.cols(); ++i) {
+ const Vector3d& n = Nf.col(i);
+ const Vector3d &q_1 = mQ.col(mF(0, i)), &q_2 = mQ.col(mF(1, i)), &q_3 = mQ.col(mF(2, i));
+ const Vector3d &n_1 = mN.col(mF(0, i)), &n_2 = mN.col(mF(1, i)), &n_3 = mN.col(mF(2, i));
+ Vector3d q_1n = rotate_vector_into_plane(q_1, n_1, n);
+ Vector3d q_2n = rotate_vector_into_plane(q_2, n_2, n);
+ Vector3d q_3n = rotate_vector_into_plane(q_3, n_3, n);
+
+ auto p = compat_orientation_extrinsic_4(q_1n, n, q_2n, n);
+ Vector3d q = (p.first + p.second).normalized();
+ p = compat_orientation_extrinsic_4(q, n, q_3n, n);
+ q = (p.first * 2 + p.second);
+ q = q - n * q.dot(n);
+ FQ.col(i) = q.normalized();
+ }
+ for (int i = 0; i < mF.cols(); ++i) {
+ double step = hierarchy.mScale * 1.f;
+
+ const Vector3d &n = Nf.col(i);
+ Vector3d p = (mV.col(mF(0, i)) + mV.col(mF(1, i)) + mV.col(mF(2, i))) * (1.0 / 3.0);
+ Vector3d q_x = FQ.col(i), q_y = n.cross(q_x);
+ Vector3d q_xl = -q_x, q_xr = q_x;
+ Vector3d q_yl = -q_y, q_yr = q_y;
+ Vector3d q_yl_unfold = q_y, q_yr_unfold = q_y, q_xl_unfold = q_x, q_xr_unfold = q_x;
+ int f;
+ double tx, ty, len;
+
+ f = i; len = step;
+ TravelField(p, q_xl, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_yl_unfold);
+
+ f = i; len = step;
+ TravelField(p, q_xr, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_yr_unfold);
+
+ f = i; len = step;
+ TravelField(p, q_yl, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_xl_unfold);
+
+ f = i; len = step;
+ TravelField(p, q_yr, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_xr_unfold);
+ double dSx = (q_yr_unfold - q_yl_unfold).dot(q_x) / (2.0f * step);
+ double dSy = (q_xr_unfold - q_xl_unfold).dot(q_y) / (2.0f * step);
+ FS.col(i) = Vector2d(dSx, dSy);
+ }
+
+ std::vector<double> areas(mV.cols(), 0.0);
+ for (int i = 0; i < mF.cols(); ++i) {
+ Vector3d p1 = mV.col(mF(1, i)) - mV.col(mF(0, i));
+ Vector3d p2 = mV.col(mF(2, i)) - mV.col(mF(0, i));
+ double area = p1.cross(p2).norm();
+ for (int j = 0; j < 3; ++j) {
+ auto index = compat_orientation_extrinsic_index_4(FQ.col(i), Nf.col(i), mQ.col(mF(j, i)), mN.col(mF(j, i)));
+ double scaleX = FS.col(i).x(), scaleY = FS.col(i).y();
+ if (index.first != index.second % 2) {
+ std::swap(scaleX, scaleY);
+ }
+ if (index.second >= 2) {
+ scaleX = -scaleX;
+ scaleY = -scaleY;
+ }
+ hierarchy.mK[0].col(mF(j, i)) += area * Vector2d(scaleX, scaleY);
+ areas[mF(j, i)] += area;
+ }
+ }
+ for (int i = 0; i < mV.cols(); ++i) {
+ if (areas[i] != 0)
+ hierarchy.mK[0].col(i) /= areas[i];
+ }
+ for (int l = 0; l< hierarchy.mK.size() - 1; ++l) {
+ const MatrixXd &K = hierarchy.mK[l];
+ MatrixXd &K_next = hierarchy.mK[l + 1];
+ auto& toUpper = hierarchy.mToUpper[l];
+ for (int i = 0; i < toUpper.cols(); ++i) {
+ Vector2i upper = toUpper.col(i);
+ Vector2d k0 = K.col(upper[0]);
+
+ if (upper[1] != -1) {
+ Vector2d k1 = K.col(upper[1]);
+ k0 = 0.5 * (k0 + k1);
+ }
+
+ K_next.col(i) = k0;
+ }
+ }
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/parametrizer-sing.cpp b/extern/quadriflow/src/parametrizer-sing.cpp
new file mode 100644
index 00000000000..2d600e3cda4
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer-sing.cpp
@@ -0,0 +1,142 @@
+#include "config.hpp"
+#include "field-math.hpp"
+#include "parametrizer.hpp"
+
+namespace qflow {
+
+void Parametrizer::ComputeOrientationSingularities() {
+ MatrixXd &N = hierarchy.mN[0], &Q = hierarchy.mQ[0];
+ const MatrixXi& F = hierarchy.mF;
+ singularities.clear();
+ for (int f = 0; f < F.cols(); ++f) {
+ int index = 0;
+ int abs_index = 0;
+ for (int k = 0; k < 3; ++k) {
+ int i = F(k, f), j = F(k == 2 ? 0 : (k + 1), f);
+ auto value =
+ compat_orientation_extrinsic_index_4(Q.col(i), N.col(i), Q.col(j), N.col(j));
+ index += value.second - value.first;
+ abs_index += std::abs(value.second - value.first);
+ }
+ int index_mod = modulo(index, 4);
+ if (index_mod == 1 || index_mod == 3) {
+ if (index >= 4 || index < 0) {
+ Q.col(F(0, f)) = -Q.col(F(0, f));
+ }
+ singularities[f] = index_mod;
+ }
+ }
+}
+
+void Parametrizer::ComputePositionSingularities() {
+ const MatrixXd &V = hierarchy.mV[0], &N = hierarchy.mN[0], &Q = hierarchy.mQ[0],
+ &O = hierarchy.mO[0];
+ const MatrixXi& F = hierarchy.mF;
+
+ pos_sing.clear();
+ pos_rank.resize(F.rows(), F.cols());
+ pos_index.resize(6, F.cols());
+ for (int f = 0; f < F.cols(); ++f) {
+ Vector2i index = Vector2i::Zero();
+ uint32_t i0 = F(0, f), i1 = F(1, f), i2 = F(2, f);
+
+ Vector3d q[3] = {Q.col(i0).normalized(), Q.col(i1).normalized(), Q.col(i2).normalized()};
+ Vector3d n[3] = {N.col(i0), N.col(i1), N.col(i2)};
+ Vector3d o[3] = {O.col(i0), O.col(i1), O.col(i2)};
+ Vector3d v[3] = {V.col(i0), V.col(i1), V.col(i2)};
+
+ int best[3];
+ double best_dp = -std::numeric_limits<double>::infinity();
+ for (int i = 0; i < 4; ++i) {
+ Vector3d v0 = rotate90_by(q[0], n[0], i);
+ for (int j = 0; j < 4; ++j) {
+ Vector3d v1 = rotate90_by(q[1], n[1], j);
+ for (int k = 0; k < 4; ++k) {
+ Vector3d v2 = rotate90_by(q[2], n[2], k);
+ double dp = std::min(std::min(v0.dot(v1), v1.dot(v2)), v2.dot(v0));
+ if (dp > best_dp) {
+ best_dp = dp;
+ best[0] = i;
+ best[1] = j;
+ best[2] = k;
+ }
+ }
+ }
+ }
+ pos_rank(0, f) = best[0];
+ pos_rank(1, f) = best[1];
+ pos_rank(2, f) = best[2];
+ for (int k = 0; k < 3; ++k) q[k] = rotate90_by(q[k], n[k], best[k]);
+
+ for (int k = 0; k < 3; ++k) {
+ int kn = k == 2 ? 0 : (k + 1);
+ double scale_x = hierarchy.mScale, scale_y = hierarchy.mScale,
+ scale_x_1 = hierarchy.mScale, scale_y_1 = hierarchy.mScale;
+ if (flag_adaptive_scale) {
+ scale_x *= hierarchy.mS[0](0, F(k, f));
+ scale_y *= hierarchy.mS[0](1, F(k, f));
+ scale_x_1 *= hierarchy.mS[0](0, F(kn, f));
+ scale_y_1 *= hierarchy.mS[0](1, F(kn, f));
+ if (best[k] % 2 != 0) std::swap(scale_x, scale_y);
+ if (best[kn] % 2 != 0) std::swap(scale_x_1, scale_y_1);
+ }
+ double inv_scale_x = 1.0 / scale_x, inv_scale_y = 1.0 / scale_y,
+ inv_scale_x_1 = 1.0 / scale_x_1, inv_scale_y_1 = 1.0 / scale_y_1;
+ std::pair<Vector2i, Vector2i> value = compat_position_extrinsic_index_4(
+ v[k], n[k], q[k], o[k], v[kn], n[kn], q[kn], o[kn], scale_x, scale_y, inv_scale_x,
+ inv_scale_y, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1, nullptr);
+ auto diff = value.first - value.second;
+ index += diff;
+ pos_index(k * 2, f) = diff[0];
+ pos_index(k * 2 + 1, f) = diff[1];
+ }
+
+ if (index != Vector2i::Zero()) {
+ pos_sing[f] = rshift90(index, best[0]);
+ }
+ }
+}
+
+void Parametrizer::AnalyzeValence() {
+ auto& F = hierarchy.mF;
+ std::map<int, int> sing;
+ for (auto& f : singularities) {
+ for (int i = 0; i < 3; ++i) {
+ sing[F(i, f.first)] = f.second;
+ }
+ }
+ auto& F2E = face_edgeIds;
+ auto& E2E = hierarchy.mE2E;
+ auto& FQ = face_edgeOrients;
+ std::set<int> sing1, sing2;
+ for (int i = 0; i < F2E.size(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int deid = i * 3 + j;
+ int sum_int = 0;
+ std::vector<int> edges;
+ std::vector<double> angles;
+ do {
+ int deid1 = deid / 3 * 3 + (deid + 2) % 3;
+ deid = E2E[deid1];
+ sum_int += (FQ[deid / 3][deid % 3] + 6 - FQ[deid1 / 3][deid1 % 3]) % 4;
+ } while (deid != i * 3 + j);
+ if (sum_int % 4 == 2) {
+ printf("OMG! valence = 2\n");
+ exit(0);
+ }
+ if (sum_int % 4 == 1) sing1.insert(F(j, i));
+ if (sum_int % 4 == 3) sing2.insert(F(j, i));
+ }
+ }
+ int count3 = 0, count4 = 0;
+ for (auto& s : singularities) {
+ if (s.second == 1)
+ count3 += 1;
+ else
+ count4 += 1;
+ }
+ printf("singularity: <%d %d> <%d %d>\n", (int)sing1.size(), (int)sing2.size(), count3, count4);
+}
+
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/parametrizer.cpp b/extern/quadriflow/src/parametrizer.cpp
new file mode 100644
index 00000000000..b85383566c9
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer.cpp
@@ -0,0 +1,247 @@
+#include "parametrizer.hpp"
+#include "config.hpp"
+#include "dedge.hpp"
+#include "field-math.hpp"
+#include "flow.hpp"
+#include "localsat.hpp"
+#include "optimizer.hpp"
+#include "subdivide.hpp"
+
+#include "dset.hpp"
+
+#include <Eigen/Sparse>
+#include <fstream>
+#include <list>
+#include <map>
+#include <queue>
+#include <set>
+
+namespace qflow {
+
+void Parametrizer::ComputeIndexMap(int with_scale) {
+ // build edge info
+ auto& V = hierarchy.mV[0];
+ auto& F = hierarchy.mF;
+ auto& Q = hierarchy.mQ[0];
+ auto& N = hierarchy.mN[0];
+ auto& O = hierarchy.mO[0];
+ auto& S = hierarchy.mS[0];
+ // ComputeOrientationSingularities();
+
+ BuildEdgeInfo();
+
+ if (flag_preserve_sharp) {
+ // ComputeSharpO();
+ }
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i]) {
+ int e = face_edgeIds[i / 3][i % 3];
+ if (edge_diff[e][0] * edge_diff[e][1] != 0) {
+ Vector3d d = O.col(edge_values[e].y) - O.col(edge_values[e].x);
+ Vector3d q = Q.col(edge_values[e].x);
+ Vector3d n = N.col(edge_values[e].x);
+ Vector3d qy = n.cross(q);
+ if (abs(q.dot(d)) > qy.dot(d))
+ edge_diff[e][1] = 0;
+ else
+ edge_diff[e][0] = 0;
+ }
+ }
+ }
+ std::map<int, std::pair<Vector3d, Vector3d>> sharp_constraints;
+ std::set<int> sharpvert;
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i]) {
+ sharpvert.insert(F(i % 3, i / 3));
+ sharpvert.insert(F((i + 1) % 3, i / 3));
+ }
+ }
+
+ allow_changes.resize(edge_diff.size() * 2, 1);
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ int e = face_edgeIds[i / 3][i % 3];
+ if (sharpvert.count(edge_values[e].x) && sharpvert.count(edge_values[e].y)) {
+ if (sharp_edges[i] != 0) {
+ for (int k = 0; k < 2; ++k) {
+ if (edge_diff[e][k] == 0) {
+ allow_changes[e * 2 + k] = 0;
+ }
+ }
+ }
+ }
+ }
+#ifdef LOG_OUTPUT
+ printf("Build Integer Constraints...\n");
+#endif
+ BuildIntegerConstraints();
+
+ ComputeMaxFlow();
+ // potential bug
+#ifdef LOG_OUTPUT
+ printf("subdivide...\n");
+#endif
+ subdivide_edgeDiff(F, V, N, Q, O, &hierarchy.mS[0], V2E, hierarchy.mE2E, boundary, nonManifold,
+ edge_diff, edge_values, face_edgeOrients, face_edgeIds, sharp_edges,
+ singularities, 1);
+
+ allow_changes.clear();
+ allow_changes.resize(edge_diff.size() * 2, 1);
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i] == 0) continue;
+ int e = face_edgeIds[i / 3][i % 3];
+ for (int k = 0; k < 2; ++k) {
+ if (edge_diff[e][k] == 0) allow_changes[e * 2 + k] = 0;
+ }
+ }
+
+#ifdef LOG_OUTPUT
+ printf("Fix flip advance...\n");
+ int t1 = GetCurrentTime64();
+#endif
+ FixFlipHierarchy();
+ subdivide_edgeDiff(F, V, N, Q, O, &hierarchy.mS[0], V2E, hierarchy.mE2E, boundary, nonManifold,
+ edge_diff, edge_values, face_edgeOrients, face_edgeIds, sharp_edges,
+ singularities, 1);
+ FixFlipSat();
+
+#ifdef LOG_OUTPUT
+ int t2 = GetCurrentTime64();
+ printf("Flip use %lf\n", (t2 - t1) * 1e-3);
+ printf("Post Linear Solver...\n");
+#endif
+ std::set<int> sharp_vertices;
+ for (int i = 0; i < sharp_edges.size(); ++i) {
+ if (sharp_edges[i] == 1) {
+ sharp_vertices.insert(F(i % 3, i / 3));
+ sharp_vertices.insert(F((i + 1) % 3, i / 3));
+ }
+ }
+
+ Optimizer::optimize_positions_sharp(hierarchy, edge_values, edge_diff, sharp_edges,
+ sharp_vertices, sharp_constraints, with_scale);
+
+ Optimizer::optimize_positions_fixed(hierarchy, edge_values, edge_diff, sharp_vertices,
+ sharp_constraints, flag_adaptive_scale);
+
+ AdvancedExtractQuad();
+
+ FixValence();
+
+ std::vector<int> sharp_o(O_compact.size(), 0);
+ std::map<int, std::pair<Vector3d, Vector3d>> compact_sharp_constraints;
+ for (int i = 0; i < Vset.size(); ++i) {
+ int sharpv = -1;
+ for (auto& p : Vset[i]) {
+ if (sharp_constraints.count(p)) {
+ sharpv = p;
+ sharp_o[i] = 1;
+ if (compact_sharp_constraints.count(i) == 0 ||
+ compact_sharp_constraints[i].second != Vector3d::Zero()) {
+ compact_sharp_constraints[i] = sharp_constraints[sharpv];
+ O_compact[i] = O.col(sharpv);
+ compact_sharp_constraints[i].first = O_compact[i];
+ }
+ }
+ }
+ }
+
+ std::map<std::pair<int, int>, int> o2e;
+ for (int i = 0; i < F_compact.size(); ++i) {
+ for (int j = 0; j < 4; ++j) {
+ int v1 = F_compact[i][j];
+ int v2 = F_compact[i][(j + 1) % 4];
+ o2e[std::make_pair(v1, v2)] = i * 4 + j;
+ }
+ }
+ std::vector<std::vector<int>> v2o(V.cols());
+ for (int i = 0; i < Vset.size(); ++i) {
+ for (auto v : Vset[i]) {
+ v2o[v].push_back(i);
+ }
+ }
+ std::vector<Vector3d> diffs(F_compact.size() * 4, Vector3d(0, 0, 0));
+ std::vector<int> diff_count(F_compact.size() * 4, 0);
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int v1 = F(j, i);
+ int v2 = F((j + 1) % 3, i);
+ if (v1 != edge_values[face_edgeIds[i][j]].x) continue;
+ if (edge_diff[face_edgeIds[i][j]].array().abs().sum() != 1) continue;
+ if (v2o[v1].size() > 1 || v2o[v2].size() > 1) continue;
+ for (auto o1 : v2o[v1]) {
+ for (auto o2 : v2o[v2]) {
+ auto key = std::make_pair(o1, o2);
+ if (o2e.count(key)) {
+ int dedge = o2e[key];
+ Vector3d q_1 = Q.col(v1);
+ Vector3d q_2 = Q.col(v2);
+ Vector3d n_1 = N.col(v1);
+ Vector3d n_2 = N.col(v2);
+ Vector3d q_1_y = n_1.cross(q_1);
+ Vector3d q_2_y = n_2.cross(q_2);
+ auto index = compat_orientation_extrinsic_index_4(q_1, n_1, q_2, n_2);
+ double s_x1 = S(0, v1), s_y1 = S(1, v1);
+ double s_x2 = S(0, v2), s_y2 = S(1, v2);
+ int rank_diff = (index.second + 4 - index.first) % 4;
+ if (rank_diff % 2 == 1) std::swap(s_x2, s_y2);
+ Vector3d qd_x = 0.5 * (rotate90_by(q_2, n_2, rank_diff) + q_1);
+ Vector3d qd_y = 0.5 * (rotate90_by(q_2_y, n_2, rank_diff) + q_1_y);
+ double scale_x = (with_scale ? 0.5 * (s_x1 + s_x2) : 1) * hierarchy.mScale;
+ double scale_y = (with_scale ? 0.5 * (s_y1 + s_y2) : 1) * hierarchy.mScale;
+ Vector2i diff = edge_diff[face_edgeIds[i][j]];
+ Vector3d C = diff[0] * scale_x * qd_x + diff[1] * scale_y * qd_y;
+
+ diff_count[dedge] += 1;
+ diffs[dedge] += C;
+ auto key = std::make_pair(o2, o1);
+ if (o2e.count(key)) {
+ int dedge = o2e[key];
+ diff_count[dedge] += 1;
+ diffs[dedge] -= C;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < F.cols(); ++i) {
+ Vector2i d1 = rshift90(edge_diff[face_edgeIds[i][0]], face_edgeOrients[i][0]);
+ Vector2i d2 = rshift90(edge_diff[face_edgeIds[i][1]], face_edgeOrients[i][1]);
+ if (d1[0] * d2[1] - d1[1] * d2[0] < 0) {
+ for (int j = 0; j < 3; ++j) {
+ int v1 = F(j, i);
+ int v2 = F((j + 1) % 3, i);
+ for (auto o1 : v2o[v1]) {
+ for (auto o2 : v2o[v2]) {
+ auto key = std::make_pair(o1, o2);
+ if (o2e.count(key)) {
+ int dedge = o2e[key];
+ diff_count[dedge] = 0;
+ diffs[dedge] = Vector3d(0, 0, 0);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < diff_count.size(); ++i) {
+ if (diff_count[i] != 0) {
+ diffs[i] /= diff_count[i];
+ diff_count[i] = 1;
+ }
+ }
+
+ Optimizer::optimize_positions_dynamic(F, V, N, Q, Vset, O_compact, F_compact, V2E_compact,
+ E2E_compact, sqrt(surface_area / F_compact.size()),
+ diffs, diff_count, o2e, sharp_o,
+ compact_sharp_constraints, flag_adaptive_scale);
+
+ // optimize_quad_positions(O_compact, N_compact, Q_compact, F_compact, V2E_compact,
+ // E2E_compact,
+ // V, N, Q, O, F, V2E, hierarchy.mE2E, disajoint_tree,
+ // hierarchy.mScale, false);
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/parametrizer.hpp b/extern/quadriflow/src/parametrizer.hpp
new file mode 100644
index 00000000000..1f4a02be6c2
--- /dev/null
+++ b/extern/quadriflow/src/parametrizer.hpp
@@ -0,0 +1,177 @@
+#ifndef PARAMETRIZER_H_
+#define PARAMETRIZER_H_
+#include <atomic>
+#include <condition_variable>
+#ifdef WITH_TBB
+#include <tbb/tbb.h>
+#endif
+
+#include <Eigen/Core>
+#include <Eigen/Dense>
+#include <list>
+#include <map>
+#include <set>
+#include <unordered_set>
+#include "adjacent-matrix.hpp"
+#include "disajoint-tree.hpp"
+#include "field-math.hpp"
+#include "hierarchy.hpp"
+#include "post-solver.hpp"
+#include "serialize.hpp"
+
+namespace qflow {
+
+using namespace Eigen;
+
+typedef std::pair<unsigned int, unsigned int> Edge;
+typedef std::map<int, std::pair<int, int>> SingDictionary;
+
+struct ExpandInfo {
+ ExpandInfo() {}
+ int current_v;
+ int singularity;
+ int step;
+ int edge_id;
+ int prev;
+};
+
+class Parametrizer {
+ public:
+ Parametrizer() {}
+ // Mesh Initialization
+ void Load(const char* filename);
+ void NormalizeMesh();
+ void ComputeMeshStatus();
+ void ComputeSmoothNormal();
+ void ComputeSharpEdges();
+ void ComputeSharpO();
+ void ComputeVertexArea();
+ void Initialize(int faces);
+
+ // Singularity and Mesh property
+ void AnalyzeValence();
+ void ComputeOrientationSingularities();
+ void ComputePositionSingularities();
+
+ // Integer Grid Map Pipeline
+ void ComputeIndexMap(int with_scale = 0);
+ void BuildEdgeInfo();
+ void ComputeMaxFlow();
+ void MarkInteger();
+ void BuildIntegerConstraints();
+
+ // Fix Flip
+ void FixFlipHierarchy();
+ void FixFlipSat();
+ void FixHoles();
+ void FixHoles(std::vector<int>& loop_vertices);
+ void FixValence();
+ double QuadEnergy(std::vector<int>& loop_vertices, std::vector<Vector4i>& res_quads,
+ int level);
+
+ // Quadmesh and IO
+ void AdvancedExtractQuad();
+ void BuildTriangleManifold(DisajointTree& disajoint_tree, std::vector<int>& edge,
+ std::vector<int>& face, std::vector<DEdge>& edge_values,
+ std::vector<Vector3i>& F2E, std::vector<Vector2i>& E2F,
+ std::vector<Vector2i>& EdgeDiff, std::vector<Vector3i>& FQ);
+ void OutputMesh(const char* obj_name);
+
+ std::map<int, int> singularities; // map faceid to valence (1 (valence=3) or 3(valence=5))
+ std::map<int, Vector2i> pos_sing;
+ MatrixXi pos_rank; // pos_rank(i, j) i \in [0, 3) jth face ith vertex rotate by its value so
+ // that all thress vertices are in the same orientation
+ MatrixXi pos_index; // pos_index(i x 2 + dim, j) i \in [0, 6) jth face ith vertex's
+ // (t_ij-t_ji)'s dim's dimenstion in the paper
+ // input mesh
+ MatrixXd V;
+ MatrixXd N;
+ MatrixXd Nf;
+ MatrixXd FS;
+ MatrixXd FQ;
+ MatrixXi F;
+
+ double normalize_scale;
+ Vector3d normalize_offset;
+
+ // data structures
+ VectorXd rho;
+ VectorXi V2E;
+ VectorXi E2E;
+ VectorXi boundary;
+ VectorXi nonManifold; // nonManifold vertices, in boolean
+ AdjacentMatrix adj;
+ Hierarchy hierarchy;
+
+ // Mesh Status;
+ double surface_area;
+ double scale;
+ double average_edge_length;
+ double max_edge_length;
+ VectorXd A;
+
+ // just for test
+ DisajointTree disajoint_tree;
+
+ int compact_num_v;
+ std::vector<std::vector<int>> Vset;
+ std::vector<Vector3d> O_compact;
+ std::vector<Vector3d> Q_compact;
+ std::vector<Vector3d> N_compact;
+ std::vector<Vector4i> F_compact;
+ std::set<std::pair<int, int>> Quad_edges;
+ std::vector<int> V2E_compact;
+ std::vector<int> E2E_compact;
+ VectorXi boundary_compact;
+ VectorXi nonManifold_compact;
+
+ std::vector<int> bad_vertices;
+ std::vector<double> counter;
+ std::vector<int>
+ sharp_edges; // sharp_edges[deid]: whether deid is a sharp edge that should be preserved
+ std::vector<int> allow_changes; // allow_changes[variable_id]: whether var can be changed
+ // based on sharp edges
+ std::vector<Vector2i> edge_diff; // edge_diff[edgeIds[i](j)]: t_ij+t_ji under
+ // edge_values[edgeIds[i](j)].x's Q value
+ std::vector<DEdge> edge_values; // see above
+ std::vector<Vector3i>
+ face_edgeIds; // face_edgeIds[i](j): ith face jth edge's "undirected edge ID"
+
+ // face_edgeOrients[i](j): Rotate from edge_diff space
+ // (a) initially, to F(0, i)'s Q space
+ // (b) later on, to a global Q space where some edges are fixed
+ std::vector<Vector3i> face_edgeOrients;
+
+ // variable[i].first: indices of the two equations corresponding to variable i
+ // variable[i].second: number of positive minus negative of variables' occurances
+ std::vector<std::pair<Vector2i, int>> variables;
+
+ struct QuadInfo {
+ QuadInfo() : patchId(-1), coordinate(0x10000000, 0x10000000), singular(0), edge(0) {}
+ int patchId;
+ Vector2i coordinate;
+ int singular;
+ int edge;
+ };
+ std::vector<QuadInfo> quad_info;
+
+ // scale
+ void ComputeInverseAffine();
+ void EstimateSlope();
+ std::vector<MatrixXd> triangle_space;
+
+ // flag
+ int flag_preserve_sharp = 0;
+ int flag_preserve_boundary = 0;
+ int flag_adaptive_scale = 0;
+ int flag_aggresive_sat = 0;
+ int flag_minimum_cost_flow = 0;
+};
+
+extern void generate_adjacency_matrix_uniform(const MatrixXi& F, const VectorXi& V2E,
+ const VectorXi& E2E, const VectorXi& nonManifold,
+ AdjacentMatrix& adj);
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/post-solver.cpp b/extern/quadriflow/src/post-solver.cpp
new file mode 100644
index 00000000000..6027ddd2eb7
--- /dev/null
+++ b/extern/quadriflow/src/post-solver.cpp
@@ -0,0 +1,427 @@
+//
+// post-solver.cpp
+// parametrize
+//
+// Created by Jingwei on 2/5/18.
+//
+#include <algorithm>
+#include <boost/program_options.hpp>
+#include <cmath>
+#include <cstdio>
+#include <string>
+
+#include "ceres/ceres.h"
+#include "ceres/rotation.h"
+
+#include "post-solver.hpp"
+#include "serialize.hpp"
+
+namespace qflow {
+
+/// Coefficient of area constraint. The magnitude is 1 if area is equal to 0.
+const double COEFF_AREA = 1;
+/// Coefficient of tangent constraint. The magnitude is 0.03 if the bais is reference_length.
+/// This is because current tangent constraint is not very accurate.
+/// This optimization conflicts with COEFF_AREA.
+const double COEFF_TANGENT = 0.02;
+/// Coefficient of normal constraint. The magnitude is the arc angle.
+const double COEFF_NORMAL = 1;
+/// Coefficient of normal constraint. The magnitude is the arc angle.
+const double COEFF_FLOW = 1;
+/// Coefficient of orthogonal edge. The magnitude is the arc angle.
+const double COEFF_ORTH = 1;
+/// Coefficient of edge length. The magnitude is the arc angle.
+const double COEFF_LENGTH = 1;
+/// Number of iterations of the CGNR solver
+const int N_ITER = 100;
+
+template <typename T, typename T2>
+T DotProduct(const T a[3], const T2 b[3]) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+}
+
+template <typename T>
+T Length2(const T a[3]) {
+ return DotProduct(a, a);
+}
+
+namespace ceres {
+inline double min(const double f, const double g) { return std::min(f, g); }
+
+template <typename T, int N>
+inline Jet<T, N> min(const Jet<T, N>& f, const Jet<T, N>& g) {
+ if (f.a < g.a)
+ return f;
+ else
+ return g;
+}
+} // namespace ceres
+
+bool DEBUG = 0;
+struct FaceConstraint {
+ FaceConstraint(double coeff_area, double coeff_normal, double coeff_flow, double coeff_orth,
+ double length, Vector3d normal[4], Vector3d Q0[4], Vector3d Q1[4])
+ : coeff_area(coeff_area),
+ coeff_normal(coeff_normal),
+ coeff_flow(coeff_flow),
+ coeff_orth(coeff_orth),
+ area0(length * length),
+ normal0{
+ normal[0],
+ normal[1],
+ normal[2],
+ normal[3],
+ },
+ Q0{Q0[0], Q0[1], Q0[2], Q0[3]},
+ Q1{Q1[0], Q1[1], Q1[2], Q1[3]} {}
+
+ template <typename T>
+ bool operator()(const T* p0, const T* p1, const T* p2, const T* p3, T* r) const {
+ const T* p[] = {p0, p1, p2, p3};
+ r[12] = T();
+ for (int k = 0; k < 4; ++k) {
+ auto pc = p[k];
+ auto pa = p[(k + 1) % 4];
+ auto pb = p[(k + 3) % 4];
+
+ T a[3]{pa[0] - pc[0], pa[1] - pc[1], pa[2] - pc[2]};
+ T b[3]{pb[0] - pc[0], pb[1] - pc[1], pb[2] - pc[2]};
+
+ T length_a = ceres::sqrt(Length2(a));
+ T length_b = ceres::sqrt(Length2(b));
+ T aa[3]{a[0] / length_a, a[1] / length_a, a[2] / length_a};
+ T bb[3]{b[0] / length_b, b[1] / length_b, b[2] / length_b};
+ r[3 * k + 0] = coeff_orth * DotProduct(aa, bb);
+
+ T degree_edge0 = ceres::abs(DotProduct(aa, &Q0[k][0]));
+ T degree_edge1 = ceres::abs(DotProduct(aa, &Q1[k][0]));
+ T degree_edge = ceres::min(degree_edge0, degree_edge1);
+ r[3 * k + 1] = coeff_flow * degree_edge;
+
+ T normal[3];
+ ceres::CrossProduct(a, b, normal);
+ T area = ceres::sqrt(Length2(normal));
+ r[12] += area;
+
+ assert(area != T());
+ for (int i = 0; i < 3; ++i) normal[i] /= area;
+ T degree_normal = DotProduct(normal, &normal0[k][0]) - T(1);
+ r[3 * k + 2] = coeff_normal * degree_normal * degree_normal;
+ }
+ r[12] = coeff_area * (r[12] / (4.0 * area0) - 1.0);
+ return true;
+ }
+
+ static ceres::CostFunction* create(double coeff_area, double coeff_normal, double coeff_flow,
+ double coeff_orth, double length, Vector3d normal[4],
+ Vector3d Q0[4], Vector3d Q1[4]) {
+ return new ceres::AutoDiffCostFunction<FaceConstraint, 13, 3, 3, 3, 3>(new FaceConstraint(
+ coeff_area, coeff_normal, coeff_flow, coeff_orth, length, normal, Q0, Q1));
+ }
+
+ double coeff_area;
+ double coeff_normal;
+ double coeff_flow;
+ double coeff_orth;
+
+ double area0;
+ Vector3d normal0[4];
+ Vector3d Q0[4], Q1[4];
+};
+
+struct VertexConstraint {
+ VertexConstraint(double coeff_tangent, Vector3d normal, double bias, double length)
+ : coeff{coeff_tangent / length * 10}, bias0{bias}, normal0{normal} {}
+
+ template <typename T>
+ bool operator()(const T* p, T* r) const {
+ r[0] = coeff * (DotProduct(p, &normal0[0]) - bias0);
+ return true;
+ }
+
+ static ceres::CostFunction* create(double coeff_tangent, Vector3d normal, double bias,
+ double length) {
+ return new ceres::AutoDiffCostFunction<VertexConstraint, 1, 3>(
+ new VertexConstraint(coeff_tangent, normal, bias, length));
+ }
+
+ double coeff;
+ double bias0;
+ Vector3d normal0;
+};
+
+void solve(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad,
+ std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad,
+ std::vector<double>& B_quad, MatrixXd& V, MatrixXd& N, MatrixXd& Q, MatrixXd& O,
+ MatrixXi& F, double reference_length, double coeff_area, double coeff_tangent,
+ double coeff_normal, double coeff_flow, double coeff_orth) {
+ printf("Parameter: \n");
+ printf(" coeff_area: %.4f\n", coeff_area);
+ printf(" coeff_tangent: %.4f\n", coeff_tangent);
+ printf(" coeff_normal: %.4f\n", coeff_normal);
+ printf(" coeff_flow: %.4f\n", coeff_flow);
+ printf(" coeff_orth: %.4f\n\n", coeff_orth);
+ int n_quad = Q_quad.size();
+
+ ceres::Problem problem;
+ std::vector<double> solution(n_quad * 3);
+ for (int vquad = 0; vquad < n_quad; ++vquad) {
+ solution[3 * vquad + 0] = O_quad[vquad][0];
+ solution[3 * vquad + 1] = O_quad[vquad][1];
+ solution[3 * vquad + 2] = O_quad[vquad][2];
+ }
+
+ // Face constraint (area and normal direction)
+ for (int fquad = 0; fquad < F_quad.size(); ++fquad) {
+ auto v = F_quad[fquad];
+ Vector3d normal[4], Q0[4], Q1[4];
+ for (int k = 0; k < 4; ++k) {
+ normal[k] = N_quad[v[k]];
+ Q0[k] = Q_quad[v[k]];
+ Q1[k] = Q0[k].cross(normal[k]).normalized();
+ }
+ ceres::CostFunction* cost_function = FaceConstraint::create(
+ coeff_area, coeff_normal, coeff_flow, coeff_orth, reference_length, normal, Q0, Q1);
+ problem.AddResidualBlock(cost_function, nullptr, &solution[3 * v[0]], &solution[3 * v[1]],
+ &solution[3 * v[2]], &solution[3 * v[3]]);
+ }
+
+ // Tangent constraint
+ for (int vquad = 0; vquad < O_quad.size(); ++vquad) {
+ ceres::CostFunction* cost_function = VertexConstraint::create(
+ coeff_tangent, N_quad[vquad], B_quad[vquad], reference_length);
+ problem.AddResidualBlock(cost_function, nullptr, &solution[3 * vquad]);
+ }
+
+ // Flow constraint
+
+ ceres::Solver::Options options;
+ options.num_threads = 1;
+ options.max_num_iterations = N_ITER;
+ options.initial_trust_region_radius = 1;
+ options.linear_solver_type = ceres::CGNR;
+ options.minimizer_progress_to_stdout = true;
+ ceres::Solver::Summary summary;
+ ceres::Solve(options, &problem, &summary);
+
+ std::cout << summary.BriefReport() << std::endl;
+
+ for (int vquad = 0; vquad < n_quad; ++vquad) {
+ O_quad[vquad][0] = solution[3 * vquad + 0];
+ O_quad[vquad][1] = solution[3 * vquad + 1];
+ O_quad[vquad][2] = solution[3 * vquad + 2];
+ }
+
+ return;
+}
+
+void optimize_quad_positions(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad,
+ std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad,
+ VectorXi& V2E_quad, std::vector<int>& E2E_quad, MatrixXd& V,
+ MatrixXd& N, MatrixXd& Q, MatrixXd& O, MatrixXi& F, VectorXi& V2E,
+ VectorXi& E2E, DisajointTree& disajoint_tree, double reference_length,
+ bool just_serialize) {
+ printf("Quad mesh info:\n");
+ printf("Number of vertices with normals and orientations: %d = %d = %d\n", (int)O_quad.size(),
+ (int)N_quad.size(), (int)Q_quad.size());
+ printf("Number of faces: %d\n", (int)F_quad.size());
+ printf("Number of directed edges: %d\n", (int)E2E_quad.size());
+ // Information for the original mesh
+ printf("Triangle mesh info:\n");
+ printf(
+ "Number of vertices with normals, "
+ "orientations and associated quad positions: "
+ "%d = %d = %d = %d\n",
+ (int)V.cols(), (int)N.cols(), (int)Q.cols(), (int)O.cols());
+ printf("Number of faces: %d\n", (int)F.cols());
+ printf("Number of directed edges: %d\n", (int)E2E.size());
+ printf("Reference length: %.2f\n", reference_length);
+
+ int flip_count = 0;
+ for (int i = 0; i < F_quad.size(); ++i) {
+ bool flipped = false;
+ for (int j = 0; j < 4; ++j) {
+ int v1 = F_quad[i][j];
+ int v2 = F_quad[i][(j + 1) % 4];
+ int v3 = F_quad[i][(j + 3) % 4];
+
+ Vector3d face_norm = (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]);
+ Vector3d vertex_norm = N_quad[v1];
+ if (face_norm.dot(vertex_norm) < 0) {
+ flipped = true;
+ }
+ }
+ if (flipped) {
+ flip_count++;
+ }
+ }
+ printf("Flipped Quads: %d\n", flip_count);
+
+ int n_quad = O_quad.size();
+ int n_trig = O.cols();
+ std::vector<double> B_quad(n_quad); // Average bias for quad vertex
+ std::vector<int> B_weight(n_quad);
+
+ printf("ntrig: %d, disjoint_tree.size: %d\n", n_trig, (int)disajoint_tree.indices.size());
+ for (int vtrig = 0; vtrig < n_trig; ++vtrig) {
+ int vquad = disajoint_tree.Index(vtrig);
+ double b = N_quad[vquad].dot(O.col(vtrig));
+ B_quad[vquad] += b;
+ B_weight[vquad] += 1;
+ }
+ for (int vquad = 0; vquad < n_quad; ++vquad) {
+ assert(B_weight[vquad]);
+ B_quad[vquad] /= B_weight[vquad];
+ }
+
+ puts("Save parameters to post.bin for optimization");
+ FILE* out = fopen("post.bin", "wb");
+ assert(out);
+ Save(out, O_quad);
+ Save(out, N_quad);
+ Save(out, Q_quad);
+ Save(out, F_quad);
+ Save(out, B_quad);
+ Save(out, V);
+ Save(out, N);
+ Save(out, Q);
+ Save(out, O);
+ Save(out, F);
+ Save(out, reference_length);
+ fclose(out);
+
+ if (!just_serialize) {
+ puts("Start post optimization");
+ solve(O_quad, N_quad, Q_quad, F_quad, B_quad, V, N, Q, O, F, reference_length, COEFF_AREA,
+ COEFF_TANGENT, COEFF_NORMAL, COEFF_FLOW, COEFF_ORTH);
+ }
+}
+
+#ifdef POST_SOLVER
+
+void SaveObj(const std::string& fname, std::vector<Vector3d> O_quad,
+ std::vector<Vector4i> F_quad) {
+ std::ofstream os(fname);
+ for (int i = 0; i < (int)O_quad.size(); ++i) {
+ os << "v " << O_quad[i][0] << " " << O_quad[i][1] << " " << O_quad[i][2] << "\n";
+ }
+ for (int i = 0; i < (int)F_quad.size(); ++i) {
+ os << "f " << F_quad[i][0] + 1 << " " << F_quad[i][1] + 1 << " " << F_quad[i][2] + 1 << " "
+ << F_quad[i][3] + 1 << "\n";
+ }
+ os.close();
+}
+
+int main(int argc, char* argv[]) {
+ double coeff_area;
+ double coeff_tangent;
+ double coeff_normal;
+ double coeff_flow;
+ double coeff_orth;
+
+ namespace po = boost::program_options;
+ po::options_description desc("Allowed options");
+ desc.add_options() // clang-format off
+ ("help,h", "produce help message")
+ ("area,a", po::value<double>(&coeff_area)->default_value(COEFF_AREA), "Set the coefficient of area constraint")
+ ("tangent,t", po::value<double>(&coeff_tangent)->default_value(COEFF_TANGENT), "Set the coefficient of tangent constraint")
+ ("normal,n", po::value<double>(&coeff_normal)->default_value(COEFF_NORMAL), "Set the coefficient of normal constraint")
+ ("flow,f", po::value<double>(&coeff_flow)->default_value(COEFF_FLOW), "Set the coefficient of flow (Q) constraint")
+ ("orth,o", po::value<double>(&coeff_orth)->default_value(COEFF_ORTH), "Set the coefficient of orthogonal constraint");
+
+ // clang-format on
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+ if (vm.count("help")) {
+ std::cout << desc << std::endl;
+ return 1;
+ }
+
+ std::vector<Vector3d> O_quad;
+ std::vector<Vector3d> N_quad;
+ std::vector<Vector3d> Q_quad;
+ std::vector<Vector4i> F_quad;
+ std::vector<double> B_quad;
+ MatrixXd V;
+ MatrixXd N;
+ MatrixXd Q;
+ MatrixXd O;
+ MatrixXi F;
+ double reference_length;
+
+ puts("Read parameters from post.bin");
+ FILE* in = fopen("post.bin", "rb");
+ assert(in);
+ Read(in, O_quad);
+ Read(in, N_quad);
+ Read(in, Q_quad);
+ Read(in, F_quad);
+ Read(in, B_quad);
+ Read(in, V);
+ Read(in, N);
+ Read(in, Q);
+ Read(in, O);
+ Read(in, F);
+ Read(in, reference_length);
+ fclose(in);
+ printf("reference_length: %.2f\n", reference_length);
+ SaveObj("presolver.obj", O_quad, F_quad);
+
+ int n_flip = 0;
+ double sum_degree = 0;
+ for (int i = 0; i < F_quad.size(); ++i) {
+ bool flipped = false;
+ for (int j = 0; j < 4; ++j) {
+ int v1 = F_quad[i][j];
+ int v2 = F_quad[i][(j + 1) % 4];
+ int v3 = F_quad[i][(j + 3) % 4];
+
+ Vector3d face_norm =
+ (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]).normalized();
+ Vector3d vertex_norm = N_quad[v1];
+ if (face_norm.dot(vertex_norm) < 0) {
+ flipped = true;
+ }
+ double degree = std::acos(face_norm.dot(vertex_norm));
+ assert(degree >= 0);
+ // printf("cos theta = %.2f\n", degree);
+ sum_degree += degree * degree;
+ }
+ n_flip += flipped;
+ }
+ printf("n_flip: %d\nsum_degree: %.3f\n", n_flip, sum_degree);
+
+ puts("Start post optimization");
+ solve(O_quad, N_quad, Q_quad, F_quad, B_quad, V, N, Q, O, F, reference_length, coeff_area,
+ coeff_tangent, coeff_normal, coeff_flow, coeff_orth);
+ SaveObj("postsolver.obj", O_quad, F_quad);
+
+ n_flip = 0;
+ sum_degree = 0;
+ for (int i = 0; i < F_quad.size(); ++i) {
+ bool flipped = false;
+ for (int j = 0; j < 4; ++j) {
+ int v1 = F_quad[i][j];
+ int v2 = F_quad[i][(j + 1) % 4];
+ int v3 = F_quad[i][(j + 3) % 4];
+
+ Vector3d face_norm =
+ (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]).normalized();
+ Vector3d vertex_norm = N_quad[v1];
+ if (face_norm.dot(vertex_norm) < 0) {
+ flipped = true;
+ }
+ double degree = std::acos(face_norm.dot(vertex_norm));
+ assert(degree >= 0);
+ sum_degree += degree * degree;
+ }
+ n_flip += flipped;
+ }
+ printf("n_flip: %d\nsum_degree: %.3f\n", n_flip, sum_degree);
+ return 0;
+}
+
+#endif
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/post-solver.hpp b/extern/quadriflow/src/post-solver.hpp
new file mode 100644
index 00000000000..546245d801e
--- /dev/null
+++ b/extern/quadriflow/src/post-solver.hpp
@@ -0,0 +1,64 @@
+//
+// post-solver.hpp
+// Parametrize
+//
+// Created by Jingwei on 2/5/18.
+//
+
+#ifndef post_solver_h
+#define post_solver_h
+
+#include <Eigen/Core>
+#include <vector>
+#include "disajoint-tree.hpp"
+
+namespace qflow {
+
+using namespace Eigen;
+
+/*
+ * TODO: Optimize O_quad, and possibly N_quad
+ * Input:
+ * O_quad[i]: initialized i-th vertex position of the quad mesh
+ * N_quad[i]: initialized i-th vertex normal of the quad mesh
+ * Q_quad[i]: initialized i-th vertex orientation of the quad mesh, guaranteed to be orthogonal to
+ * N_quad[i]
+ * F_quad[i]: 4 vertex index of the i-th quad face
+ *
+ * Concept: i-th directed edge is the (i%4)-th edge of the (i/4)-th face of the quad mesh
+ * V2E_quad[i]: one directed edge from i-th vertex of the quad mesh
+ * E2E_quad[i]: the reverse directed edge's index of the i-th directed edge of the quad mesh
+ *
+ * V.col(i): i-th vertex position of the triangle mesh
+ * N.col(i): i-th vertex normal of the triangle mesh
+ * Q.col(i): i-th vertex orientation of the triangle mesh, guaranteed to be orthogonal to N.col(i)
+ * O.col(i): "quad position" associated with the i-th vertex in the triangle mesh (see InstantMesh
+ * position field)
+ * F.col(i): i-th triangle of the triangle mesh
+ *
+ * V2E[i]: one directed edge from the i-th vertex of the triangle mesh
+ * E2E[i]: the reverse directed edge's index of the i-th directed edge of the triangle mesh
+ *
+ * j = disajoint_tree.Index(i)
+ * the j-th vertex of the quad mesh is corresponding to the i-th vertex of the triangle mesh
+ * the relation is one-to-multiple
+ * O_quad can be viewed as an average of corresponding O
+ * N_quad can be viewed as an average of corresponding N
+ * Q_quad can be viewed as aggregation of corresponding Q
+ * Method that aggregates qi to qj with weights wi and wj:
+ * value = compat_orientation_extrinsic_4(qj, nj, qi, ni)
+ * result = (value.first * wj + value.second * wi).normalized()
+ *
+ * Output:
+ * Optimized O_quad, (possibly N_quad)
+ */
+void optimize_quad_positions(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad,
+ std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad,
+ VectorXi& V2E_quad, std::vector<int>& E2E_quad, MatrixXd& V, MatrixXd& N,
+ MatrixXd& Q, MatrixXd& O, MatrixXi& F, VectorXi& V2E, VectorXi& E2E,
+ DisajointTree& disajoint_tree, double reference_length,
+ bool just_serialize = true);
+
+} // namespace qflow
+
+#endif /* post_solver_h */
diff --git a/extern/quadriflow/src/serialize.hpp b/extern/quadriflow/src/serialize.hpp
new file mode 100644
index 00000000000..3e670e02fa5
--- /dev/null
+++ b/extern/quadriflow/src/serialize.hpp
@@ -0,0 +1,127 @@
+#ifndef SERIALIZE_H_
+#define SERIALIZE_H_
+
+#include <Eigen/Core>
+#include <fstream>
+#include <map>
+#include <set>
+#include <vector>
+#include "adjacent-matrix.hpp"
+
+namespace qflow {
+
+template <typename T, int A, int B>
+inline void Save(FILE* fp, const Eigen::Matrix<T, A, B>& m) {
+ int r = m.rows(), c = m.cols();
+ fwrite(&r, sizeof(int), 1, fp);
+ fwrite(&c, sizeof(int), 1, fp);
+ std::vector<T> buffer(r * c);
+ for (int i = 0; i < r; ++i) {
+ for (int j = 0; j < c; ++j) {
+ buffer[i * c + j] = m(i, j);
+ }
+ }
+ fwrite(buffer.data(), sizeof(T), r * c, fp);
+}
+
+template <typename T, int A, int B>
+inline void Read(FILE* fp, Eigen::Matrix<T, A, B>& m) {
+ int r, c;
+ fread(&r, sizeof(int), 1, fp);
+ fread(&c, sizeof(int), 1, fp);
+ std::vector<T> buffer(r * c);
+ fread(buffer.data(), sizeof(T), r * c, fp);
+ m.resize(r, c);
+ for (int i = 0; i < r; ++i) {
+ for (int j = 0; j < c; ++j) {
+ m(i, j) = buffer[i * c + j];
+ }
+ }
+}
+
+inline void Save(FILE* fp, const Link& p) { fwrite(&p, sizeof(Link), 1, fp); }
+
+inline void Read(FILE* fp, Link& p) { fread(&p, sizeof(Link), 1, fp); }
+
+inline void Save(FILE* fp, const TaggedLink& p) { fwrite(&p, sizeof(TaggedLink), 1, fp); }
+
+inline void Read(FILE* fp, TaggedLink& p) { fread(&p, sizeof(TaggedLink), 1, fp); }
+
+inline void Save(FILE* fp, double p) { fwrite(&p, sizeof(double), 1, fp); }
+
+inline void Read(FILE* fp, double& p) { fread(&p, sizeof(double), 1, fp); }
+
+inline void Save(FILE* fp, int p) { fwrite(&p, sizeof(int), 1, fp); }
+
+inline void Read(FILE* fp, int& p) { fread(&p, sizeof(int), 1, fp); }
+
+template <class T, class F>
+inline void Save(FILE* fp, const std::pair<T, F>& p) {
+ fwrite(&p.first, sizeof(T), 1, fp);
+ fwrite(&p.second, sizeof(F), 1, fp);
+}
+
+template <class T, class F>
+inline void Read(FILE* fp, std::pair<T, F>& p) {
+ fread(&p.first, sizeof(T), 1, fp);
+ fread(&p.second, sizeof(F), 1, fp);
+}
+
+template <class T, class F>
+inline void Save(FILE* fp, const std::map<T, F>& p) {
+ int num = p.size();
+ fwrite(&num, sizeof(int), 1, fp);
+ for (auto& s : p) {
+ fwrite(&s, sizeof(s), 1, fp);
+ }
+}
+
+template <class T, class F>
+inline void Read(FILE* fp, std::map<T, F>& p) {
+ int num;
+ p.clear();
+ fread(&num, sizeof(int), 1, fp);
+ for (int i = 0; i < num; ++i) {
+ std::pair<T, F> m;
+ fread(&m, sizeof(m), 1, fp);
+ p.insert(m);
+ }
+}
+
+template <class T>
+void Save(FILE* fp, const std::vector<T>& p) {
+ int num = p.size();
+ fwrite(&num, sizeof(int), 1, fp);
+ for (auto& q : p) {
+ Save(fp, q);
+ }
+}
+
+template <class T>
+void Read(FILE* fp, std::vector<T>& p) {
+ int num;
+ fread(&num, sizeof(int), 1, fp);
+ p.resize(num);
+ for (auto& q : p) {
+ Read(fp, q);
+ }
+}
+
+template <class T>
+void Save(FILE* fp, const std::set<T>& p) {
+ std::vector<T> buffer;
+ buffer.insert(buffer.end(), p.begin(), p.end());
+ Save(fp, buffer);
+}
+
+template <class T>
+void Read(FILE* fp, std::set<T>& p) {
+ std::vector<T> buffer;
+ Read(fp, buffer);
+ p.clear();
+ for (auto& q : buffer) p.insert(q);
+}
+
+} // namespace qflow
+
+#endif
diff --git a/extern/quadriflow/src/subdivide.cpp b/extern/quadriflow/src/subdivide.cpp
new file mode 100644
index 00000000000..c408bbc6394
--- /dev/null
+++ b/extern/quadriflow/src/subdivide.cpp
@@ -0,0 +1,516 @@
+#include "subdivide.hpp"
+
+#include <fstream>
+#include <queue>
+
+#include "dedge.hpp"
+#include "disajoint-tree.hpp"
+#include "field-math.hpp"
+#include "parametrizer.hpp"
+
+namespace qflow {
+
+void subdivide(MatrixXi &F, MatrixXd &V, VectorXd& rho, VectorXi &V2E, VectorXi &E2E, VectorXi &boundary,
+ VectorXi &nonmanifold, double maxLength) {
+ typedef std::pair<double, int> Edge;
+
+ std::priority_queue<Edge> queue;
+
+ maxLength *= maxLength;
+
+ for (int i = 0; i < E2E.size(); ++i) {
+ int v0 = F(i % 3, i / 3), v1 = F((i + 1) % 3, i / 3);
+ if (nonmanifold[v0] || nonmanifold[v1]) continue;
+ double length = (V.col(v0) - V.col(v1)).squaredNorm();
+ if (length > maxLength || length > std::max(maxLength * 0.75, std::min(rho[v0], rho[v1]) * 1.0)) {
+ int other = E2E[i];
+ if (other == -1 || other > i) queue.push(Edge(length, i));
+ }
+ }
+
+ int nV = V.cols(), nF = F.cols(), nSplit = 0;
+ /*
+ / v0 \
+ v1p 1 | 0 v0p
+ \ v1 /
+
+ / v0 \
+ / 1 | 0 \
+ v1p - vn - v0p
+ \ 2 | 3 /
+ \ v1 /
+
+ f0: vn, v0p, v0
+ f1: vn, v0, v1p
+ f2: vn, v1p, v1
+ f3: vn, v1, v0p
+ */
+ int counter = 0;
+ while (!queue.empty()) {
+ counter += 1;
+ Edge edge = queue.top();
+ queue.pop();
+ int e0 = edge.second, e1 = E2E[e0];
+ bool is_boundary = e1 == -1;
+ int f0 = e0 / 3, f1 = is_boundary ? -1 : (e1 / 3);
+ int v0 = F(e0 % 3, f0), v0p = F((e0 + 2) % 3, f0), v1 = F((e0 + 1) % 3, f0);
+ if ((V.col(v0) - V.col(v1)).squaredNorm() != edge.first) {
+ continue;
+ }
+ int v1p = is_boundary ? -1 : F((e1 + 2) % 3, f1);
+ int vn = nV++;
+ nSplit++;
+ /* Update V */
+ if (nV > V.cols()) {
+ V.conservativeResize(V.rows(), V.cols() * 2);
+ rho.conservativeResize(V.cols() * 2);
+ V2E.conservativeResize(V.cols());
+ boundary.conservativeResize(V.cols());
+ nonmanifold.conservativeResize(V.cols());
+ }
+
+ /* Update V */
+ V.col(vn) = (V.col(v0) + V.col(v1)) * 0.5f;
+ rho[vn] = 0.5f * (rho[v0], rho[v1]);
+ nonmanifold[vn] = false;
+ boundary[vn] = is_boundary;
+
+ /* Update F and E2E */
+ int f2 = is_boundary ? -1 : (nF++);
+ int f3 = nF++;
+ if (nF > F.cols()) {
+ F.conservativeResize(F.rows(), std::max(nF, (int)F.cols() * 2));
+ E2E.conservativeResize(F.cols() * 3);
+ }
+
+ /* Update F */
+ F.col(f0) << vn, v0p, v0;
+ if (!is_boundary) {
+ F.col(f1) << vn, v0, v1p;
+ F.col(f2) << vn, v1p, v1;
+ }
+ F.col(f3) << vn, v1, v0p;
+
+ /* Update E2E */
+ const int e0p = E2E[dedge_prev_3(e0)], e0n = E2E[dedge_next_3(e0)];
+
+#define sE2E(a, b) \
+ E2E[a] = b; \
+ if (b != -1) E2E[b] = a;
+ sE2E(3 * f0 + 0, 3 * f3 + 2);
+ sE2E(3 * f0 + 1, e0p);
+ sE2E(3 * f3 + 1, e0n);
+ if (is_boundary) {
+ sE2E(3 * f0 + 2, -1);
+ sE2E(3 * f3 + 0, -1);
+ } else {
+ const int e1p = E2E[dedge_prev_3(e1)], e1n = E2E[dedge_next_3(e1)];
+ sE2E(3 * f0 + 2, 3 * f1 + 0);
+ sE2E(3 * f1 + 1, e1n);
+ sE2E(3 * f1 + 2, 3 * f2 + 0);
+ sE2E(3 * f2 + 1, e1p);
+ sE2E(3 * f2 + 2, 3 * f3 + 0);
+ }
+#undef sE2E
+
+ /* Update V2E */
+ V2E[v0] = 3 * f0 + 2;
+ V2E[vn] = 3 * f0 + 0;
+ V2E[v1] = 3 * f3 + 1;
+ V2E[v0p] = 3 * f0 + 1;
+ if (!is_boundary) V2E[v1p] = 3 * f1 + 2;
+
+ auto schedule = [&](int f) {
+ for (int i = 0; i < 3; ++i) {
+ double length = (V.col(F(i, f)) - V.col(F((i + 1) % 3, f))).squaredNorm();
+ if (length > maxLength
+ || length > std::max(maxLength * 0.75, std::min(rho[F(i, f)], rho[F((i + 1) % 3, f)]) * 1.0))
+ queue.push(Edge(length, f * 3 + i));
+ }
+ };
+
+ schedule(f0);
+ if (!is_boundary) {
+ schedule(f2);
+ schedule(f1);
+ };
+ schedule(f3);
+ }
+ F.conservativeResize(F.rows(), nF);
+ V.conservativeResize(V.rows(), nV);
+ rho.conservativeResize(nV);
+ V2E.conservativeResize(nV);
+ boundary.conservativeResize(nV);
+ nonmanifold.conservativeResize(nV);
+ E2E.conservativeResize(nF * 3);
+}
+
+void subdivide_edgeDiff(MatrixXi &F, MatrixXd &V, MatrixXd &N, MatrixXd &Q, MatrixXd &O, MatrixXd* S,
+ VectorXi &V2E, VectorXi &E2E, VectorXi &boundary, VectorXi &nonmanifold,
+ std::vector<Vector2i> &edge_diff, std::vector<DEdge> &edge_values,
+ std::vector<Vector3i> &face_edgeOrients, std::vector<Vector3i> &face_edgeIds,
+ std::vector<int>& sharp_edges, std::map<int, int> &singularities, int max_len) {
+ struct EdgeLink {
+ int id;
+ double length;
+ Vector2i diff;
+ int maxlen() const { return std::max(abs(diff[0]), abs(diff[1])); }
+ bool operator<(const EdgeLink &link) const { return maxlen() < link.maxlen(); }
+ };
+
+ struct FaceOrient {
+ int orient;
+ Vector3i d;
+ Vector3d q;
+ Vector3d n;
+ };
+
+ std::vector<FaceOrient> face_spaces(F.cols());
+ std::priority_queue<EdgeLink> queue;
+ std::vector<Vector2i> diffs(E2E.size());
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ int eid = i * 3 + j;
+ diffs[eid] = rshift90(edge_diff[face_edgeIds[i][j]], face_edgeOrients[i][j]);
+ }
+ }
+ for (int i = 0; i < F.cols(); ++i) {
+ FaceOrient orient{};
+ orient.q = Q.col(F(0, i));
+ orient.n = N.col(F(0, i));
+ int orient_diff[3];
+ for (int j = 0; j < 3; ++j) {
+ int final_orient = face_edgeOrients[i][j];
+ int eid = face_edgeIds[i][j];
+ auto value = compat_orientation_extrinsic_index_4(
+ Q.col(edge_values[eid].x), N.col(edge_values[eid].x), orient.q, orient.n);
+ int target_orient = (value.second - value.first + 4) % 4;
+ if (F(j, i) == edge_values[eid].y) target_orient = (target_orient + 2) % 4;
+ orient_diff[j] = (final_orient - target_orient + 4) % 4;
+ }
+ if (orient_diff[0] == orient_diff[1])
+ orient.orient = orient_diff[0];
+ else if (orient_diff[0] == orient_diff[2])
+ orient.orient = orient_diff[2];
+ else if (orient_diff[1] == orient_diff[2])
+ orient.orient = orient_diff[1];
+ orient.d = Vector3i((orient_diff[0] - orient.orient + 4) % 4,
+ (orient_diff[1] - orient.orient + 4) % 4,
+ (orient_diff[2] - orient.orient + 4) % 4);
+ face_spaces[i] = (orient);
+ }
+ for (int i = 0; i < E2E.size(); ++i) {
+ int v0 = F(i % 3, i / 3), v1 = F((i + 1) % 3, i / 3);
+ if (nonmanifold[v0] || nonmanifold[v1]) continue;
+ double length = (V.col(v0) - V.col(v1)).squaredNorm();
+ Vector2i diff = diffs[i];
+ if (abs(diff[0]) > max_len || abs(diff[1]) > max_len) {
+ int other = E2E[i];
+ if (other == -1 || other > i) {
+ EdgeLink e;
+ e.id = i;
+ e.length = length;
+ e.diff = diff;
+ queue.push(e);
+ }
+ }
+ }
+ auto AnalyzeOrient = [&](int f0, const Vector3i &d) {
+ for (int j = 0; j < 3; ++j) {
+ int orient = face_spaces[f0].orient + d[j];
+ int v = std::min(F(j, f0), F((j + 1) % 3, f0));
+ auto value = compat_orientation_extrinsic_index_4(
+ Q.col(v), N.col(v), face_spaces[f0].q, face_spaces[f0].n);
+ if (F(j, f0) != v) orient += 2;
+ face_edgeOrients[f0][j] = (orient + value.second - value.first + 4) % 4;
+ }
+ face_spaces[f0].d = d;
+ for (int j = 0; j < 3; ++j) {
+ int eid = face_edgeIds[f0][j];
+ int orient = face_edgeOrients[f0][j];
+ auto diff = rshift90(diffs[f0 * 3 + j], (4 - orient) % 4);
+ edge_diff[eid] = diff;
+ }
+ };
+ auto FixOrient = [&](int f0) {
+ for (int j = 0; j < 3; ++j) {
+ auto diff = edge_diff[face_edgeIds[f0][j]];
+ if (rshift90(diff, face_edgeOrients[f0][j]) != diffs[f0 * 3 + j]) {
+ int orient = 0;
+ while (orient < 4 && rshift90(diff, orient) != diffs[f0 * 3 + j]) orient += 1;
+ face_spaces[f0].d[j] =
+ (face_spaces[f0].d[j] + orient - face_edgeOrients[f0][j]) % 4;
+ face_edgeOrients[f0][j] = orient;
+ }
+ }
+ };
+ /*
+ auto Length = [&](int f0) {
+ int l = 0;
+ for (int j = 0; j < 3; ++j) {
+ for (int k = 0; k < 2; ++k) {
+ l += abs(diffs[f0*3+j][k]);
+ }
+ printf("<%d %d> ", diffs[f0*3+j][0], diffs[f0*3+j][1]);
+ }
+ printf("\n");
+ return l;
+ };
+ */
+ int nV = V.cols(), nF = F.cols(), nSplit = 0;
+ /*
+ / v0 \
+ v1p 1 | 0 v0p
+ \ v1 /
+
+ / v0 \
+ / 1 | 0 \
+ v1p - vn - v0p
+ \ 2 | 3 /
+ \ v1 /
+
+ f0: vn, v0p, v0
+ f1: vn, v0, v1p
+ f2: vn, v1p, v1
+ f3: vn, v1, v0p
+ */
+ int counter = 0;
+ while (!queue.empty()) {
+ counter += 1;
+ EdgeLink edge = queue.top();
+ queue.pop();
+
+ int e0 = edge.id, e1 = E2E[e0];
+ bool is_boundary = e1 == -1;
+ int f0 = e0 / 3, f1 = is_boundary ? -1 : (e1 / 3);
+ int v0 = F(e0 % 3, f0), v0p = F((e0 + 2) % 3, f0), v1 = F((e0 + 1) % 3, f0);
+ if ((V.col(v0) - V.col(v1)).squaredNorm() != edge.length) {
+ continue;
+ }
+ if (abs(diffs[e0][0]) < 2 && abs(diffs[e0][1]) < 2) continue;
+ if (f1 != -1) {
+ face_edgeOrients.push_back(Vector3i());
+ sharp_edges.push_back(0);
+ sharp_edges.push_back(0);
+ sharp_edges.push_back(0);
+ face_edgeIds.push_back(Vector3i());
+ }
+ int v1p = is_boundary ? -1 : F((e1 + 2) % 3, f1);
+ int vn = nV++;
+ nSplit++;
+ if (nV > V.cols()) {
+ V.conservativeResize(V.rows(), V.cols() * 2);
+ N.conservativeResize(N.rows(), N.cols() * 2);
+ Q.conservativeResize(Q.rows(), Q.cols() * 2);
+ O.conservativeResize(O.rows(), O.cols() * 2);
+ if (S)
+ S->conservativeResize(S->rows(), S->cols() * 2);
+ V2E.conservativeResize(V.cols());
+ boundary.conservativeResize(V.cols());
+ nonmanifold.conservativeResize(V.cols());
+ }
+
+ V.col(vn) = (V.col(v0) + V.col(v1)) * 0.5;
+ N.col(vn) = N.col(v0);
+ Q.col(vn) = Q.col(v0);
+ O.col(vn) = (O.col(v0) + O.col(v1)) * 0.5;
+ if (S)
+ S->col(vn) = S->col(v0);
+
+ nonmanifold[vn] = false;
+ boundary[vn] = is_boundary;
+
+ int eid = face_edgeIds[f0][e0 % 3];
+ int sharp_eid = sharp_edges[e0];
+ int eid01 = face_edgeIds[f0][(e0 + 1) % 3];
+ int sharp_eid01 = sharp_edges[f0 * 3 + (e0 + 1) % 3];
+ int eid02 = face_edgeIds[f0][(e0 + 2) % 3];
+ int sharp_eid02 = sharp_edges[f0 * 3 + (e0 + 2) % 3];
+
+ int eid0, eid1, eid0p, eid1p;
+ int sharp_eid0, sharp_eid1, sharp_eid0p, sharp_eid1p;
+
+ eid0 = eid;
+ sharp_eid0 = sharp_eid;
+ edge_values[eid0] = DEdge(v0, vn);
+
+ eid1 = edge_values.size();
+ sharp_eid1 = sharp_eid;
+ edge_values.push_back(DEdge(vn, v1));
+ edge_diff.push_back(Vector2i());
+
+ eid0p = edge_values.size();
+ sharp_eid0p = 0;
+ edge_values.push_back(DEdge(vn, v0p));
+ edge_diff.push_back(Vector2i());
+
+ int f2 = is_boundary ? -1 : (nF++);
+ int f3 = nF++;
+ sharp_edges.push_back(0);
+ sharp_edges.push_back(0);
+ sharp_edges.push_back(0);
+ face_edgeIds.push_back(Vector3i());
+ face_edgeOrients.push_back(Vector3i());
+
+ if (nF > F.cols()) {
+ F.conservativeResize(F.rows(), std::max(nF, (int)F.cols() * 2));
+ face_spaces.resize(F.cols());
+ E2E.conservativeResize(F.cols() * 3);
+ diffs.resize(F.cols() * 3);
+ }
+
+ auto D01 = diffs[e0];
+ auto D1p = diffs[e0 / 3 * 3 + (e0 + 1) % 3];
+ auto Dp0 = diffs[e0 / 3 * 3 + (e0 + 2) % 3];
+
+ Vector2i D0n = D01 / 2;
+
+ auto orients1 = face_spaces[f0];
+ F.col(f0) << vn, v0p, v0;
+ face_edgeIds[f0] = Vector3i(eid0p, eid02, eid0);
+ sharp_edges[f0 * 3] = sharp_eid0p;
+ sharp_edges[f0 * 3 + 1] = sharp_eid02;
+ sharp_edges[f0 * 3 + 2] = sharp_eid0;
+
+ diffs[f0 * 3] = D01 + D1p - D0n;
+ diffs[f0 * 3 + 1] = Dp0;
+ diffs[f0 * 3 + 2] = D0n;
+ int o1 = e0 % 3, o2 = e1 % 3;
+ AnalyzeOrient(f0, Vector3i(0, orients1.d[(o1 + 2) % 3], orients1.d[o1]));
+ if (!is_boundary) {
+ auto orients2 = face_spaces[f1];
+ int eid11 = face_edgeIds[f1][(e1 + 1) % 3];
+ int sharp_eid11 = sharp_edges[f1 * 3 + (e1 + 1) % 3];
+ int eid12 = face_edgeIds[f1][(e1 + 2) % 3];
+ int sharp_eid12 = sharp_edges[f1 * 3 + (e1 + 2) % 3];
+
+ auto Ds10 = diffs[e1];
+ auto Ds0p = diffs[e1 / 3 * 3 + (e1 + 1) % 3];
+
+ auto Dsp1 = diffs[e1 / 3 * 3 + (e1 + 2) % 3];
+ int orient = 0;
+ while (rshift90(D01, orient) != Ds10) orient += 1;
+ Vector2i Dsn0 = rshift90(D0n, orient);
+
+ F.col(f1) << vn, v0, v1p;
+ eid1p = edge_values.size();
+ sharp_eid1p = 0;
+ edge_values.push_back(DEdge(vn, v1p));
+ edge_diff.push_back(Vector2i());
+
+ sharp_edges[f1 * 3] = sharp_eid0;
+ sharp_edges[f1 * 3 + 1] = sharp_eid11;
+ sharp_edges[f1 * 3 + 2] = sharp_eid1p;
+ face_edgeIds[f1] = (Vector3i(eid0, eid11, eid1p));
+ diffs[f1 * 3] = Dsn0;
+ diffs[f1 * 3 + 1] = Ds0p;
+ diffs[f1 * 3 + 2] = Dsp1 + (Ds10 - Dsn0);
+
+ AnalyzeOrient(f1, Vector3i(orients2.d[o2], orients2.d[(o2 + 1) % 3], 0));
+
+ face_spaces[f2] = face_spaces[f1];
+ sharp_edges[f2 * 3] = sharp_eid1p;
+ sharp_edges[f2 * 3 + 1] = sharp_eid12;
+ sharp_edges[f2 * 3 + 2] = sharp_eid1;
+ face_edgeIds[f2] = (Vector3i(eid1p, eid12, eid1));
+ F.col(f2) << vn, v1p, v1;
+ diffs[f2 * 3] = -Dsp1 - (Ds10 - Dsn0);
+ diffs[f2 * 3 + 1] = Dsp1;
+ diffs[f2 * 3 + 2] = Ds10 - Dsn0;
+
+ AnalyzeOrient(f2, Vector3i(0, orients2.d[(o2 + 2) % 3], orients2.d[o2]));
+ }
+ face_spaces[f3] = face_spaces[f0];
+ sharp_edges[f3 * 3] = sharp_eid1;
+ sharp_edges[f3 * 3 + 1] = sharp_eid01;
+ sharp_edges[f3 * 3 + 2] = sharp_eid0p;
+ face_edgeIds[f3] = (Vector3i(eid1, eid01, eid0p));
+ F.col(f3) << vn, v1, v0p;
+ diffs[f3 * 3] = D01 - D0n;
+ diffs[f3 * 3 + 1] = D1p;
+ diffs[f3 * 3 + 2] = D0n - (D01 + D1p);
+
+ AnalyzeOrient(f3, Vector3i(orients1.d[o1], orients1.d[(o1 + 1) % 3], 0));
+
+ FixOrient(f0);
+ if (!is_boundary) {
+ FixOrient(f1);
+ FixOrient(f2);
+ }
+ FixOrient(f3);
+
+ const int e0p = E2E[dedge_prev_3(e0)], e0n = E2E[dedge_next_3(e0)];
+
+#define sE2E(a, b) \
+ E2E[a] = b; \
+ if (b != -1) E2E[b] = a;
+ sE2E(3 * f0 + 0, 3 * f3 + 2);
+ sE2E(3 * f0 + 1, e0p);
+ sE2E(3 * f3 + 1, e0n);
+ if (is_boundary) {
+ sE2E(3 * f0 + 2, -1);
+ sE2E(3 * f3 + 0, -1);
+ } else {
+ const int e1p = E2E[dedge_prev_3(e1)], e1n = E2E[dedge_next_3(e1)];
+ sE2E(3 * f0 + 2, 3 * f1 + 0);
+ sE2E(3 * f1 + 1, e1n);
+ sE2E(3 * f1 + 2, 3 * f2 + 0);
+ sE2E(3 * f2 + 1, e1p);
+ sE2E(3 * f2 + 2, 3 * f3 + 0);
+ }
+#undef sE2E
+
+ V2E[v0] = 3 * f0 + 2;
+ V2E[vn] = 3 * f0 + 0;
+ V2E[v1] = 3 * f3 + 1;
+ V2E[v0p] = 3 * f0 + 1;
+ if (!is_boundary) V2E[v1p] = 3 * f1 + 2;
+
+ auto schedule = [&](int f) {
+ for (int i = 0; i < 3; ++i) {
+ if (abs(diffs[f * 3 + i][0]) > max_len || abs(diffs[f * 3 + i][1]) > max_len) {
+ EdgeLink e;
+ e.id = f * 3 + i;
+ e.length = (V.col(F((i + 1) % 3, f)) - V.col(F(i, f))).squaredNorm();
+ e.diff = diffs[f * 3 + i];
+ queue.push(e);
+ }
+ }
+ };
+
+ schedule(f0);
+ if (!is_boundary) {
+ schedule(f2);
+ schedule(f1);
+ };
+ schedule(f3);
+ }
+ F.conservativeResize(F.rows(), nF);
+ V.conservativeResize(V.rows(), nV);
+ N.conservativeResize(V.rows(), nV);
+ Q.conservativeResize(V.rows(), nV);
+ O.conservativeResize(V.rows(), nV);
+ if (S)
+ S->conservativeResize(S->rows(), nV);
+ V2E.conservativeResize(nV);
+ boundary.conservativeResize(nV);
+ nonmanifold.conservativeResize(nV);
+ E2E.conservativeResize(nF * 3);
+ for (int i = 0; i < F.cols(); ++i) {
+ for (int j = 0; j < 3; ++j) {
+ auto diff = edge_diff[face_edgeIds[i][j]];
+ if (abs(diff[0]) > 1 || abs(diff[1]) > 1) {
+ printf("wrong init %d %d!\n", face_edgeIds[i][j], i * 3 + j);
+ exit(0);
+ }
+ }
+ }
+ for (int i = 0; i < edge_diff.size(); ++i) {
+ if (abs(edge_diff[i][0]) > 1 || abs(edge_diff[i][1]) > 1) {
+ printf("wrong...\n");
+ exit(0);
+ }
+ }
+}
+
+} // namespace qflow
diff --git a/extern/quadriflow/src/subdivide.hpp b/extern/quadriflow/src/subdivide.hpp
new file mode 100644
index 00000000000..a93c58ac2a7
--- /dev/null
+++ b/extern/quadriflow/src/subdivide.hpp
@@ -0,0 +1,17 @@
+#include <Eigen/Core>
+#include <Eigen/Dense>
+
+#include "parametrizer.hpp"
+using namespace Eigen;
+
+namespace qflow {
+
+void subdivide(MatrixXi &F, MatrixXd &V, VectorXd& rho, VectorXi &V2E, VectorXi &E2E, VectorXi &boundary,
+ VectorXi &nonmanifold, double maxLength);
+
+void subdivide_edgeDiff(MatrixXi &F, MatrixXd &V, MatrixXd &N, MatrixXd &Q, MatrixXd &O, MatrixXd* S,
+ VectorXi &V2E, VectorXi &E2E, VectorXi &boundary, VectorXi &nonmanifold,
+ std::vector<Vector2i> &edge_diff, std::vector<DEdge> &edge_values,
+ std::vector<Vector3i> &face_edgeOrients, std::vector<Vector3i> &face_edgeIds,
+ std::vector<int>& sharp_edges, std::map<int, int> &singularities, int max_len);
+} // namespace qflow